From 0c099de57e5eaf9f9a55c8fcf6b713bf411afe1e Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Thu, 1 Mar 2018 10:20:34 +0000 Subject: [PATCH] Feature: Implemented Gopher support. Gopher clients are automagically detected based on the selector. All selectors must start with / and map directly to the HTTP paths. --- src/client.c | 33 +++++++++++++++++++++--------- src/connection.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++ src/fserve.c | 10 ++++++---- src/util.c | 4 ++++ src/xslt.c | 10 +++++++--- 5 files changed, 93 insertions(+), 16 deletions(-) diff --git a/src/client.c b/src/client.c index 93076c17..b2e70c72 100644 --- a/src/client.c +++ b/src/client.c @@ -272,7 +272,7 @@ void client_destroy(client_t *client) fastevent_emit(FASTEVENT_TYPE_CLIENT_DESTROY, FASTEVENT_FLAG_MODIFICATION_ALLOWED, FASTEVENT_DATATYPE_CLIENT, client); - if (client->reuse != ICECAST_REUSE_CLOSE) { + if (client->protocol != ICECAST_PROTOCOL_GOPHER && client->reuse != ICECAST_REUSE_CLOSE) { /* only reuse the client if we reached the body's EOF. */ if (client_body_eof(client) == 1) { client_reuseconnection(client); @@ -287,6 +287,10 @@ void client_destroy(client_t *client) if (auth_release_client(client)) return; + if (client->protocol == ICECAST_PROTOCOL_GOPHER && client->respcode != 0) { + client_send_bytes(client, "\r\n.\r\n", 5); + } + /* write log entry if ip is set (some things don't set it, like outgoing * slave requests */ @@ -511,19 +515,26 @@ void client_send_426(client_t *client, reuse_t reuse) /* this function is designed to work even if client is in bad state */ static inline void client_send_500(client_t *client, const char *message) { - const char header[] = "HTTP/1.0 500 Internal Server Error\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n" - "500 - Internal Server Error\n---------------------------\n"; - const ssize_t header_len = sizeof(header) - 1; + const char header_http[] = "HTTP/1.0 500 Internal Server Error\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n" + "500 - Internal Server Error\n---------------------------\n"; + const char header_gopher[] = "Internal Server Error\n---------------------------\n"; ssize_t ret; + ssize_t len; client->respcode = 500; client->refbuf->len = 0; fastevent_emit(FASTEVENT_TYPE_CLIENT_SEND_RESPONSE, FASTEVENT_FLAG_MODIFICATION_ALLOWED, FASTEVENT_DATATYPE_CLIENT, client); - ret = client_send_bytes(client, header, header_len); + if (client->protocol == ICECAST_PROTOCOL_GOPHER) { + len = sizeof(header_gopher) - 1; + ret = client_send_bytes(client, header_gopher, len); + } else { + len = sizeof(header_http) - 1; + ret = client_send_bytes(client, header_http, len); + } /* only send message if we have one AND if header could have transmitted completly */ - if (message && ret == header_len) + if (message && ret == len) client_send_bytes(client, message, strlen(message)); client_destroy(client); @@ -656,10 +667,14 @@ void client_send_reportxml(client_t *client, reportxml_t *report, document_domai } /* FIXME: in this section we hope no function will ever return -1 */ - if (location) { - ret += snprintf(client->refbuf->data + ret, buf_len - ret, "Location: %s\r\n", location); + if (client->protocol == ICECAST_PROTOCOL_GOPHER) { + ret += snprintf(client->refbuf->data + ret, buf_len - ret, "%s", buff); + } else { + if (location) { + ret += snprintf(client->refbuf->data + ret, buf_len - ret, "Location: %s\r\n", location); + } + ret += snprintf(client->refbuf->data + ret, buf_len - ret, "Content-Length: %d\r\n\r\n%s", xmlStrlen(buff), buff); } - ret += snprintf(client->refbuf->data + ret, buf_len - ret, "Content-Length: %d\r\n\r\n%s", xmlStrlen(buff), buff); client->refbuf->len = ret; xmlFree(buff); diff --git a/src/connection.c b/src/connection.c index 9a8e3a3a..cd0b81eb 100644 --- a/src/connection.c +++ b/src/connection.c @@ -503,6 +503,8 @@ static void process_request_queue (void) stream_offset = (ptr+2) - client->refbuf->data; break; } + if (client->refbuf->data[0] == '/' && strstr(client->refbuf->data, "\n") != NULL) + break; pass_it = 0; } while (0); @@ -1198,6 +1200,49 @@ static void _handle_shoutcast_compatible(client_queue_t *node) return; } +static int _handle_gopher_compatible(client_queue_t *node) +{ + size_t n_len; + char *n; + char *ptr; + http_parser_t *parser; + client_t *client = node->client; + + ptr = strstr(client->refbuf->data, "\r\n"); + if (!ptr) + ptr = strstr(client->refbuf->data, "\n"); + if (!ptr) + return -1; + + *ptr = 0; + + n_len = strlen(client->refbuf->data) + 80; + n = calloc(1, n_len); + if (!n) { + client_destroy(client); + free(node); + return 0; + } + + snprintf(n, n_len, "GET %s HTTP/1.0\r\n\r\n", client->refbuf->data); + + parser = httpp_create_parser(); + httpp_initialize(parser, NULL); + if (httpp_parse(parser, n, strlen(n))) { + client->refbuf->len = 0; + client->parser = parser; + client->protocol = ICECAST_PROTOCOL_GOPHER; + return 1; + } else { + httpp_destroy(parser); + client_destroy(client); + } + free(n); + free(node); + return 0; +} + + /* Handle lookups here. */ @@ -1610,6 +1655,13 @@ static void _handle_connection(void) continue; } + /* check for gopher clients */ + if (client->refbuf->data[0] == '/') { + if (_handle_gopher_compatible(node) == 0) { + continue; + } + } + /* process normal HTTP headers */ if (client->parser) { already_parsed = 1; diff --git a/src/fserve.c b/src/fserve.c index 13be4478..4df052a3 100644 --- a/src/fserve.c +++ b/src/fserve.c @@ -596,10 +596,12 @@ int fserve_client_create (client_t *httpclient) fclose(file); return -1; } - bytes += snprintf (httpclient->refbuf->data + bytes, BUFSIZE - bytes, - "Accept-Ranges: bytes\r\n" - "Content-Length: %" PRI_OFF_T "\r\n\r\n", - content_length); + if (httpclient->protocol != ICECAST_PROTOCOL_GOPHER) { + bytes += snprintf (httpclient->refbuf->data + bytes, BUFSIZE - bytes, + "Accept-Ranges: bytes\r\n" + "Content-Length: %" PRI_OFF_T "\r\n\r\n", + content_length); + } free (type); } httpclient->refbuf->len = bytes; diff --git a/src/util.c b/src/util.c index 1e11016d..ef85ee42 100644 --- a/src/util.c +++ b/src/util.c @@ -740,6 +740,10 @@ ssize_t util_http_build_header(char * out, size_t len, ssize_t offset, out += offset; len -= offset; + if (client && client->protocol == ICECAST_PROTOCOL_GOPHER) { + return snprintf(out, len, "%s", (datablock) ? datablock : ""); + } + if (status == -1) { status_buffer[0] = '\0'; diff --git a/src/xslt.c b/src/xslt.c index 480b3f74..f4b23c3c 100644 --- a/src/xslt.c +++ b/src/xslt.c @@ -417,10 +417,14 @@ void xslt_transform(xmlDocPtr doc, const char *xslfilename, client_t *client, in if (!failed) { /* FIXME: in this section we hope no function will ever return -1 */ - if (location) { - ret += snprintf(refbuf->data + ret, full_len - ret, "Location: %s\r\n", location); + if (client->protocol == ICECAST_PROTOCOL_GOPHER) { + snprintf(refbuf->data + ret, full_len - ret, "%s", string); + } else { + if (location) { + ret += snprintf(refbuf->data + ret, full_len - ret, "Location: %s\r\n", location); + } + ret += snprintf(refbuf->data + ret, full_len - ret, "Content-Length: %d\r\n\r\n%s", len, string); } - ret += snprintf(refbuf->data + ret, full_len - ret, "Content-Length: %d\r\n\r\n%s", len, string); client->respcode = status; client_set_queue (client, NULL);