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 }