radio

radio.ircforever.org
git clone git://git.ircforever.org/radio
Log | Files | Refs | Submodules | README | LICENSE

main.c (13222B)


      1 /* This work is in the public domain. See COPYING file for details. */
      2 
      3 #include <ctype.h>	/* tolower */
      4 #include <errno.h>
      5 #include <stdio.h>
      6 #include <stdlib.h>
      7 #include <string.h>
      8 
      9 #include "util.h"
     10 #include "thread.h"
     11 
     12 #define HTTP_IMPLEMENTATION
     13 #include "http.h"
     14 #include "pdjson/pdjson.h"
     15 
     16 #define THREAD_FILE "threads/thread.db"
     17 
     18 enum scope {
     19 	SCOPE_NONE,
     20 	SCOPE_ROOT,
     21 	SCOPE_ICESTATES,
     22 	SCOPE_SOURCE
     23 };
     24 
     25 struct icestats {
     26 	char	*admin;
     27 	char	*host;
     28 	char	*location;
     29 	char	*server_id;
     30 	char	*server_start;
     31 	char	*server_start_iso8601;
     32 	char	*dummy;
     33 };
     34 
     35 struct source {
     36 	char	*audio_bitrate;
     37 	char	*audio_channels;
     38 	char	*audio_info;
     39 	char	*audio_samplerate;
     40 	char	*bitrate;
     41 	char	*channels;
     42 	char	*genre;
     43 	char	*ice_bitrate;
     44 	char	*listener_peak;
     45 	char	*listeners;
     46 	char	*listenurl;
     47 	char	*quality;
     48 	char	*samplerate;
     49 	char	*server_description;
     50 	char	*server_name;
     51 	char	*server_type;
     52 	char	*server_url;
     53 	char	*stream_start;
     54 	char	*stream_start_iso8601;
     55 	char	*subtype;
     56 	char	*title;
     57 	char	*dummy;
     58 	char	*url;
     59 };
     60 
     61 struct icestats	icestats;
     62 struct source	srces[MAX_SOURCES];
     63 int		srclen;
     64 int		header_sent;
     65 
     66 void
     67 not_found(void)
     68 {
     69 	puts("<h1>404 Not Found</h1>");
     70 	puts("<p>This is not a joke.</p>");
     71 	exit(1);
     72 }
     73 
     74 void
     75 format_to_url(char *s)
     76 {
     77 	for(int i = 0; i < (int)strlen(s); i++) {
     78 		if (s[i] == ' ')
     79 			s[i] = '_';
     80 		else
     81 			s[i] = tolower(s[i]);
     82 	}
     83 }
     84 
     85 char *
     86 json_get_value(json_stream *json)
     87 {
     88 	enum json_type type = json_next(json);
     89 	if (type == JSON_NUMBER || type == JSON_STRING)
     90 		return strdup(json_get_string(json, NULL));
     91 	else
     92 		return NULL;
     93 }
     94 
     95 void
     96 icestats_set(struct icestats *s, json_stream *json, const char *key)
     97 {
     98 	char **var = NULL;
     99 
    100 	     if (strcmp(key, "admin"			) == 0) var = &s->admin;
    101 	else if (strcmp(key, "host"			) == 0) var = &s->host;
    102 	else if (strcmp(key, "location"			) == 0) var = &s->location;
    103 	else if (strcmp(key, "server_id"		) == 0) var = &s->server_id;
    104 	else if (strcmp(key, "server_start"		) == 0) var = &s->server_start;
    105 	else if (strcmp(key, "server_start_iso8601"	) == 0) var = &s->server_start_iso8601;
    106 	else if (strcmp(key, "dummy"			) == 0) var = &s->dummy;
    107 
    108 	if (var == NULL)
    109 		fatal("failed to handle icestats key: %s", key);
    110 	else
    111 		*var = json_get_value(json);
    112 }
    113 
    114 void
    115 icestats_print(struct icestats *s)
    116 {
    117 	puts("<table class='about'>");
    118 	printf("<tr><td>Admin</td>		<td>%s</td></tr>\n", s->admin);
    119 	printf("<tr><td>Host</td>		<td>%s</td></tr>\n", s->host);
    120 	printf("<tr><td>Location</td>		<td>%s</td></tr>\n", s->location);
    121 	printf("<tr><td>Server ID</td>		<td>%s</td></tr>\n", s->server_id);
    122 	printf("<tr><td>Server Start</td>	<td>%s</td></tr>\n", s->server_start);
    123 	puts("</table>");
    124 	puts("<hr>");
    125 }
    126 
    127 void
    128 icestats_free(struct icestats *s)
    129 {
    130 	free(s->admin);
    131 	free(s->host);
    132 	free(s->location);
    133 	free(s->server_id);
    134 	free(s->server_start);
    135 	free(s->server_start_iso8601);
    136 }
    137 
    138 void
    139 source_set(struct source *s, json_stream *json, const char *key)
    140 {
    141 	char **var = NULL;
    142 
    143 	     if (strcmp(key, "audio_bitrate"		) == 0) var = &s->audio_bitrate;
    144 	else if (strcmp(key, "audio_channels"		) == 0) var = &s->audio_channels;
    145 	else if (strcmp(key, "audio_info"		) == 0) var = &s->audio_info;
    146 	else if (strcmp(key, "audio_samplerate"		) == 0) var = &s->audio_samplerate;
    147 	else if (strcmp(key, "bitrate"			) == 0) var = &s->bitrate;
    148 	else if (strcmp(key, "channels"			) == 0) var = &s->channels;
    149 	else if (strcmp(key, "genre"			) == 0) var = &s->genre;
    150 	else if (strcmp(key, "ice-bitrate"		) == 0) var = &s->ice_bitrate;
    151 	else if (strcmp(key, "listener_peak"		) == 0) var = &s->listener_peak;
    152 	else if (strcmp(key, "listeners"		) == 0) var = &s->listeners;
    153 	else if (strcmp(key, "listenurl"		) == 0) var = &s->listenurl;
    154 	else if (strcmp(key, "quality"			) == 0) var = &s->quality;
    155 	else if (strcmp(key, "samplerate"		) == 0) var = &s->samplerate;
    156 	else if (strcmp(key, "server_description"	) == 0) var = &s->server_description;
    157 	else if (strcmp(key, "server_name"		) == 0) var = &s->server_name;
    158 	else if (strcmp(key, "server_type"		) == 0) var = &s->server_type;
    159 	else if (strcmp(key, "server_url"		) == 0) var = &s->server_url;
    160 	else if (strcmp(key, "stream_start"		) == 0) var = &s->stream_start;
    161 	else if (strcmp(key, "stream_start_iso8601"	) == 0) var = &s->stream_start_iso8601;
    162 	else if (strcmp(key, "subtype"			) == 0) var = &s->subtype;
    163 	else if (strcmp(key, "title"			) == 0) var = &s->title;
    164 	else if (strcmp(key, "dummy"			) == 0) var = &s->dummy;
    165 
    166 	if (var == NULL) {
    167 		fatal("failed to handle source key: %s", key);
    168 	} else if (var == &s->listenurl) {
    169 		/* change http to https and remove port */
    170 		/* syntax: http://radio.theinterlude.live:8000/autodj */
    171 		char *tmp, *host, *port, *mount;
    172 		size_t len;
    173 
    174 		tmp = json_get_value(json);
    175 		if ((host = strchr(tmp, '/')) == NULL || *(++host) != '/')
    176 			goto err;
    177 		host++;
    178 		if ((port = strchr(host, ':')) == NULL)
    179 			goto err;
    180 		*port++ = '\0';
    181 		if ((mount = strchr(port, '/')) == NULL)
    182 			goto err;
    183 		*mount++ = '\0';
    184 
    185 		len = 8 /* https:// */ + strlen(host) + 1 /* slash */ +
    186 			strlen(mount) + 1 /* NULL */;
    187 		*var = emalloc(len);
    188 		snprintf(*var, len, "https://%s/%s", host, mount);
    189 		free(tmp);
    190 		return;
    191 
    192 err:
    193 		fatal("syntax error in stream URL");
    194 	} else {
    195 		*var = json_get_value(json);
    196 		if (var == &s->server_name) {
    197 			int len = strlen("/stream/") + strlen(s->server_name) + 1;
    198 			s->url = emalloc(len * sizeof(char));
    199 			strcpy(s->url, "/stream/");
    200 			strcat(s->url, s->server_name);
    201 			format_to_url(s->url);
    202 		}
    203 	}
    204 }
    205 
    206 void
    207 source_print(struct source *s)
    208 {
    209 	puts("<div class='stream'>");
    210 
    211 	/* thumbnail and player */
    212 	puts("<div class='player'>");
    213 	puts("<img src='/music.svg' alt='thumbnail' width='256' height='256'>");
    214 	puts("<audio controls>");
    215 	printf("<source src='%s' type='application/ogg'>\n", s->listenurl);
    216 	puts("Your browser does not support the video tag.");
    217 	puts("</audio>");
    218 	puts("</div>");
    219 
    220 	printf("<a class='title' href='%s'>%s</a>\n", s->url, s->server_name);
    221 
    222 	puts("<table>");
    223 	printf("<tr><td>Stream Description</td>	<td>%s</td></tr>\n", s->server_description);
    224 	printf("<tr><td>Stream Type</td>	<td>%s</td></tr>\n", s->server_type);
    225 	printf("<tr><td>Stream Start</td>	<td>%s</td></tr>\n", s->stream_start);
    226 	printf("<tr><td>Bitrate</td>		<td>%s</td></tr>\n", s->bitrate);
    227 	printf("<tr><td>Quality</td>		<td>%s</td></tr>\n", s->quality);
    228 	printf("<tr><td>Listeners (current)</td><td>%s</td></tr>\n", s->listeners);
    229 	printf("<tr><td>Listeners (peak)</td>	<td>%s</td></tr>\n", s->listener_peak);
    230 	printf("<tr><td>Genre</td>		<td>%s</td></tr>\n", s->genre);
    231 	printf("<tr><td>Stream URL</td>		<td><a href='%s'>%s</a></td></tr>\n", s->listenurl, s->listenurl);
    232 	puts("</table>");
    233 
    234 	puts("</div>");
    235 
    236 	puts("<hr>");
    237 }
    238 
    239 void
    240 source_free(struct source *s)
    241 {
    242 	free(s->audio_bitrate);
    243 	free(s->audio_channels);
    244 	free(s->audio_info);
    245 	free(s->audio_samplerate);
    246 	free(s->bitrate);
    247 	free(s->channels);
    248 	free(s->genre);
    249 	free(s->ice_bitrate);
    250 	free(s->listener_peak);
    251 	free(s->listeners);
    252 	free(s->listenurl);
    253 	free(s->quality);
    254 	free(s->samplerate);
    255 	free(s->server_description);
    256 	free(s->server_name);
    257 	free(s->server_type);
    258 	free(s->server_url);
    259 	free(s->stream_start);
    260 	free(s->stream_start_iso8601);
    261 	free(s->subtype);
    262 	free(s->title);
    263 	free(s->dummy);
    264 	free(s->url);
    265 }
    266 
    267 void
    268 print_navigation_bar(int index)
    269 {
    270 	char *names[]   = { "Stream",	"Music",	"Contact",	"About",	"Login"		};
    271 	char *scripts[] = { "/stream/",	"/music/",	"/contact/",	"/about/",	"/login/"	};
    272 
    273 	puts("<ul>");
    274 	puts("<li><a href='/'>"NAME"</a></li>");
    275 
    276 	for (int i = 0; i < 5; i++) {
    277 		printf("<li><a ");
    278 		if (i == index)
    279 			printf("class='active' ");
    280 		printf("href='%s'>%s</a></li>\n", scripts[i], names[i]);
    281 	}
    282 
    283 	puts("</ul>");
    284 }
    285 
    286 void
    287 json_process(json_stream *json)
    288 {
    289 	enum scope scope = SCOPE_NONE;
    290 	char *str;
    291 	int multisrc = FALSE;
    292 	while(1) {
    293 		enum json_type type = json_next(json);
    294 		switch (type) {
    295 		case JSON_DONE:
    296 			if (scope != SCOPE_NONE)
    297 				fatal("incomplete scope");
    298 			return;
    299 		case JSON_STRING:
    300 			str = json_get_string(json, NULL);
    301 			switch (scope) {
    302 			case SCOPE_ROOT:
    303 				if (strcmp(str, "icestats") == 0)
    304 					scope = SCOPE_ICESTATES;
    305 				else
    306 					fatal("%s: undefined string in icestats scope", str);
    307 				break;
    308 			case SCOPE_ICESTATES:
    309 				if (strcmp(str, "source") == 0)
    310 					scope = SCOPE_SOURCE;
    311 				else
    312 					icestats_set(&icestats, json, str);
    313 				break;
    314 			case SCOPE_SOURCE:
    315 				source_set(&srces[srclen-1], json, str);
    316 				break;
    317 			default:
    318 				fatal("%s: string in undefined scope", str);
    319 			}
    320 			break;
    321 		case JSON_ARRAY:
    322 			if (scope != SCOPE_SOURCE)
    323 				fatal("undefined array");
    324 			multisrc = TRUE;
    325 			break;
    326 		case JSON_ARRAY_END:
    327 			if (scope != SCOPE_SOURCE)
    328 				fatal("undefined array");
    329 			scope = SCOPE_ICESTATES;
    330 			break;
    331 		case JSON_OBJECT:
    332 			switch (scope) {
    333 			case SCOPE_NONE:
    334 				scope = SCOPE_ROOT;
    335 				break;
    336 			case SCOPE_ICESTATES:
    337 				break;
    338 			case SCOPE_SOURCE:
    339 				srclen++;
    340 				break;
    341 			default:
    342 				fatal("undefined object");
    343 			}
    344 			break;
    345 		case JSON_OBJECT_END:
    346 			switch (scope) {
    347 			case SCOPE_NONE:
    348 				fatal("extra scope");
    349 				break;
    350 			case SCOPE_ROOT:
    351 				scope = SCOPE_NONE;
    352 				break;
    353 			case SCOPE_ICESTATES:
    354 				scope = SCOPE_ROOT;
    355 				break;
    356 			case SCOPE_SOURCE:
    357 				if (!multisrc)
    358 					scope = SCOPE_ICESTATES;
    359 				break;
    360 			}
    361 			break;
    362 		case JSON_ERROR:
    363 			fatal("%zu: %s", json_get_lineno(json),
    364 					json_get_error(json));
    365 			break;
    366 		default:
    367 			fatal("cannot handle json type: %d", type);
    368 			break;
    369 		}
    370 	}
    371 }
    372 
    373 void
    374 redirect(char *url)
    375 {
    376 	printf("Content-Type: text/html\r\n");
    377 	printf("Status: 302 Found\r\n");
    378 	printf("Location: %s\r\n", url);
    379 	printf("\r\n");
    380 	printf("<html><body>Redirecting to <a href='%s'>%s</a></body></html>\n", url, url);
    381 	exit(0);
    382 }
    383 
    384 int
    385 main(int argc, char *argv[])
    386 {
    387 	json_stream json;
    388 	char *jsonbuf;
    389 	char *request_url;
    390 	char comment[4096];
    391 	FILE *f;
    392 	char c;
    393 	int id;
    394 
    395 	struct thread thread;
    396 
    397 	UNUSED(argc);
    398 	UNUSED(argv);
    399 
    400 	/* body */
    401 	request_url = getenv("REQUEST_URI");
    402 	if (request_url == NULL)
    403 		fatal("environment variable 'REQUEST_URI' is not set");
    404 
    405 	if (strchr(request_url, '?') == NULL) {
    406 		puts("Content-Type: text/html\r");
    407 		puts("Status: 200 OK\r");
    408 		puts("\r");
    409 		header_sent = TRUE;
    410 
    411 		/* print header */
    412 		f = file_open("header.html");
    413 		file_print(f, stdout);
    414 		file_close(f);
    415 	}
    416 
    417 	/* get json file */
    418 	http_t* request;
    419 	http_status_t status = HTTP_STATUS_PENDING;
    420 	request = http_get(ICECAST_URL"/status-json.xsl", NULL);
    421 	if (request == NULL)
    422 		fatal("invalid request");
    423 
    424 	while (status == HTTP_STATUS_PENDING)
    425 		status = http_process(request);
    426 	if(status == HTTP_STATUS_FAILED)
    427 		fatal("HTTP request failed (%d): %s.\n", request->status_code, request->reason_phrase);
    428 	jsonbuf = (char*)request->response_data;
    429 
    430 	/* parse json buffer */
    431 	json_open_string(&json, jsonbuf);
    432 	json_set_streaming(&json, false);
    433 	json_process(&json);
    434 	json_close(&json);
    435 
    436 	http_release(request);
    437 
    438 	if (request_url[strlen(request_url)-1] == '/')
    439 		request_url[strlen(request_url)-1] = '\0';
    440 
    441 	if (strcmp(request_url, "/stream") == 0
    442 	|| *request_url == '\0') {
    443 		print_navigation_bar(0);
    444 		if (srclen == 0)
    445 		{
    446 			printf("<p style=\"text-align: center\"><b>\n"
    447 				"It seems like there are no streams going on currently.\n"
    448 				"Try refreshing the page to see if a new stream is available.\n"
    449 				"</b></p>\n"
    450 				"<hr>\n");
    451 		} else {
    452 			for (int i = 0; i < srclen; i++)
    453 				source_print(&srces[i]);
    454 		}
    455 
    456 		puts("<h2> Comments </h2>");
    457 		/* comment box */
    458 		f = file_open("thread.html");
    459 		file_print(f, stdout);
    460 		file_close(f);
    461 		/* comment threads */
    462 		thread_open(&thread, THREAD_FILE);
    463 		while (thread_next(&thread) != EOF)
    464 			thread_write_html(&thread, stdout, thread.depth);
    465 		thread_close(&thread);
    466 	} else if (sscanf(request_url, "/stream/%9d%c", &id, &c) == 1) {
    467 		int parent_depth = -1;
    468 		thread_open(&thread, THREAD_FILE);
    469 		while (thread_next(&thread) != EOF) {
    470 			if (thread.id == id) {
    471 				parent_depth = thread.depth;
    472 				break;
    473 			}
    474 		}
    475 		if (parent_depth == -1)
    476 			not_found();
    477 
    478 		print_navigation_bar(0);
    479 		thread_write_html(&thread, stdout, 0);
    480 		f = file_open("thread.html");
    481 		file_print(f, stdout);
    482 		file_close(f);
    483 		while (thread_next(&thread) != EOF && thread.depth > parent_depth)
    484 			thread_write_html(&thread, stdout, thread.depth - parent_depth);
    485 		thread_close(&thread);
    486 	} else if (sscanf(request_url, "/stream/?comment=%4095[^&]%c", comment, &c) == 1) {
    487 		char *msg = request_url + strlen("/stream/?comment=");
    488 		parse_html_str(msg);
    489 		thread_open(&thread, THREAD_FILE);
    490 		thread_add(&thread, 0, msg);
    491 		thread_close(&thread);
    492 		*strchr(request_url, '?') = '\0';
    493 		redirect(request_url);
    494 	} else if (sscanf(request_url, "/stream/%9d?comment=%4095[^&]%c", &id, comment, &c) == 2) {
    495 		char *msg = strchr(request_url, '=');
    496 		msg++;
    497 		parse_html_str(msg);
    498 		thread_open(&thread, THREAD_FILE);
    499 		thread_add(&thread, id, msg);
    500 		thread_close(&thread);
    501 		*strchr(request_url, '?') = '\0';
    502 		redirect(request_url);
    503 	} else if (strcmp(request_url, "/about") == 0) {
    504 		print_navigation_bar(3);
    505 		icestats_print(&icestats);
    506 	} else {
    507 		not_found();
    508 	}
    509 
    510 out:
    511 	/* footer */
    512 	f = file_open("footer.html");
    513 	file_print(f, stdout);
    514 	file_close(f);
    515 
    516 	/* cleanup */
    517 	for (int i = 0; i < srclen; i++)
    518 		source_free(&srces[i]);
    519 	icestats_free(&icestats);
    520 
    521 	return 0;
    522 }