/** * Copyright (C) 2011-2012 Juho Vähä-Herttua * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * *=================================================================== * modified by fduncanh 2022 */ #include #include #include #include #include #include #include "httpd.h" #include "netutils.h" #include "http_request.h" #include "compat.h" #include "logger.h" #include "utils.h" static const char *typename[] = { [CONNECTION_TYPE_UNKNOWN] = "Unknown", [CONNECTION_TYPE_RAOP] = "RAOP", [CONNECTION_TYPE_AIRPLAY] = "AirPlay", [CONNECTION_TYPE_PTTH] = "AirPlay (reversed)", [CONNECTION_TYPE_HLS] = "HLS" }; struct http_connection_s { int connected; int socket_fd; void *user_data; connection_type_t type; http_request_t *request; }; typedef struct http_connection_s http_connection_t; struct httpd_s { logger_t *logger; httpd_callbacks_t callbacks; int max_connections; int open_connections; http_connection_t *connections; char nohold; /* These variables only edited mutex locked */ int running; int joined; thread_handle_t thread; mutex_handle_t run_mutex; /* Server fds for accepting connections */ int server_fd4; int server_fd6; }; const char * httpd_get_connection_typename (connection_type_t type) { return typename[type]; } int httpd_get_connection_socket (httpd_t *httpd, void *user_data) { for (int i = 0; i < httpd->max_connections; i++) { http_connection_t *connection = &httpd->connections[i]; if (!connection->connected) { continue; } if (connection->user_data == user_data) { return connection->socket_fd; } } return -1; } int httpd_set_connection_type (httpd_t *httpd, void *user_data, connection_type_t type) { for (int i = 0; i < httpd->max_connections; i++) { http_connection_t *connection = &httpd->connections[i]; if (!connection->connected) { continue; } if (connection->user_data == user_data) { connection->type = type; return i; } } return -1; } int httpd_count_connection_type (httpd_t *httpd, connection_type_t type) { int count = 0; for (int i = 0; i < httpd->max_connections; i++) { http_connection_t *connection = &httpd->connections[i]; if (!connection->connected) { continue; } if (connection->type == type) { count++; } } return count; } int httpd_get_connection_socket_by_type (httpd_t *httpd, connection_type_t type, int instance){ int count = 0; for (int i = 0; i < httpd->max_connections; i++) { http_connection_t *connection = &httpd->connections[i]; if (!connection->connected) { continue; } if (connection->type == type) { count++; if (count == instance) { return connection->socket_fd; } } } return 0; } void * httpd_get_connection_by_type (httpd_t *httpd, connection_type_t type, int instance){ int count = 0; for (int i = 0; i < httpd->max_connections; i++) { http_connection_t *connection = &httpd->connections[i]; if (!connection->connected) { continue; } if (connection->type == type) { count++; if (count == instance) { return connection->user_data; } } } return NULL; } #define MAX_CONNECTIONS 12 /* value used in AppleTV 3*/ httpd_t * httpd_init(logger_t *logger, httpd_callbacks_t *callbacks, int nohold) { httpd_t *httpd; assert(logger); assert(callbacks); /* Allocate the httpd_t structure */ httpd = calloc(1, sizeof(httpd_t)); if (!httpd) { return NULL; } httpd->nohold = (nohold ? 1 : 0); httpd->max_connections = MAX_CONNECTIONS; httpd->connections = calloc(httpd->max_connections, sizeof(http_connection_t)); if (!httpd->connections) { free(httpd); return NULL; } /* Use the logger provided */ httpd->logger = logger; /* Save callback pointers */ memcpy(&httpd->callbacks, callbacks, sizeof(httpd_callbacks_t)); /* Initial status joined */ httpd->running = 0; httpd->joined = 1; return httpd; } void httpd_destroy(httpd_t *httpd) { if (httpd) { httpd_stop(httpd); free(httpd->connections); free(httpd); } } static void httpd_remove_connection(httpd_t *httpd, http_connection_t *connection) { if (connection->request) { http_request_destroy(connection->request); connection->request = NULL; } httpd->callbacks.conn_destroy(connection->user_data); shutdown(connection->socket_fd, SHUT_WR); closesocket(connection->socket_fd); connection->connected = 0; connection->user_data = NULL; connection->type = CONNECTION_TYPE_UNKNOWN; httpd->open_connections--; } static int httpd_add_connection(httpd_t *httpd, int fd, unsigned char *local, int local_len, unsigned char *remote, int remote_len, unsigned int zone_id) { void *user_data; int i; for (i=0; imax_connections; i++) { if (!httpd->connections[i].connected) { break; } } if (i == httpd->max_connections) { /* This code should never be reached, we do not select server_fds when full */ logger_log(httpd->logger, LOGGER_INFO, "Max connections reached"); return -1; } user_data = httpd->callbacks.conn_init(httpd->callbacks.opaque, local, local_len, remote, remote_len, zone_id); if (!user_data) { logger_log(httpd->logger, LOGGER_ERR, "Error initializing HTTP request handler"); return -1; } httpd->open_connections++; httpd->connections[i].socket_fd = fd; httpd->connections[i].connected = 1; httpd->connections[i].user_data = user_data; httpd->connections[i].type = CONNECTION_TYPE_UNKNOWN; //should not be necessary ... return 0; } static int httpd_accept_connection(httpd_t *httpd, int server_fd, int is_ipv6) { struct sockaddr_storage remote_saddr; socklen_t remote_saddrlen; struct sockaddr_storage local_saddr; socklen_t local_saddrlen; unsigned char *local, *remote; unsigned int local_zone_id, remote_zone_id; int local_len, remote_len; int ret, fd; remote_saddrlen = sizeof(remote_saddr); fd = accept(server_fd, (struct sockaddr *)&remote_saddr, &remote_saddrlen); if (fd == -1) { /* FIXME: Error happened */ return -1; } local_saddrlen = sizeof(local_saddr); ret = getsockname(fd, (struct sockaddr *)&local_saddr, &local_saddrlen); if (ret == -1) { shutdown(fd, SHUT_RDWR); closesocket(fd); return 0; } logger_log(httpd->logger, LOGGER_INFO, "Accepted %s client on socket %d", (is_ipv6 ? "IPv6" : "IPv4"), fd); local = netutils_get_address(&local_saddr, &local_len, &local_zone_id); remote = netutils_get_address(&remote_saddr, &remote_len, &remote_zone_id); assert (local_zone_id == remote_zone_id); ret = httpd_add_connection(httpd, fd, local, local_len, remote, remote_len, local_zone_id); if (ret == -1) { shutdown(fd, SHUT_RDWR); closesocket(fd); return 0; } return 1; } bool httpd_nohold(httpd_t *httpd) { return (httpd->nohold ? true: false); } void httpd_remove_known_connections(httpd_t *httpd) { for (int i = 0; i < httpd->max_connections; i++) { http_connection_t *connection = &httpd->connections[i]; if (!connection->connected || connection->type == CONNECTION_TYPE_UNKNOWN) { continue; } httpd_remove_connection(httpd, connection); } } static THREAD_RETVAL httpd_thread(void *arg) { httpd_t *httpd = arg; char http[] = "HTTP/1.1"; char buffer[1024]; int i; bool logger_debug = (logger_get_level(httpd->logger) >= LOGGER_DEBUG); assert(httpd); while (1) { fd_set rfds; struct timeval tv; int nfds=0; int ret; int new_request; MUTEX_LOCK(httpd->run_mutex); if (!httpd->running) { MUTEX_UNLOCK(httpd->run_mutex); break; } MUTEX_UNLOCK(httpd->run_mutex); /* Set timeout value to 5ms */ tv.tv_sec = 1; tv.tv_usec = 5000; /* Get the correct nfds value and set rfds */ FD_ZERO(&rfds); if (httpd->open_connections < httpd->max_connections) { if (httpd->server_fd4 != -1) { FD_SET(httpd->server_fd4, &rfds); if (nfds <= httpd->server_fd4) { nfds = httpd->server_fd4+1; } } if (httpd->server_fd6 != -1) { FD_SET(httpd->server_fd6, &rfds); if (nfds <= httpd->server_fd6) { nfds = httpd->server_fd6+1; } } } for (i=0; imax_connections; i++) { int socket_fd; if (!httpd->connections[i].connected) { continue; } socket_fd = httpd->connections[i].socket_fd; FD_SET(socket_fd, &rfds); if (nfds <= socket_fd) { nfds = socket_fd+1; } } ret = select(nfds, &rfds, NULL, NULL, &tv); if (ret == 0) { /* Timeout happened */ continue; } else if (ret == -1) { logger_log(httpd->logger, LOGGER_ERR, "httpd error in select: %d %s", errno, strerror(errno)); break; } if (httpd->open_connections < httpd->max_connections && httpd->server_fd4 != -1 && FD_ISSET(httpd->server_fd4, &rfds)) { ret = httpd_accept_connection(httpd, httpd->server_fd4, 0); if (ret == -1) { logger_log(httpd->logger, LOGGER_ERR, "httpd error in accept ipv4"); break; } else if (ret == 0) { continue; } } if (httpd->open_connections < httpd->max_connections && httpd->server_fd6 != -1 && FD_ISSET(httpd->server_fd6, &rfds)) { ret = httpd_accept_connection(httpd, httpd->server_fd6, 1); if (ret == -1) { logger_log(httpd->logger, LOGGER_ERR, "httpd error in accept ipv6"); break; } else if (ret == 0) { continue; } } for (i=0; imax_connections; i++) { http_connection_t *connection = &httpd->connections[i]; if (!connection->connected) { continue; } if (!FD_ISSET(connection->socket_fd, &rfds)) { continue; } /* If not in the middle of request, allocate one */ if (!connection->request) { connection->request = http_request_init(); assert(connection->request); new_request = 1; if (connection->type == CONNECTION_TYPE_PTTH) { http_request_is_reverse(connection->request); } logger_log(httpd->logger, LOGGER_DEBUG, "new request, connection %d, socket %d type %s", i, connection->socket_fd, typename [connection->type]); } else { new_request = 0; } logger_log(httpd->logger, LOGGER_DEBUG, "httpd receiving on socket %d, connection %d", connection->socket_fd, i); if (logger_debug) { logger_log(httpd->logger, LOGGER_DEBUG,"\nhttpd: current connections:"); for (int i = 0; i < httpd->max_connections; i++) { http_connection_t *connection = &httpd->connections[i]; if(!connection->connected) { continue; } if (!FD_ISSET(connection->socket_fd, &rfds)) { logger_log(httpd->logger, LOGGER_DEBUG, "connection %d type %d socket %d conn %p %s", i, connection->type, connection->socket_fd, connection->user_data, typename [connection->type]); } else { logger_log(httpd->logger, LOGGER_DEBUG, "connection %d type %d socket %d conn %p %s ACTIVE CONNECTION", i, connection->type, connection->socket_fd, connection->user_data, typename [connection->type]); } } logger_log(httpd->logger, LOGGER_DEBUG, " "); } /* reverse-http responses from the client must not be sent to the llhttp parser: * such messages start with "HTTP/1.1" */ if (new_request) { int readstart = 0; new_request = 0; while (readstart < 8) { ret = recv(connection->socket_fd, buffer + readstart, sizeof(buffer) - 1 - readstart, 0); if (ret == 0) { logger_log(httpd->logger, LOGGER_INFO, "Connection closed for socket %d", connection->socket_fd); break; } else if (ret == -1) { if (errno == EAGAIN) { continue; } else { int sock_err = SOCKET_GET_ERROR(); logger_log(httpd->logger, LOGGER_ERR, "httpd: recv socket error %d:%s", sock_err, strerror(sock_err)); break; } } else { readstart += ret; ret = readstart; } } if (!memcmp(buffer, http, 8)) { http_request_set_reverse(connection->request); } } else { ret = recv(connection->socket_fd, buffer, sizeof(buffer) - 1, 0); if (ret == 0) { logger_log(httpd->logger, LOGGER_INFO, "Connection closed for socket %d", connection->socket_fd); httpd_remove_connection(httpd, connection); continue; } } if (http_request_is_reverse(connection->request)) { /* this is a response from the client to a * GET /event reverse HTTP request from the server */ if (ret && logger_debug) { buffer[ret] = '\0'; logger_log(httpd->logger, LOGGER_INFO, "<<<< received response from client" " (reversed HTTP = \"PTTH/1.0\") connection" " on socket %d:\n%s\n", connection->socket_fd, buffer); } if (ret == 0) { httpd_remove_connection(httpd, connection); } continue; } /* Parse HTTP request from data read from connection */ http_request_add_data(connection->request, buffer, ret); if (http_request_has_error(connection->request)) { logger_log(httpd->logger, LOGGER_ERR, "httpd error in parsing: %s", http_request_get_error_name(connection->request)); httpd_remove_connection(httpd, connection); continue; } /* If request is finished, process and deallocate */ if (http_request_is_complete(connection->request)) { http_response_t *response = NULL; // Callback the received data to raop if (logger_debug) { const char *method = http_request_get_method(connection->request); const char *url = http_request_get_url(connection->request); const char *protocol = http_request_get_protocol(connection->request); logger_log(httpd->logger, LOGGER_INFO, "httpd request received on socket %d, " "connection %d, method = %s, url = %s, protocol = %s", connection->socket_fd, i, method, url, protocol); } httpd->callbacks.conn_request(connection->user_data, connection->request, &response); http_request_destroy(connection->request); connection->request = NULL; if (response) { const char *data; int datalen; int written; int ret; /* Get response data and datalen */ data = http_response_get_data(response, &datalen); written = 0; while (written < datalen) { ret = send(connection->socket_fd, data+written, datalen-written, 0); if (ret == -1) { logger_log(httpd->logger, LOGGER_ERR, "httpd error in sending data"); break; } written += ret; } if (http_response_get_disconnect(response)) { logger_log(httpd->logger, LOGGER_INFO, "Disconnecting on software request"); httpd_remove_connection(httpd, connection); } } else { logger_log(httpd->logger, LOGGER_WARNING, "httpd didn't get response"); } http_response_destroy(response); } else { logger_log(httpd->logger, LOGGER_DEBUG, "Request not complete, waiting for more data..."); } } } /* Remove all connections that are still connected */ for (i=0; imax_connections; i++) { http_connection_t *connection = &httpd->connections[i]; if (!connection->connected) { continue; } logger_log(httpd->logger, LOGGER_INFO, "Removing connection for socket %d", connection->socket_fd); httpd_remove_connection(httpd, connection); } /* Close server sockets since they are not used any more */ if (httpd->server_fd4 != -1) { shutdown(httpd->server_fd4, SHUT_RDWR); closesocket(httpd->server_fd4); httpd->server_fd4 = -1; } if (httpd->server_fd6 != -1) { shutdown(httpd->server_fd6, SHUT_RDWR); closesocket(httpd->server_fd6); httpd->server_fd6 = -1; } // Ensure running reflects the actual state MUTEX_LOCK(httpd->run_mutex); httpd->running = 0; MUTEX_UNLOCK(httpd->run_mutex); logger_log(httpd->logger, LOGGER_DEBUG, "Exiting HTTP thread"); return 0; } int httpd_start(httpd_t *httpd, unsigned short *port) { /* How many connection attempts are kept in queue */ int backlog = 5; assert(httpd); assert(port); MUTEX_LOCK(httpd->run_mutex); if (httpd->running || !httpd->joined) { MUTEX_UNLOCK(httpd->run_mutex); return 0; } httpd->server_fd4 = netutils_init_socket(port, 0, 0); if (httpd->server_fd4 == -1) { logger_log(httpd->logger, LOGGER_ERR, "Error initialising socket %d", SOCKET_GET_ERROR()); MUTEX_UNLOCK(httpd->run_mutex); return -1; } httpd->server_fd6 = netutils_init_socket(port, 1, 0); if (httpd->server_fd6 == -1) { logger_log(httpd->logger, LOGGER_WARNING, "Error initialising IPv6 socket %d", SOCKET_GET_ERROR()); logger_log(httpd->logger, LOGGER_WARNING, "Continuing without IPv6 support"); } if (httpd->server_fd4 != -1 && listen(httpd->server_fd4, backlog) == -1) { logger_log(httpd->logger, LOGGER_ERR, "Error listening to IPv4 socket"); closesocket(httpd->server_fd4); closesocket(httpd->server_fd6); MUTEX_UNLOCK(httpd->run_mutex); return -2; } if (httpd->server_fd6 != -1 && listen(httpd->server_fd6, backlog) == -1) { logger_log(httpd->logger, LOGGER_ERR, "Error listening to IPv6 socket"); closesocket(httpd->server_fd4); closesocket(httpd->server_fd6); MUTEX_UNLOCK(httpd->run_mutex); return -2; } logger_log(httpd->logger, LOGGER_INFO, "Initialized server socket(s)"); /* Set values correctly and create new thread */ httpd->running = 1; httpd->joined = 0; THREAD_CREATE(httpd->thread, httpd_thread, httpd); MUTEX_UNLOCK(httpd->run_mutex); return 1; } int httpd_is_running(httpd_t *httpd) { int running; assert(httpd); MUTEX_LOCK(httpd->run_mutex); running = httpd->running || !httpd->joined; MUTEX_UNLOCK(httpd->run_mutex); return running; } void httpd_stop(httpd_t *httpd) { assert(httpd); MUTEX_LOCK(httpd->run_mutex); if (!httpd->running || httpd->joined) { MUTEX_UNLOCK(httpd->run_mutex); return; } httpd->running = 0; MUTEX_UNLOCK(httpd->run_mutex); THREAD_JOIN(httpd->thread); MUTEX_LOCK(httpd->run_mutex); httpd->joined = 1; MUTEX_UNLOCK(httpd->run_mutex); }