From d548803d389249097e9c99f324f782206c099c6e Mon Sep 17 00:00:00 2001 From: Matias N Date: Tue, 30 Mar 2021 14:17:21 -0300 Subject: [PATCH] httpd: expose http_send_headers to be used in CGI handlers --- include/netutils/httpd.h | 73 +++++- netutils/webserver/httpd.c | 456 ++++++++++++++++----------------- netutils/webserver/httpd_cgi.c | 10 +- 3 files changed, 300 insertions(+), 239 deletions(-) diff --git a/include/netutils/httpd.h b/include/netutils/httpd.h index b014f118d..2a3f261a4 100644 --- a/include/netutils/httpd.h +++ b/include/netutils/httpd.h @@ -192,12 +192,83 @@ EXTERN const int g_httpd_numfiles; * Public Function Prototypes ****************************************************************************/ +/**************************************************************************** + * Name: httpd_init + * + * Description: + * This function initializes the web server and should be called at system + * boot-up. + * + ****************************************************************************/ + void httpd_init(void); + +/**************************************************************************** + * Name: httpd_listen + * + * Description: + * This is the main processing thread for the webserver. It never returns + * unless an error occurs + * + ****************************************************************************/ + int httpd_listen(void); + +/**************************************************************************** + * Name: httpd_cgi_register + * + * Description: + * Register a CGI handler + * + ****************************************************************************/ + void httpd_cgi_register(struct httpd_cgi_call *cgi_call); -uint16_t httpd_fs_count(char *name); + +/**************************************************************************** + * Name: httpd_send_datachunk + * + * Description: + * Sends a chunk of HTML data using either chunked or non-chunked encoding. + * + * Input Parameters: + * sockfd Socket to which to send the data. + * data Data to send + * len Length of data to send + * chunked If True, sends an HTTP Chunked-Encoding prolog before the data + * block, and a HTTP Chunked-Encoding epilog ("\r\n") after the + * data block. If False, just sends the data. + * + * Returned Value: + * On success, returns >=0. On failure, returns a negative number + * indicating the failure code. + * + ****************************************************************************/ + int httpd_send_datachunk(int sockfd, void *data, int len, bool chunked); +/**************************************************************************** + * Name: httpd_send_headers + * + * Description: + * Sends HTTP headers + * + * Input Parameters: + * pstate The httpd state + * status Numeric HTTP status code + * len Length of data to be sent in subsequent send + * + * Returned Value: + * On success, returns >=0. On failure, returns a negative number + * indicating the failure code. + * + ****************************************************************************/ + +int httpd_send_headers(struct httpd_state *pstate, int status, int len); + +#ifdef CONFIG_NETUTILS_HTTPDFSSTATS +uint16_t httpd_fs_count(char *name); +#endif + #ifdef CONFIG_NETUTILS_HTTPD_DIRLIST bool httpd_is_file(FAR const char *filename); ssize_t httpd_dirlist(int outfd, FAR struct httpd_fs_file *file); diff --git a/netutils/webserver/httpd.c b/netutils/webserver/httpd.c index 9e97d85ce..73d5bd6e3 100644 --- a/netutils/webserver/httpd.c +++ b/netutils/webserver/httpd.c @@ -1,5 +1,5 @@ /**************************************************************************** - * netutils/webserver/httpd.c + * apps/netutils/webserver/httpd.c * httpd Web server * * Copyright (C) 2007-2009, 2011-2012 Gregory Nutt. All rights reserved. @@ -192,76 +192,6 @@ static int httpd_close(struct httpd_fs_file *file) #endif } -/**************************************************************************** - * Name: httpd_send_datachunk - * - * Description: - * Sends a chunk of HTML data using either chunked or non-chunked encoding. - * - * Input Parameters: - * sockfd Socket to which to send the data. - * data Data to send - * len Length of data to send - * chunked If True, sends an HTTP Chunked-Encoding prolog before the data - * block, and a HTTP Chunked-Encoding epilog ("\r\n") after the - * data block. If False, just sends the data. - * - * Returned Value: - * On success, returns >=0. On failure, returns a negative number - * indicating the failure code. - * - ****************************************************************************/ - -int httpd_send_datachunk(int sockfd, void *data, int len, bool chunked) -{ - int ret = 0; -#if defined(CONFIG_NETUTILS_HTTPD_ENABLE_CHUNKED_ENCODING) - char chunked_info[HTTPD_MAX_CHUNKEDLEN]; -#endif - -#if defined(CONFIG_NETUTILS_HTTPD_ENABLE_CHUNKED_ENCODING) - /* Chunk prolog */ - - if (chunked) - { - int chunked_info_len = snprintf(chunked_info, HTTPD_MAX_CHUNKEDLEN, - "%X\r\n", len); - ret = send(sockfd, chunked_info, chunked_info_len, 0); - DEBUGASSERT(ret == chunked_info_len); - } -#endif - - if (ret >= 0) - { - if (len == 0) - { - /* Lower layer does not tolerate buf = NULL even if len = 0 - * so just pass a dummy pointer. - */ - - data = &len; - } - - ret = send(sockfd, data, len, 0); - DEBUGASSERT(ret == len); - } - -#if defined(CONFIG_NETUTILS_HTTPD_ENABLE_CHUNKED_ENCODING) - /* Chunk epilog */ - - if (ret >= 0) - { - if (chunked) - { - ret = send(sockfd, "\r\n", 2, 0); - DEBUGASSERT(ret == 2); - } - } -#endif - - return ret; -} - #ifdef CONFIG_NETUTILS_HTTPD_DUMPBUFFER static void httpd_dumpbuffer(FAR const char *msg, FAR const char *buffer, unsigned int nbytes) @@ -440,153 +370,6 @@ static int send_chunk(struct httpd_state *pstate, const char *buf, int len) return OK; } -static int send_headers(struct httpd_state *pstate, int status, int len) -{ - const char *mime; - const char *ptr; - char contentlen[HTTPD_MAX_CONTENTLEN] = - { - 0 - }; - - char header[HTTPD_MAX_HEADERLEN]; - int hdrlen; - int i; - - static const struct - { - const char *ext; - const char *mime; - } - - a[] = - { -#ifndef CONFIG_NETUTILS_HTTPD_SCRIPT_DISABLE - { - "shtml", "text/html" - }, -#endif - - { - "html", "text/html" - }, - - { - "css", "text/css" - }, - - { - "txt", "text/plain" - }, - - { - "js", "text/javascript" - }, - - { - "png", "image/png" - }, - - { - "gif", "image/gif" - }, - - { - "jpeg", "image/jpeg" - }, - - { - "jpg", "image/jpeg" - }, - - { - "mp3", "audio/mpeg" - } - }; - - ptr = strrchr(pstate->ht_filename, ISO_PERIOD); - if (ptr == NULL) - { - mime = "application/octet-stream"; - } - else - { - mime = "text/plain"; - - for (i = 0; i < sizeof a / sizeof *a; i++) - { - if (strncmp(a[i].ext, ptr + 1, strlen(a[i].ext)) == 0) - { - mime = a[i].mime; - break; - } - } - } - -#ifdef CONFIG_NETUTILS_HTTPD_DIRLIST - if (false == httpd_is_file(pstate->ht_filename)) - { - /* we assume that it's a directory */ - - mime = "text/html"; - } -#endif - - if (len >= 0) - { - snprintf(contentlen, HTTPD_MAX_CONTENTLEN, - "Content-Length: %d\r\n", len); - } - else - { -#ifndef CONFIG_NETUTILS_HTTPD_KEEPALIVE_DISABLE - /* Length unknown ahead of time */ - - pstate->ht_keepalive = false; -#endif -#if defined(CONFIG_NETUTILS_HTTPD_ENABLE_CHUNKED_ENCODING) - /* Turn on chunked encoding */ - - snprintf(contentlen, HTTPD_MAX_CONTENTLEN, - "Transfer-Encoding: chunked\r\n"); - pstate->ht_chunked = true; -#endif - } - - if (status == 413) - { - /* TODO: here we "SHOULD" include a Retry-After header */ - } - - /* Construct the header. - * - * REVISIT: Wouldn't asprintf be a better option than a large stack - * array? - */ - - hdrlen = snprintf(header, HTTPD_MAX_HEADERLEN, - "HTTP/1.0 %d %s\r\n" -#ifndef CONFIG_NETUTILS_HTTPD_SERVERHEADER_DISABLE - "Server: uIP/NuttX http://nuttx.org/\r\n" -#endif - "Connection: %s\r\n" - "Content-type: %s\r\n" - "%s" - "\r\n", - status, - status >= 400 ? "Error" : "OK", -#ifndef CONFIG_NETUTILS_HTTPD_KEEPALIVE_DISABLE - pstate->ht_keepalive ? "keep-alive" : "close", -#else - "close", -#endif - mime, - contentlen - ); - - return send_chunk(pstate, header, hdrlen); -} - static int httpd_senderror(struct httpd_state *pstate, int status) { int ret; @@ -611,7 +394,7 @@ static int httpd_senderror(struct httpd_state *pstate, int status) ret = httpd_openindex(pstate); - if (send_headers(pstate, status, + if (httpd_send_headers(pstate, status, ret == OK ? pstate->ht_file.len : sizeof msg - 1) != OK) { return ERROR; @@ -682,7 +465,7 @@ static int httpd_sendfile(struct httpd_state *pstate) #ifndef CONFIG_NETUTILS_HTTPD_KEEPALIVE_DISABLE pstate->ht_keepalive = false; #endif - if (send_headers(pstate, 200, -1) != OK) + if (httpd_send_headers(pstate, 200, -1) != OK) { goto done; } @@ -693,13 +476,13 @@ static int httpd_sendfile(struct httpd_state *pstate) #endif #ifdef CONFIG_NETUTILS_HTTPD_DIRLIST - if (send_headers(pstate, 200, -1) != OK) + if (httpd_send_headers(pstate, 200, -1) != OK) { goto done; } #else - if (send_headers(pstate, pstate->ht_file.len == 0 ? 204 : 200, - pstate->ht_file.len) != OK) + if (httpd_send_headers(pstate, pstate->ht_file.len == 0 ? 204 : 200, + pstate->ht_file.len) != OK) { goto done; } @@ -1039,13 +822,19 @@ static void single_server(uint16_t portno, pthread_startroutine_t handler, * Public Functions ****************************************************************************/ +/**************************************************************************** + * Name: httpd_init + ****************************************************************************/ + +void httpd_init(void) +{ +#ifdef CONFIG_NETUTILS_HTTPD_CLASSIC + httpd_fs_init(); +#endif +} + /**************************************************************************** * Name: httpd_listen - * - * Description: - * This is the main processing thread for the webserver. It never returns - * unless an error occurs - * ****************************************************************************/ int httpd_listen(void) @@ -1064,17 +853,210 @@ int httpd_listen(void) } /**************************************************************************** - * Name: httpd_init - * - * Description: - * This function initializes the web server and should be called at system - * boot-up. - * + * Name: httpd_send_datachunk ****************************************************************************/ -void httpd_init(void) +int httpd_send_datachunk(int sockfd, void *data, int len, bool chunked) { -#ifdef CONFIG_NETUTILS_HTTPD_CLASSIC - httpd_fs_init(); + int ret = 0; +#if defined(CONFIG_NETUTILS_HTTPD_ENABLE_CHUNKED_ENCODING) + char chunked_info[HTTPD_MAX_CHUNKEDLEN]; #endif + +#if defined(CONFIG_NETUTILS_HTTPD_ENABLE_CHUNKED_ENCODING) + /* Chunk prolog */ + + if (chunked) + { + int chunked_info_len = snprintf(chunked_info, HTTPD_MAX_CHUNKEDLEN, + "%X\r\n", len); + ret = send(sockfd, chunked_info, chunked_info_len, 0); + DEBUGASSERT(ret == chunked_info_len); + } +#endif + + if (ret >= 0) + { + if (len == 0) + { + /* Lower layer does not tolerate buf = NULL even if len = 0 + * so just pass a dummy pointer. + */ + + data = &len; + } + + ret = send(sockfd, data, len, 0); + DEBUGASSERT(ret == len); + } + +#if defined(CONFIG_NETUTILS_HTTPD_ENABLE_CHUNKED_ENCODING) + /* Chunk epilog */ + + if (ret >= 0) + { + if (chunked) + { + ret = send(sockfd, "\r\n", 2, 0); + DEBUGASSERT(ret == 2); + } + } +#endif + + return ret; +} + +/**************************************************************************** + * Name: httpd_send_headers + ****************************************************************************/ + +int httpd_send_headers(struct httpd_state *pstate, int status, int len) +{ + const char *mime; + const char *ptr; + char contentlen[HTTPD_MAX_CONTENTLEN] = + { + 0 + }; + + char header[HTTPD_MAX_HEADERLEN]; + int hdrlen; + int i; + + static const struct + { + const char *ext; + const char *mime; + } + + a[] = + { +#ifndef CONFIG_NETUTILS_HTTPD_SCRIPT_DISABLE + { + "shtml", "text/html" + }, +#endif + + { + "html", "text/html" + }, + + { + "css", "text/css" + }, + + { + "txt", "text/plain" + }, + + { + "json", "application/json" + }, + + { + "js", "text/javascript" + }, + + { + "png", "image/png" + }, + + { + "gif", "image/gif" + }, + + { + "jpeg", "image/jpeg" + }, + + { + "jpg", "image/jpeg" + }, + + { + "mp3", "audio/mpeg" + }, + }; + + ptr = strrchr(pstate->ht_filename, ISO_PERIOD); + if (ptr == NULL) + { + mime = "application/octet-stream"; + } + else + { + mime = "text/plain"; + + for (i = 0; i < sizeof a / sizeof *a; i++) + { + if (strncmp(a[i].ext, ptr + 1, strlen(a[i].ext)) == 0) + { + mime = a[i].mime; + break; + } + } + } + +#ifdef CONFIG_NETUTILS_HTTPD_DIRLIST + if (false == httpd_is_file(pstate->ht_filename)) + { + /* we assume that it's a directory */ + + mime = "text/html"; + } +#endif + + if (len >= 0) + { + snprintf(contentlen, HTTPD_MAX_CONTENTLEN, + "Content-Length: %d\r\n", len); + } + else + { +#ifndef CONFIG_NETUTILS_HTTPD_KEEPALIVE_DISABLE + /* Length unknown ahead of time */ + + pstate->ht_keepalive = false; +#endif +#if defined(CONFIG_NETUTILS_HTTPD_ENABLE_CHUNKED_ENCODING) + /* Turn on chunked encoding */ + + snprintf(contentlen, HTTPD_MAX_CONTENTLEN, + "Transfer-Encoding: chunked\r\n"); + pstate->ht_chunked = true; +#endif + } + + if (status == 413) + { + /* TODO: here we "SHOULD" include a Retry-After header */ + } + + /* Construct the header. + * + * REVISIT: Wouldn't asprintf be a better option than a large stack + * array? + */ + + hdrlen = snprintf(header, HTTPD_MAX_HEADERLEN, + "HTTP/1.0 %d %s\r\n" +#ifndef CONFIG_NETUTILS_HTTPD_SERVERHEADER_DISABLE + "Server: uIP/NuttX http://nuttx.org/\r\n" +#endif + "Connection: %s\r\n" + "Content-type: %s\r\n" + "%s" + "\r\n", + status, + status >= 400 ? "Error" : "OK", +#ifndef CONFIG_NETUTILS_HTTPD_KEEPALIVE_DISABLE + pstate->ht_keepalive ? "keep-alive" : "close", +#else + "close", +#endif + mime, + contentlen + ); + + return send_chunk(pstate, header, hdrlen); } diff --git a/netutils/webserver/httpd_cgi.c b/netutils/webserver/httpd_cgi.c index b5195bfcd..f917ebd16 100644 --- a/netutils/webserver/httpd_cgi.c +++ b/netutils/webserver/httpd_cgi.c @@ -1,5 +1,5 @@ /**************************************************************************** - * httpd_cgi.c + * apps/netutils/webserver/httpd_cgi.c * Web server script interface * Author: Adam Dunkels * @@ -58,6 +58,10 @@ struct httpd_cgi_call *cgi_calls = NULL; * Public Functions ****************************************************************************/ +/**************************************************************************** + * Name: httpd_cgi_register + ****************************************************************************/ + void httpd_cgi_register(struct httpd_cgi_call *cgi_call) { if (cgi_calls == NULL) @@ -71,6 +75,10 @@ void httpd_cgi_register(struct httpd_cgi_call *cgi_call) } } +/**************************************************************************** + * Name: httpd_cgi + ****************************************************************************/ + httpd_cgifunction httpd_cgi(char *name) { struct httpd_cgi_call *cgi_call = cgi_calls;