commit 161a2a5a6d2eb679e010a84cf29793e1cb75ef9f
Author: libredev <libredev@ircforever.org>
Date: Tue, 3 Jan 2023 01:13:05 +0530
initial commit
Diffstat:
A | .gitignore | | | 1 | + |
A | .gitmodules | | | 3 | +++ |
A | COPYING | | | 121 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | Makefile | | | 34 | ++++++++++++++++++++++++++++++++++ |
A | README | | | 1 | + |
A | footer.html | | | 10 | ++++++++++ |
A | header.html | | | 10 | ++++++++++ |
A | http.h | | | 719 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | main.c | | | 386 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | music.svg | | | 2 | ++ |
A | pdjson | | | 1 | + |
A | style.css | | | 97 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
12 files changed, 1385 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1 @@
+index.cgi
diff --git a/.gitmodules b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "pdjson"]
+ path = pdjson
+ url = https://github.com/skeeto/pdjson
diff --git a/COPYING b/COPYING
@@ -0,0 +1,121 @@
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+ HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display,
+ communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+ likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+ v. rights protecting the extraction, dissemination, use and reuse of data
+ in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation
+ thereof, including any amended or successor version of such
+ directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+ world based on applicable law or treaty, and any national
+ implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+ warranties of any kind concerning the Work, express, implied,
+ statutory or otherwise, including without limitation warranties of
+ title, merchantability, fitness for a particular purpose, non
+ infringement, or the absence of latent or other defects, accuracy, or
+ the present or absence of errors, whether or not discoverable, all to
+ the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without
+ limitation any person's Copyright and Related Rights in the Work.
+ Further, Affirmer disclaims responsibility for obtaining any necessary
+ consents, permissions or other rights required for any use of the
+ Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to
+ this CC0 or use of the Work.
diff --git a/Makefile b/Makefile
@@ -0,0 +1,34 @@
+# The author(s) have dedicated this work to the public domain by waiving all
+# of his or her rights to this work worldwide under copyright and patent law,
+# including all related and neighboring rights, to the extent allowed by law.
+# This work is provided 'as-is', without any express or implied warranty.
+# See COPYING file for details.
+
+CC = cc
+CFLAGS =\
+ -g -std=c99 -Wall -Wextra -Wpedantic -Wfatal-errors\
+ -Wstrict-prototypes -Wold-style-definition\
+ -D_DEFAULT_SOURCE
+
+INSTALL_DIR=/var/www/htdocs/radio.ircforever.org
+
+install: index.cgi header.html footer.html style.css music.svg
+ doas rm -R -f $(INSTALL_DIR)
+ doas mkdir -p $(INSTALL_DIR)
+ doas cp index.cgi $(INSTALL_DIR)
+ doas cp index.cgi $(INSTALL_DIR)/about.cgi
+ doas cp header.html $(INSTALL_DIR)
+ doas cp footer.html $(INSTALL_DIR)
+ doas cp style.css $(INSTALL_DIR)
+ doas cp music.svg $(INSTALL_DIR)
+
+index.cgi: main.c pdjson/pdjson.c
+ $(CC) $(CFLAGS) -o $@ main.c pdjson/pdjson.c
+
+run: index.cgi
+ SCRIPT_NAME="/index.cgi" valgrind --leak-check=full --show-leak-kinds=all ./index.cgi
+
+clean:
+ rm -f index.cgi
+
+.PHONY: install run clean
diff --git a/README b/README
@@ -0,0 +1 @@
+Everything in this repository is released under CC0 1.0 Universal.
diff --git a/footer.html b/footer.html
@@ -0,0 +1,10 @@
+
+<p class="footer">
+ Except where otherwise noted, content on this site and the source code are
+ released under the <br>
+ <a href="https://creativecommons.org/publicdomain/zero/1.0/">
+ CC0 1.0 Public Domain Dedication "No Rights Reserved"
+ </a>
+</p>
+
+</html>
diff --git a/header.html b/header.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8"/>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title> IRCNow Radio | Free Culture Media Streaming </title>
+ <link rel="stylesheet" type="text/css" href="style.css"/>
+</head>
+
diff --git a/http.h b/http.h
@@ -0,0 +1,719 @@
+/*
+------------------------------------------------------------------------------
+ Licensing information can be found at the end of the file.
+------------------------------------------------------------------------------
+
+http.hpp - v1.0 - Basic HTTP protocol implementation over sockets (no https).
+
+Do this:
+ #define HTTP_IMPLEMENTATION
+before you include this file in *one* C/C++ file to create the implementation.
+*/
+
+#ifndef http_hpp
+#define http_hpp
+
+#define _CRT_NONSTDC_NO_DEPRECATE
+#define _CRT_SECURE_NO_WARNINGS
+#include <stddef.h> // for size_t
+#include <stdint.h> // for uintptr_t
+
+typedef enum http_status_t
+ {
+ HTTP_STATUS_PENDING,
+ HTTP_STATUS_COMPLETED,
+ HTTP_STATUS_FAILED,
+ } http_status_t;
+
+typedef struct http_t
+ {
+ http_status_t status;
+ int status_code;
+ char const* reason_phrase;
+ char const* content_type;
+ size_t response_size;
+ void* response_data;
+ } http_t;
+
+http_t* http_get( char const* url, void* memctx );
+http_t* http_post( char const* url, void const* data, size_t size, void* memctx );
+
+http_status_t http_process( http_t* http );
+
+void http_release( http_t* http );
+
+#endif /* http_hpp */
+
+/**
+
+http.hpp
+========
+
+Basic HTTP protocol implementation over sockets (no https).
+
+
+Example
+-------
+
+ #define HTTP_IMPLEMENTATION
+ #include "http.h"
+
+ int main( int argc, char** argv ) {
+ http_t* request = http_get( "http://www.mattiasgustavsson.com/http_test.txt", NULL );
+ if( !request ) {
+ printf( "Invalid request.\n" );
+ return 1;
+ }
+
+ http_status_t status = HTTP_STATUS_PENDING;
+ int prev_size = -1;
+ while( status == HTTP_STATUS_PENDING ) {
+ status = http_process( request );
+ if( prev_size != (int) request->response_size ) {
+ printf( "%d byte(s) received.\n", (int) request->response_size );
+ prev_size = (int) request->response_size;
+ }
+ }
+
+ if( status == HTTP_STATUS_FAILED ) {
+ printf( "HTTP request failed (%d): %s.\n", request->status_code, request->reason_phrase );
+ http_release( request );
+ return 1;
+ }
+
+ printf( "\nContent type: %s\n\n%s\n", request->content_type, (char const*)request->response_data );
+ http_release( request );
+ return 0;
+ }
+
+
+API Documentation
+-----------------
+
+http.h is a small library for making http requests from a web server. It only supports GET and POST http commands, and
+is designed for when you just need a very basic way of communicating over http. http.h does not support https
+connections, just plain http.
+
+http.h is a single-header library, and does not need any .lib files or other binaries, or any build scripts. To use
+it, you just include http.h to get the API declarations. To get the definitions, you must include http.h from
+*one* single C or C++ file, and #define the symbol `HTTP_IMPLEMENTATION` before you do.
+
+
+#### Custom memory allocators
+
+For working memory and to store the retrieved data, http.h needs to do dynamic allocation by calling `malloc`. Programs
+might want to keep track of allocations done, or use custom defined pools to allocate memory from. http.h allows
+for specifying custom memory allocation functions for `malloc` and `free`. This is done with the following code:
+
+ #define HTTP_IMPLEMENTATION
+ #define HTTP_MALLOC( ctx, size ) ( my_custom_malloc( ctx, size ) )
+ #define HTTP_FREE( ctx, ptr ) ( my_custom_free( ctx, ptr ) )
+ #include "http.h"
+
+where `my_custom_malloc` and `my_custom_free` are your own memory allocation/deallocation functions. The `ctx` parameter
+is an optional parameter of type `void*`. When `http_get` or `http_post` is called, , you can pass in a `memctx`
+parameter, which can be a pointer to anything you like, and which will be passed through as the `ctx` parameter to every
+`HTTP_MALLOC`/`HTTP_FREE` call. For example, if you are doing memory tracking, you can pass a pointer to your
+tracking data as `memctx`, and in your custom allocation/deallocation function, you can cast the `ctx` param back to the
+right type, and access the tracking data.
+
+If no custom allocator is defined, http.h will default to `malloc` and `free` from the C runtime library.
+
+
+http_get
+--------
+
+ http_t* http_get( char const* url, void* memctx )
+
+Initiates a http GET request with the specified url. `url` is a zero terminated string containing the request location,
+just like you would type it in a browser, for example `http://www.mattiasgustavsson.com:80/http_test.txt`. `memctx` is a
+pointer to user defined data which will be passed through to the custom HTTP_MALLOC/HTTP_FREE calls. It can be NULL if
+no user defined data is needed. Returns a `http_t` instance, which needs to be passed to `http_process` to process the
+request. When the request is finished (or have failed), the returned `http_t` instance needs to be released by calling
+`http_release`. If the request was invalid, `http_get` returns NULL.
+
+
+http_post
+---------
+
+ http_t* http_post( char const* url, void const* data, size_t size, void* memctx )
+
+Initiates a http POST request with the specified url. `url` is a zero terminated string containing the request location,
+just like you would type it in a browser, for example `http://www.mattiasgustavsson.com:80/http_test.txt`. `data` is a
+pointer to the data to be sent along as part of the request, and `size` is the number of bytes to send. `memctx` is a
+pointer to user defined data which will be passed through to the custom HTTP_MALLOC/HTTP_FREE calls. It can be NULL if
+no user defined data is needed. Returns a `http_t` instance, which needs to be passed to `http_process` to process the
+request. When the request is finished (or have failed), the returned `http_t` instance needs to be released by calling
+`http_release`. If the request was invalid, `http_post` returns NULL.
+
+
+http_process
+------------
+
+ http_status_t http_process( http_t* http )
+
+http.h uses non-blocking sockets, so after a request have been made by calling either `http_get` or `http_post`, you
+have to keep calling `http_process` for as long as it returns `HTTP_STATUS_PENDING`. You can call it from a loop which
+does other work too, for example from inside a game loop or from a loop which calls `http_process` on multiple requests.
+If the request fails, `http_process` returns `HTTP_STATUS_FAILED`, and the fields `status_code` and `reason_phrase` may
+contain more details (for example, status code can be 404 if the requested resource was not found on the server). If the
+request completes successfully, it returns `HTTP_STATUS_COMPLETED`. In this case, the `http_t` instance will contain
+details about the result. `status_code` and `reason_phrase` contains the details about the result, as specified in the
+HTTP protocol. `content_type` contains the MIME type for the returns resource, for example `text/html` for a normal web
+page. `response_data` is the pointer to the received data, and `resonse_size` is the number of bytes it contains. In the
+case when the response data is in text format, http.h ensures there is a zero terminator placed immediately after the
+response data block, so it is safe to interpret the resonse data as a `char*`. Note that the data size in this case will
+be the length of the data without the additional zero terminator.
+
+
+http_release
+------------
+
+ void http_release( http_t* http )
+
+Releases the resources acquired by `http_get` or `http_post`. Should be call when you are finished with the request.
+
+*/
+
+/*
+----------------------
+ IMPLEMENTATION
+----------------------
+*/
+
+#ifdef HTTP_IMPLEMENTATION
+
+#ifdef _WIN32
+ #define _CRT_NONSTDC_NO_DEPRECATE
+ #define _CRT_SECURE_NO_WARNINGS
+ #pragma warning( push )
+ #pragma warning( disable: 4127 ) // conditional expression is constant
+ #pragma warning( disable: 4255 ) // 'function' : no function prototype given: converting '()' to '(void)'
+ #pragma warning( disable: 4365 ) // 'action' : conversion from 'type_1' to 'type_2', signed/unsigned mismatch
+ #pragma warning( disable: 4574 ) // 'Identifier' is defined to be '0': did you mean to use '#if identifier'?
+ #pragma warning( disable: 4668 ) // 'symbol' is not defined as a preprocessor macro, replacing with '0' for 'directive'
+ #pragma warning( disable: 4706 ) // assignment within conditional expression
+ #include <winsock2.h>
+ #include <ws2tcpip.h>
+ #pragma warning( pop )
+ #pragma comment (lib, "Ws2_32.lib")
+ #include <string.h>
+ #include <stdio.h>
+ #define HTTP_SOCKET SOCKET
+ #define HTTP_INVALID_SOCKET INVALID_SOCKET
+#else
+ #include <stdlib.h>
+ #include <stdio.h>
+ #include <string.h>
+ #include <sys/types.h>
+ #include <sys/socket.h>
+ #include <unistd.h>
+ #include <errno.h>
+ #include <fcntl.h>
+ #include <netdb.h>
+ #define HTTP_SOCKET int
+ #define HTTP_INVALID_SOCKET -1
+#endif
+
+#ifndef HTTP_MALLOC
+ #define _CRT_NONSTDC_NO_DEPRECATE
+ #define _CRT_SECURE_NO_WARNINGS
+ #include <stdlib.h>
+ #define HTTP_MALLOC( ctx, size ) ( malloc( size ) )
+ #define HTTP_FREE( ctx, ptr ) ( free( ptr ) )
+#endif
+
+typedef struct http_internal_t
+ {
+ /* keep this at the top!*/
+ http_t http;
+ /* because http_internal_t* can be cast to http_t*. */
+
+ void* memctx;
+ HTTP_SOCKET socket;
+ int connect_pending;
+ int request_sent;
+ char address[ 256 ];
+ char request_header[ 256 ];
+ char* request_header_large;
+ void* request_data;
+ size_t request_data_size;
+ char reason_phrase[ 1024 ];
+ char content_type[ 256 ];
+ size_t data_size;
+ size_t data_capacity;
+ void* data;
+ } http_internal_t;
+
+
+static int http_internal_parse_url( char const* url, char* address, size_t address_capacity, char* port,
+ size_t port_capacity, char const** resource )
+ {
+ // make sure url starts with http://
+ if( strncmp( url, "http://", 7 ) != 0 ) return 0;
+ url += 7; // skip http:// part of url
+
+ size_t url_len = strlen( url );
+
+ // find end of address part of url
+ char const* address_end = strchr( url, ':' );
+ if( !address_end ) address_end = strchr( url, '/' );
+ if( !address_end ) address_end = url + url_len;
+
+ // extract address
+ size_t address_len = (size_t)( address_end - url );
+ if( address_len >= address_capacity ) return 0;
+ memcpy( address, url, address_len );
+ address[ address_len ] = 0;
+
+ // check if there's a port defined
+ char const* port_end = address_end;
+ if( *address_end == ':' )
+ {
+ ++address_end;
+ port_end = strchr( address_end, '/' );
+ if( !port_end ) port_end = address_end + strlen( address_end );
+ size_t port_len = (size_t)( port_end - address_end );
+ if( port_len >= port_capacity ) return 0;
+ memcpy( port, address_end, port_len );
+ port[ port_len ] = 0;
+ }
+ else
+ {
+ // use default port number 80
+ if( port_capacity <= 2 ) return 0;
+ strcpy( port, "80" );
+ }
+
+
+ *resource = port_end;
+
+ return 1;
+ }
+
+
+HTTP_SOCKET http_internal_connect( char const* address, char const* port )
+ {
+ // set up hints for getaddrinfo
+ struct addrinfo hints;
+ memset( &hints, 0, sizeof( hints ) );
+ hints.ai_family = AF_INET; // the Internet Protocol version 4 (IPv4) address family.
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP; // Use Transmission Control Protocol (TCP).
+
+ // resolve the server address and port
+ struct addrinfo* addri = 0;
+ int error = getaddrinfo( address, port, &hints, &addri) ;
+ if( error != 0 ) return HTTP_INVALID_SOCKET;
+
+ // create the socket
+ HTTP_SOCKET sock = socket( addri->ai_family, addri->ai_socktype, addri->ai_protocol );
+ if( sock == -1)
+ {
+ freeaddrinfo( addri );
+ return HTTP_INVALID_SOCKET;
+ }
+
+ // set socket to nonblocking mode
+ u_long nonblocking = 1;
+ #ifdef _WIN32
+ int res = ioctlsocket( sock, FIONBIO, &nonblocking );
+ #else
+ int flags = fcntl( sock, F_GETFL, 0 );
+ int res = fcntl( sock, F_SETFL, flags | O_NONBLOCK );
+ #endif
+ if( res == -1 )
+ {
+ freeaddrinfo( addri );
+ #ifdef _WIN32
+ closesocket( sock );
+ #else
+ close( sock );
+ #endif
+ return HTTP_INVALID_SOCKET;
+ }
+
+ // connect to server
+ if( connect( sock, addri->ai_addr, (int)addri->ai_addrlen ) == -1 )
+ {
+ #ifdef _WIN32
+ if( WSAGetLastError() != WSAEWOULDBLOCK && WSAGetLastError() != WSAEINPROGRESS )
+ {
+ freeaddrinfo( addri );
+ closesocket( sock );
+ return HTTP_INVALID_SOCKET;
+ }
+ #else
+ if( errno != EWOULDBLOCK && errno != EINPROGRESS && errno != EAGAIN )
+ {
+ freeaddrinfo( addri );
+ close( sock );
+ return HTTP_INVALID_SOCKET;
+ }
+ #endif
+ }
+
+ freeaddrinfo( addri );
+ return sock;
+ }
+
+
+static http_internal_t* http_internal_create( size_t request_data_size, void* memctx )
+ {
+ http_internal_t* internal = (http_internal_t*) HTTP_MALLOC( memctx, sizeof( http_internal_t ) + request_data_size );
+
+ internal->http.status = HTTP_STATUS_PENDING;
+ internal->http.status_code = 0;
+ internal->http.response_size = 0;
+ internal->http.response_data = NULL;
+
+ internal->memctx = memctx;
+ internal->connect_pending = 1;
+ internal->request_sent = 0;
+
+ strcpy( internal->reason_phrase, "" );
+ internal->http.reason_phrase = internal->reason_phrase;
+
+ strcpy( internal->content_type, "" );
+ internal->http.content_type = internal->content_type;
+
+ internal->data_size = 0;
+ internal->data_capacity = 64 * 1024;
+ internal->data = HTTP_MALLOC( memctx, internal->data_capacity );
+
+ internal->request_data = NULL;
+ internal->request_data_size = 0;
+
+ return internal;
+ }
+
+
+http_t* http_get( char const* url, void* memctx )
+ {
+ #ifdef _WIN32
+ WSADATA wsa_data;
+ if( WSAStartup( MAKEWORD( 1, 0 ), &wsa_data ) != 0 ) return NULL;
+ #endif
+
+ char address[ 256 ];
+ char port[ 16 ];
+ char const* resource;
+
+ if( http_internal_parse_url( url, address, sizeof( address ), port, sizeof( port ), &resource ) == 0 )
+ return NULL;
+
+ HTTP_SOCKET socket = http_internal_connect( address, port );
+ if( socket == HTTP_INVALID_SOCKET ) return NULL;
+
+ http_internal_t* internal = http_internal_create( 0, memctx );
+ internal->socket = socket;
+
+ char* request_header;
+ size_t request_header_len = 64 + strlen( resource ) + strlen( address ) + strlen( port );
+ if( request_header_len < sizeof( internal->request_header ) )
+ {
+ internal->request_header_large = NULL;
+ request_header = internal->request_header;
+ }
+ else
+ {
+ internal->request_header_large = (char*) HTTP_MALLOC( memctx, request_header_len + 1 );
+ request_header = internal->request_header_large;
+ }
+ int default_http_port = (strcmp(port, "80") == 0);
+ sprintf( request_header, "GET %s HTTP/1.0\r\nHost: %s%s%s\r\n\r\n", resource, address, default_http_port ? "" : ":", default_http_port ? "" : port );
+
+ return &internal->http;
+ }
+
+
+http_t* http_post( char const* url, void const* data, size_t size, void* memctx )
+ {
+ #ifdef _WIN32
+ WSADATA wsa_data;
+ if( WSAStartup( MAKEWORD( 1, 0 ), &wsa_data ) != 0 ) return 0;
+ #endif
+
+ char address[ 256 ];
+ char port[ 16 ];
+ char const* resource;
+
+ if( http_internal_parse_url( url, address, sizeof( address ), port, sizeof( port ), &resource ) == 0 )
+ return NULL;
+
+ HTTP_SOCKET socket = http_internal_connect( address, port );
+ if( socket == HTTP_INVALID_SOCKET ) return NULL;
+
+ http_internal_t* internal = http_internal_create( size, memctx );
+ internal->socket = socket;
+
+ char* request_header;
+ size_t request_header_len = 64 + strlen( resource ) + strlen( address ) + strlen( port );
+ if( request_header_len < sizeof( internal->request_header ) )
+ {
+ internal->request_header_large = NULL;
+ request_header = internal->request_header;
+ }
+ else
+ {
+ internal->request_header_large = (char*) HTTP_MALLOC( memctx, request_header_len + 1 );
+ request_header = internal->request_header_large;
+ }
+ int default_http_port = (strcmp(port, "80") == 0);
+ sprintf( request_header, "POST %s HTTP/1.0\r\nHost: %s%s%s\r\nContent-Length: %d\r\n\r\n", resource, address, default_http_port ? "" : ":", default_http_port ? "" : port,
+ (int) size );
+
+ internal->request_data_size = size;
+ internal->request_data = ( internal + 1 );
+ memcpy( internal->request_data, data, size );
+
+ return &internal->http;
+ }
+
+
+http_status_t http_process( http_t* http )
+ {
+ http_internal_t* internal = (http_internal_t*) http;
+
+ if( http->status == HTTP_STATUS_FAILED ) return http->status;
+
+ if( internal->connect_pending )
+ {
+ fd_set sockets_to_check;
+ FD_ZERO( &sockets_to_check );
+ #pragma warning( push )
+ #pragma warning( disable: 4548 ) // expression before comma has no effect; expected expression with side-effect
+ FD_SET( internal->socket, &sockets_to_check );
+ #pragma warning( pop )
+ struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 0;
+ // check if socket is ready for send
+ if( select( (int)( internal->socket + 1 ), NULL, &sockets_to_check, NULL, &timeout ) == 1 )
+ {
+ int opt = -1;
+ socklen_t len = sizeof( opt );
+ if( getsockopt( internal->socket, SOL_SOCKET, SO_ERROR, (char*)( &opt ), &len) >= 0 && opt == 0 )
+ internal->connect_pending = 0; // if it is, we're connected
+ }
+ }
+
+ if( internal->connect_pending ) return http->status;
+
+ if( !internal->request_sent )
+ {
+ char const* request_header = internal->request_header_large ?
+ internal->request_header_large : internal->request_header;
+ if( send( internal->socket, request_header, (int) strlen( request_header ), 0 ) == -1 )
+ {
+ http->status = HTTP_STATUS_FAILED;
+ return http->status;
+ }
+ if( internal->request_data_size )
+ {
+ int res = send( internal->socket, (char const*)internal->request_data, (int) internal->request_data_size, 0 );
+ if( res == -1 )
+ {
+ http->status = HTTP_STATUS_FAILED;
+ return http->status;
+ }
+ }
+ internal->request_sent = 1;
+ return http->status;
+ }
+
+ // check if socket is ready for recv
+ fd_set sockets_to_check;
+ FD_ZERO( &sockets_to_check );
+ #pragma warning( push )
+ #pragma warning( disable: 4548 ) // expression before comma has no effect; expected expression with side-effect
+ FD_SET( internal->socket, &sockets_to_check );
+ #pragma warning( pop )
+ struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 0;
+ while( select( (int)( internal->socket + 1 ), &sockets_to_check, NULL, NULL, &timeout ) == 1 )
+ {
+ char buffer[ 4096 ];
+ int size = recv( internal->socket, buffer, sizeof( buffer ), 0 );
+ if( size == -1 )
+ {
+ http->status = HTTP_STATUS_FAILED;
+ return http->status;
+ }
+ else if( size > 0 )
+ {
+ size_t min_size = internal->data_size + size + 1;
+ if( internal->data_capacity < min_size )
+ {
+ internal->data_capacity *= 2;
+ if( internal->data_capacity < min_size ) internal->data_capacity = min_size;
+ void* new_data = HTTP_MALLOC( memctx, internal->data_capacity );
+ memcpy( new_data, internal->data, internal->data_size );
+ HTTP_FREE( memctx, internal->data );
+ internal->data = new_data;
+ }
+ memcpy( (void*)( ( (uintptr_t) internal->data ) + internal->data_size ), buffer, (size_t) size );
+ internal->data_size += size;
+ }
+ else if( size == 0 )
+ {
+ char const* status_line = (char const*) internal->data;
+
+ int header_size = 0;
+ char const* header_end = strstr( status_line, "\r\n\r\n" );
+ if( header_end )
+ {
+ header_end += 4;
+ header_size = (int)( header_end - status_line );
+ }
+ else
+ {
+ http->status = HTTP_STATUS_FAILED;
+ return http->status;
+ }
+
+ // skip http version
+ status_line = strchr( status_line, ' ' );
+ if( !status_line )
+ {
+ http->status = HTTP_STATUS_FAILED;
+ return http->status;
+ }
+ ++status_line;
+
+ // extract status code
+ char status_code[ 16 ];
+ char const* status_code_end = strchr( status_line, ' ' );
+ if( !status_code_end )
+ {
+ http->status = HTTP_STATUS_FAILED;
+ return http->status;
+ }
+ memcpy( status_code, status_line, (size_t)( status_code_end - status_line ) );
+ status_code[ status_code_end - status_line ] = 0;
+ status_line = status_code_end + 1;
+ http->status_code = atoi( status_code );
+
+ // extract reason phrase
+ char const* reason_phrase_end = strstr( status_line, "\r\n" );
+ if( !reason_phrase_end )
+ {
+ http->status = HTTP_STATUS_FAILED;
+ return http->status;
+ }
+ size_t reason_phrase_len = (size_t)( reason_phrase_end - status_line );
+ if( reason_phrase_len >= sizeof( internal->reason_phrase ) )
+ reason_phrase_len = sizeof( internal->reason_phrase ) - 1;
+ memcpy( internal->reason_phrase, status_line, reason_phrase_len );
+ internal->reason_phrase[ reason_phrase_len ] = 0;
+ status_line = reason_phrase_end + 1;
+
+ // extract content type
+ char const* content_type_start = strstr( status_line, "Content-Type: " );
+ if( content_type_start )
+ {
+ content_type_start += strlen( "Content-Type: " );
+ char const* content_type_end = strstr( content_type_start, "\r\n" );
+ if( content_type_end )
+ {
+ size_t content_type_len = (size_t)( content_type_end - content_type_start );
+ if( content_type_len >= sizeof( internal->content_type ) )
+ content_type_len = sizeof( internal->content_type ) - 1;
+ memcpy( internal->content_type, content_type_start, content_type_len );
+ internal->content_type[ content_type_len ] = 0;
+ }
+ }
+
+ http->status = http->status_code < 300 ? HTTP_STATUS_COMPLETED : HTTP_STATUS_FAILED;
+ http->response_data = (void*)( ( (uintptr_t) internal->data ) + header_size );
+ http->response_size = internal->data_size - header_size;
+
+ // add an extra zero after the received data, but don't modify the size, so ascii results can be used as
+ // a zero terminated string. the size returned will be the string without this extra zero terminator.
+ ( (char*)http->response_data )[ http->response_size ] = 0;
+ return http->status;
+ }
+ }
+
+ return http->status;
+ }
+
+
+void http_release( http_t* http )
+ {
+ http_internal_t* internal = (http_internal_t*) http;
+ #ifdef _WIN32
+ closesocket( internal->socket );
+ #else
+ close( internal->socket );
+ #endif
+
+ if( internal->request_header_large) HTTP_FREE( memctx, internal->request_header_large );
+ HTTP_FREE( memctx, internal->data );
+ HTTP_FREE( memctx, internal );
+ #ifdef _WIN32
+ WSACleanup();
+ #endif
+ }
+
+
+#endif /* HTTP_IMPLEMENTATION */
+
+/*
+revision history:
+ 1.0 first released version
+*/
+
+/*
+------------------------------------------------------------------------------
+
+This software is available under 2 licenses - you may choose the one you like.
+
+------------------------------------------------------------------------------
+
+ALTERNATIVE A - MIT License
+
+Copyright (c) 2016 Mattias Gustavsson
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+------------------------------------------------------------------------------
+
+ALTERNATIVE B - Public Domain (www.unlicense.org)
+
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
+software, either in source code form or as a compiled binary, for any purpose,
+commercial or non-commercial, and by any means.
+
+In jurisdictions that recognize copyright laws, the author or authors of this
+software dedicate any and all copyright interest in the software to the public
+domain. We make this dedication for the benefit of the public at large and to
+the detriment of our heirs and successors. We intend this dedication to be an
+overt act of relinquishment in perpetuity of all present and future rights to
+this software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+------------------------------------------------------------------------------
+*/
diff --git a/main.c b/main.c
@@ -0,0 +1,386 @@
+/*
+ * The author(s) have dedicated this work to the public domain by waiving all
+ * of his or her rights to this work worldwide under copyright and patent law,
+ * including all related and neighboring rights, to the extent allowed by law.
+ * This work is provided 'as-is', without any express or implied warranty.
+ * See COPYING file for details.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define HTTP_IMPLEMENTATION
+#include "http.h"
+#include "pdjson/pdjson.h"
+
+#define TRUE 1
+#define FALSE 0
+
+struct icestats {
+ char *admin;
+ char *host;
+ char *location;
+ char *server_id;
+ char *server_start;
+ char *server_start_iso8601;
+};
+
+struct source {
+ char *audio_bitrate;
+ char *audio_channels;
+ char *audio_info;
+ char *audio_samplerate;
+ char *bitrate;
+ char *channels;
+ char *genre;
+ char *ice_bitrate;
+ char *listener_peak;
+ char *listeners;
+ char *listenurl;
+ char *quality;
+ char *samplerate;
+ char *server_description;
+ char *server_name;
+ char *server_type;
+ char *server_url;
+ char *stream_start;
+ char *stream_start_iso8601;
+ char *subtype;
+ char *title;
+ char *dummy;
+};
+
+struct icestats icestats;
+struct source *sources = NULL;
+int sources_length = 0;
+
+char *
+read_file(const char* fname)
+{
+ char *content;
+ long fsize;
+ FILE *file;
+
+ file = fopen(fname, "rb");
+ if (file == NULL)
+ goto err;
+ if (fseek(file, 0, SEEK_END) != 0)
+ goto err;
+ if ((fsize = ftell(file)) == -1)
+ goto err;
+ if (fseek(file, 0, SEEK_SET) != 0)
+ goto err;
+ if ((content = malloc((fsize + 1) * sizeof(char))) == NULL)
+ goto err;
+ if (fread(content, sizeof(char), fsize, file) != (size_t)fsize)
+ goto err;
+ fclose(file);
+ (content)[fsize] = '\0';
+ return content;
+err:
+ printf("ERROR: %s: %s\n", fname, strerror(errno));
+ return NULL;
+}
+
+char *
+json_get_value(json_stream *json)
+{
+ enum json_type type = json_next(json);
+ if (type == JSON_NUMBER || type == JSON_STRING)
+ return strdup(json_get_string(json, NULL));
+ else
+ return NULL;
+}
+
+void
+icestats_set(struct icestats *s, json_stream *json, const char *key)
+{
+ char **var = NULL;
+
+ if (strcmp(key, "admin" ) == 0) var = &s->admin;
+ else if (strcmp(key, "host" ) == 0) var = &s->host;
+ else if (strcmp(key, "location" ) == 0) var = &s->location;
+ else if (strcmp(key, "server_id" ) == 0) var = &s->server_id;
+ else if (strcmp(key, "server_start" ) == 0) var = &s->server_start;
+ else if (strcmp(key, "server_start_iso8601" ) == 0) var = &s->server_start_iso8601;
+
+ if (var == NULL) {
+ fprintf(stdout, "Failed to handle icestats key \"%s\"\n", key);
+ exit(1);
+ } else {
+ *var = json_get_value(json);
+ }
+}
+
+void
+icestats_print(struct icestats *s)
+{
+ puts("<table class=\"about\">");
+ printf("<tr><td> Admin </td><td> %s </td></tr>\n", s->admin);
+ printf("<tr><td> Host </td><td> %s </td></tr>\n", s->host);
+ printf("<tr><td> Location </td><td> %s </td></tr>\n", s->location);
+ printf("<tr><td> Server ID </td><td> %s </td></tr>\n", s->server_id);
+ printf("<tr><td> Server Start </td><td> %s </td></tr>\n", s->server_start);
+ /* printf("<tr><td> server_start_iso8601 </td><td> %s </td></tr>\n", s->server_start_iso8601); */
+ puts("</table>");
+}
+
+void
+icestats_free(struct icestats *s)
+{
+ free(s->admin);
+ free(s->host);
+ free(s->location);
+ free(s->server_id);
+ free(s->server_start);
+ free(s->server_start_iso8601);
+}
+
+void
+source_set(struct source *s, json_stream *json, const char *key)
+{
+ char **var = NULL;
+
+ if (strcmp(key, "audio_bitrate" ) == 0) var = &s->audio_bitrate;
+ else if (strcmp(key, "audio_channels" ) == 0) var = &s->audio_channels;
+ else if (strcmp(key, "audio_info" ) == 0) var = &s->audio_info;
+ else if (strcmp(key, "audio_samplerate" ) == 0) var = &s->audio_samplerate;
+ else if (strcmp(key, "bitrate" ) == 0) var = &s->bitrate;
+ else if (strcmp(key, "channels" ) == 0) var = &s->channels;
+ else if (strcmp(key, "genre" ) == 0) var = &s->genre;
+ else if (strcmp(key, "ice-bitrate" ) == 0) var = &s->ice_bitrate;
+ else if (strcmp(key, "listener_peak" ) == 0) var = &s->listener_peak;
+ else if (strcmp(key, "listeners" ) == 0) var = &s->listeners;
+ else if (strcmp(key, "listenurl" ) == 0) var = &s->listenurl;
+ else if (strcmp(key, "quality" ) == 0) var = &s->quality;
+ else if (strcmp(key, "samplerate" ) == 0) var = &s->samplerate;
+ else if (strcmp(key, "server_description" ) == 0) var = &s->server_description;
+ else if (strcmp(key, "server_name" ) == 0) var = &s->server_name;
+ else if (strcmp(key, "server_type" ) == 0) var = &s->server_type;
+ else if (strcmp(key, "server_url" ) == 0) var = &s->server_url;
+ else if (strcmp(key, "stream_start" ) == 0) var = &s->stream_start;
+ else if (strcmp(key, "stream_start_iso8601" ) == 0) var = &s->stream_start_iso8601;
+ else if (strcmp(key, "subtype" ) == 0) var = &s->subtype;
+ else if (strcmp(key, "title" ) == 0) var = &s->title;
+ else if (strcmp(key, "dummy" ) == 0) var = &s->dummy;
+
+ if (var == NULL) {
+ fprintf(stdout, "Failed to handle icestats key \"%s\"\n", key);
+ exit(1);
+ } else {
+ *var = json_get_value(json);
+ }
+}
+
+void
+source_print(struct source *s)
+{
+
+ puts("<div class=\"stream\">");
+
+ /* thumbnail and player */
+ puts("<div>");
+ puts("<img src=\"music.svg\" alt=\"music logo\" width=\"256\" height=\"256\"></a>");
+ puts("<audio controls>");
+ puts("<source src=\"https://theinterlude.live/autodj\" type=\"application/ogg\">");
+ puts("Your browser does not support the audio element.");
+ puts("</audio>");
+ puts("</div>");
+
+ /* info */
+ puts("<table>");
+ printf("<tr><th> Stream Name </th><th> %s </th></tr>\n", s->server_name);
+ printf("<tr><td> Stream Description </td><td> %s </td></tr>\n", s->server_description);
+ printf("<tr><td> Stream Type </td><td> %s </td></tr>\n", s->server_type);
+ /* printf("<tr><td> server_url </td><td> %s </td></tr>\n", s->server_url); */
+ printf("<tr><td> Stream Start </td><td> %s </td></tr>\n", s->stream_start);
+ /* printf("<tr><td> stream_start_iso8601 </td><td> %s </td></tr>\n", s->stream_start_iso8601); */
+ /* printf("<tr><td> audio_bitrate </td><td> %s </td></tr>\n", s->audio_bitrate);
+ printf("<tr><td> audio_channels </td><td> %s </td></tr>\n", s->audio_channels);
+ printf("<tr><td> audio_info </td><td> %s </td></tr>\n", s->audio_info);
+ printf("<tr><td> audio_samplerate </td><td> %s </td></tr>\n", s->audio_samplerate); */
+ printf("<tr><td> Bitrate </td><td> %s </td></tr>\n", s->bitrate);
+ printf("<tr><td> Quality </td><td> %s </td></tr>\n", s->quality);
+ printf("<tr><td> Listeners (current) </td><td> %s </td></tr>\n", s->listeners);
+ printf("<tr><td> Listeners (peak) </td><td> %s </td></tr>\n", s->listener_peak);
+ printf("<tr><td> Genre </td><td> %s </td></tr>\n", s->genre);
+ /* printf("<tr><td> channels </td><td> %s </td></tr>\n", s->channels);
+ printf("<tr><td> ice_bitrate </td><td> %s </td></tr>\n", s->ice_bitrate);
+ printf("<tr><td> listenurl </td><td> %s </td></tr>\n", s->listenurl);
+ printf("<tr><td> samplerate </td><td> %s </td></tr>\n", s->samplerate);
+ printf("<tr><td> subtype </td><td> %s </td></tr>\n", s->subtype);
+ printf("<tr><td> title </td><td> %s </td></tr>\n", s->title);
+ printf("<tr><td> dummy </td><td> %s </td></tr>\n", s->dummy); */
+ puts("</table>");
+
+ puts("</div>");
+}
+
+void
+source_free(struct source *s)
+{
+ free(s->audio_bitrate);
+ free(s->audio_channels);
+ free(s->audio_info);
+ free(s->audio_samplerate);
+ free(s->bitrate);
+ free(s->channels);
+ free(s->genre);
+ free(s->ice_bitrate);
+ free(s->listener_peak);
+ free(s->listeners);
+ free(s->listenurl);
+ free(s->quality);
+ free(s->samplerate);
+ free(s->server_description);
+ free(s->server_name);
+ free(s->server_type);
+ free(s->server_url);
+ free(s->stream_start);
+ free(s->stream_start_iso8601);
+ free(s->subtype);
+ free(s->title);
+ free(s->dummy);
+}
+
+void
+print_navigation_bar(int index)
+{
+ char *names[] = { "Stream", "Music", "Contact", "About", "Login" };
+ char *scripts[] = { "index.cgi", "music.cgi", "contact.cgi", "about.cgi", "login.cgi" };
+
+ puts("<ul>");
+ puts("<li><a href=\"index.cgi\">IRCNow Radio</a></li>");
+
+ for (int i = 0; i < 5; i++) {
+ printf("<li><a ");
+ if (i == index)
+ printf("class=\"active\" ");
+ printf("href=\"%s\">%s</a></li>\n", scripts[i], names[i]);
+ }
+
+ puts("</ul>");
+}
+
+void
+json_handle(json_stream *json)
+{
+ if (json_next(json) != JSON_OBJECT) {
+ printf("ERROR: Invalid json key type.\n");
+ exit(EXIT_FAILURE);
+ }
+ static int source_index = 0;
+ static int source_active = FALSE;
+
+ while (json_peek(json) != JSON_OBJECT_END && !json_get_error(json)) {
+ json_next(json);
+ const char *key = json_get_string(json, NULL);
+ if (source_active == TRUE) {
+ source_set(&sources[source_index], json, key);
+ } else if (strcmp(key, "source") == 0) {
+ if (source_index == sources_length) {
+ sources_length++;
+ if ((sources = realloc(sources, sources_length * sizeof(struct source))) == NULL) {
+ printf("ERROR: relloc: %s\n", strerror(errno));
+ exit(1);
+ }
+ }
+ source_active = TRUE;
+ json_handle(json);
+ source_index++;
+ source_active = FALSE;
+ } else if (strcmp(key, "icestats") == 0) {
+ json_handle(json);
+ } else {
+ icestats_set(&icestats, json, key);
+ }
+ }
+ json_next(json);
+}
+
+int
+main(int argc, char *argv[])
+{
+ json_stream json;
+ char *buffer;
+ char *script_name;
+
+ http_t* request;
+ http_status_t status = HTTP_STATUS_PENDING;
+
+ puts("Status: 200 OK\r");
+ puts("Content-Type: text/html\r");
+ puts("\r");
+
+ request = http_get("http://theinterlude.live:8000/status-json.xsl", NULL);
+ if (!request) {
+ printf("Invalid request.\n");
+ return 1;
+ }
+
+ while (status == HTTP_STATUS_PENDING)
+ status = http_process(request);
+ if(status == HTTP_STATUS_FAILED)
+ {
+ printf("HTTP request failed (%d): %s.\n", request->status_code, request->reason_phrase);
+ http_release(request);
+ return 1;
+ }
+ buffer = (char*)request->response_data;
+
+ json_open_string(&json, buffer);
+ json_set_streaming(&json, false);
+ json_handle(&json);
+ if (json_get_error(&json)) {
+ printf("ERROR: %lu: %s\n", json_get_lineno(&json), json_get_error(&json));
+ exit(1);
+ }
+
+ /*
+ * header
+ */
+ if ((buffer = read_file("header.html")) == NULL)
+ exit(1);
+ printf("%s", buffer);
+ free(buffer);
+
+ /*
+ * body
+ */
+ script_name = getenv("SCRIPT_NAME");
+ if (script_name == NULL) {
+ printf("ERROR: environment variable \"SCRIPT_NAME\" is not set.\n");
+ return 1;
+ }
+
+ if (strcmp(script_name, "/index.cgi") == 0) {
+ print_navigation_bar(0);
+ for (int i = 0; i < sources_length; i++)
+ source_print(&sources[i]);
+ } else if (strcmp(script_name, "/about.cgi") == 0) {
+ print_navigation_bar(3);
+ icestats_print(&icestats);
+ }
+
+ /*
+ * footer
+ */
+ if ((buffer = read_file("footer.html")) == NULL)
+ exit(1);
+ printf("%s", buffer);
+ free(buffer);
+
+ /* cleanup */
+ json_close(&json);
+ http_release(request);
+ if (sources != NULL) {
+ for (int i = 0; i < sources_length; i++)
+ source_free(&sources[i]);
+ free(sources);
+ }
+ icestats_free(&icestats);
+
+ return 0;
+}
diff --git a/music.svg b/music.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve"><g><path d="M50,5C25.1,5,5,25.1,5,50c0,24.9,20.1,45,45,45s45-20.1,45-45C95,25.1,74.9,5,50,5z M69.7,59c0,5-4.1,9.1-9.1,9.1 S51.5,64,51.5,59s4.1-9.1,9.1-9.1c1.7,0,3.2,0.4,4.6,1.2l-1.1-13.8l-17.2,3.1l1.7,22.5l0,0c0,0.2,0,0.3,0,0.5c0,5-4.1,9.1-9.1,9.1 s-9.1-4.1-9.1-9.1c0-5,4.1-9.1,9.1-9.1c1.7,0,3.2,0.4,4.6,1.2l-1.8-22.7l4.1-0.9l20.9-4.3l1,9.2l1.6,21.8l0,0 C69.7,58.6,69.7,58.8,69.7,59z"/></g></svg>
+\ No newline at end of file
diff --git a/pdjson b/pdjson
@@ -0,0 +1 @@
+Subproject commit 67108d883061043e55d0fb13961ac1b6fc8a485c
diff --git a/style.css b/style.css
@@ -0,0 +1,97 @@
+body {
+ margin: 10px;
+}
+
+ul {
+ list-style-type: none;
+ margin: 10px 0px;
+ padding: 0;
+ overflow: hidden;
+ border: 1px solid darkgrey;
+}
+
+li {
+ float: left;
+}
+
+li:last-child {
+ float: right;
+}
+
+li a {
+ display: block;
+ color: dimgray;
+ text-align: center;
+ padding: 14px 16px;
+ text-decoration: none;
+}
+
+li:first-child a {
+ font-weight: bold;
+ color: dodgerblue;
+}
+
+li a:hover:not(.active) {
+ background-color: lightgrey;
+}
+
+li a.active {
+ color: white;
+ background-color: dodgerblue;
+}
+
+table.about {
+ margin-left: auto;
+ margin-right: auto;
+}
+
+th, td {
+ padding: 5px 10px;
+ text-align: left;
+}
+
+tr:nth-child(odd) {
+ background-color: gainsboro;
+}
+
+
+.stream {
+ display: table;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.stream div {
+ float: left;
+ border: 1px solid darkgrey;
+}
+
+.stream div img {
+ margin-left: auto;
+ margin-right: auto;
+ display: block;
+}
+
+.stream div audio {
+ margin-left: auto;
+ margin-right: auto;
+ display: block;
+}
+
+@media screen and (max-width: 500px) {
+ li, li:last-child {
+ float: none;
+ }
+
+ .stream div {
+ float: none;
+ }
+}
+
+
+p.footer {
+ border: 1px solid darkgrey;
+ padding: 10px;
+ text-align: center;
+ color: dimgrey;
+}