diff --git a/vendor/CivetWeb/CMakeLists.txt b/vendor/CivetWeb/CMakeLists.txt index 262adfd3..7526a460 100644 --- a/vendor/CivetWeb/CMakeLists.txt +++ b/vendor/CivetWeb/CMakeLists.txt @@ -10,7 +10,7 @@ add_library(CivetWeb STATIC wolfssl_extras.inl handle_form.inl md5.inl - mod_http2.inl + http2.inl ) # Configure include folders target_include_directories(CivetWeb PRIVATE ${CMAKE_CURRENT_LIST_DIR}) @@ -25,14 +25,14 @@ target_compile_definitions(CivetWeb PUBLIC USE_TIMERS=1 USE_WEBSOCKET=1 USE_IPV6 find_package(OpenSSL) # Check SSL status if (OPENSSL_FOUND) - message(STATUS "CivetWeb: OpenSSL was found") + message(STATUS "CivetWeb: OpenSSL was found ${OPENSSL_VERSION}") target_link_libraries(CivetWeb PUBLIC OpenSSL::Crypto OpenSSL::SSL) string(REPLACE "." ";" OPENSSL_VERSION_LIST ${OPENSSL_VERSION}) list(GET OPENSSL_VERSION_LIST 0 OPENSSL_VERSION_MAJOR) list(GET OPENSSL_VERSION_LIST 1 OPENSSL_VERSION_MINOR) list(GET OPENSSL_VERSION_LIST 2 OPENSSL_VERSION_PATCH) # Tell the library what SSL version to expect - target_compile_definitions(CivetWeb PUBLIC "OPENSSL_API_${OPENSSL_VERSION_MAJOR}_${OPENSSL_VERSION_MINOR}") + target_compile_definitions(CivetWeb PRIVATE "OPENSSL_API_${OPENSSL_VERSION_MAJOR}_${OPENSSL_VERSION_MINOR}") message(STATUS "CivetWeb: OPENSSL_API_${OPENSSL_VERSION_MAJOR}_${OPENSSL_VERSION_MINOR}") else() target_compile_definitions(CivetWeb PUBLIC NO_SSL=1) diff --git a/vendor/CivetWeb/civetweb.c b/vendor/CivetWeb/civetweb.c index fea9e6f9..2e7ad7bf 100644 --- a/vendor/CivetWeb/civetweb.c +++ b/vendor/CivetWeb/civetweb.c @@ -21,8 +21,10 @@ */ #if defined(__GNUC__) || defined(__MINGW32__) +#ifndef GCC_VERSION #define GCC_VERSION \ (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +#endif #if GCC_VERSION >= 40500 /* gcc diagnostic pragmas available */ #define GCC_DIAGNOSTIC @@ -182,20 +184,18 @@ mg_static_assert(sizeof(void *) >= sizeof(int), "data type size check"); #endif /* __SYMBIAN32__ */ #if defined(__ZEPHYR__) -#include - #include -#include -#include -#include +#include +#include +#include +#include #include #include #include -#include +#include +#include -#include - -#include +#include /* Max worker threads is the max of pthreads minus the main application thread * and minus the main civetweb thread, thus -2 @@ -352,7 +352,7 @@ __cyg_profile_func_exit(void *this_fn, void *call_site) #endif -#if defined(__MACH__) /* Apple OSX section */ +#if defined(__MACH__) && defined(__APPLE__) /* Apple OSX section */ #if defined(__clang__) #if (__clang_major__ == 3) && ((__clang_minor__ == 7) || (__clang_minor__ == 8)) @@ -438,10 +438,12 @@ _civet_safe_clock_gettime(int clk_id, struct timespec *t) #endif -#if !defined(_WIN32) +#if defined(_WIN32) +#define ERROR_TRY_AGAIN(err) ((err) == WSAEWOULDBLOCK) +#else /* Unix might return different error codes indicating to try again. * For Linux EAGAIN==EWOULDBLOCK, maybe EAGAIN!=EWOULDBLOCK is history from - * decades ago, but better check both and let the compile optimize it. */ + * decades ago, but better check both and let the compiler optimize it. */ #define ERROR_TRY_AGAIN(err) \ (((err) == EAGAIN) || ((err) == EWOULDBLOCK) || ((err) == EINTR)) #endif @@ -499,7 +501,7 @@ _civet_safe_clock_gettime(int clk_id, struct timespec *t) /********************************************************************/ -/* Helper makros */ +/* Helper macros */ #if !defined(ARRAY_SIZE) #define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0])) #endif @@ -581,6 +583,7 @@ typedef const char *SOCK_OPT_TYPE; #endif #endif /* _MSC_VER */ + #define ERRNO ((int)(GetLastError())) #define NO_SOCKLEN_T @@ -588,6 +591,10 @@ typedef const char *SOCK_OPT_TYPE; #if defined(_WIN64) || defined(__MINGW64__) #if !defined(SSL_LIB) +#if defined(OPENSSL_API_3_1) + #define OPENSSL_API_3_0 +#endif + #if defined(OPENSSL_API_3_0) #define SSL_LIB "libssl-3-x64.dll" #define CRYPTO_LIB "libcrypto-3-x64.dll" @@ -872,6 +879,7 @@ typedef unsigned short int in_port_t; #include #include #include +#include #include #include #include @@ -879,7 +887,6 @@ typedef unsigned short int in_port_t; #include #include #include -#include #include #include #include @@ -897,7 +904,7 @@ typedef unsigned short int in_port_t; #include #endif -#if defined(__MACH__) +#if defined(__MACH__) && defined(__APPLE__) #define SSL_LIB "libssl.dylib" #define CRYPTO_LIB "libcrypto.dylib" #else @@ -1555,10 +1562,15 @@ static int mg_openssl_initialized = 0; #endif #if !defined(OPENSSL_API_1_0) && !defined(OPENSSL_API_1_1) \ && !defined(OPENSSL_API_3_0) && !defined(USE_MBEDTLS) -#error "Please define OPENSSL_API_1_0 or OPENSSL_API_1_1" +#error "Please define OPENSSL_API_#_# or USE_MBEDTLS" #endif -#if defined(OPENSSL_API_1_0) && defined(OPENSSL_API_1_1) \ - && defined(OPENSSL_API_3_0) +#if defined(OPENSSL_API_1_0) && defined(OPENSSL_API_1_1) +#error "Multiple OPENSSL_API versions defined" +#endif +#if defined(OPENSSL_API_1_1) && defined(OPENSSL_API_3_0) +#error "Multiple OPENSSL_API versions defined" +#endif +#if defined(OPENSSL_API_1_0) && defined(OPENSSL_API_3_0) #error "Multiple OPENSSL_API versions defined" #endif #if (defined(OPENSSL_API_1_0) || defined(OPENSSL_API_1_1) \ @@ -1955,6 +1967,7 @@ enum { #if defined(USE_TIMERS) CGI_TIMEOUT, #endif + CGI_BUFFERING, CGI2_EXTENSIONS, CGI2_ENVIRONMENT, @@ -1963,6 +1976,7 @@ enum { #if defined(USE_TIMERS) CGI2_TIMEOUT, #endif + CGI2_BUFFERING, #if defined(USE_4_CGI) CGI3_EXTENSIONS, @@ -1972,6 +1986,7 @@ enum { #if defined(USE_TIMERS) CGI3_TIMEOUT, #endif + CGI3_BUFFERING, CGI4_EXTENSIONS, CGI4_ENVIRONMENT, @@ -1980,6 +1995,7 @@ enum { #if defined(USE_TIMERS) CGI4_TIMEOUT, #endif + CGI4_BUFFERING, #endif PUT_DELETE_PASSWORDS_FILE, /* must follow CGI_* */ @@ -1988,6 +2004,7 @@ enum { ENABLE_AUTH_DOMAIN_CHECK, SSI_EXTENSIONS, ENABLE_DIRECTORY_LISTING, + ENABLE_WEBDAV, GLOBAL_PASSWORDS_FILE, INDEX_FILES, ACCESS_CONTROL_LIST, @@ -2094,6 +2111,7 @@ static const struct mg_option config_options[] = { #if defined(USE_TIMERS) {"cgi_timeout_ms", MG_CONFIG_TYPE_NUMBER, NULL}, #endif + {"cgi_buffering", MG_CONFIG_TYPE_BOOLEAN, "yes"}, {"cgi2_pattern", MG_CONFIG_TYPE_EXT_PATTERN, NULL}, {"cgi2_environment", MG_CONFIG_TYPE_STRING_LIST, NULL}, @@ -2102,6 +2120,7 @@ static const struct mg_option config_options[] = { #if defined(USE_TIMERS) {"cgi2_timeout_ms", MG_CONFIG_TYPE_NUMBER, NULL}, #endif + {"cgi2_buffering", MG_CONFIG_TYPE_BOOLEAN, "yes"}, #if defined(USE_4_CGI) {"cgi3_pattern", MG_CONFIG_TYPE_EXT_PATTERN, NULL}, @@ -2111,14 +2130,17 @@ static const struct mg_option config_options[] = { #if defined(USE_TIMERS) {"cgi3_timeout_ms", MG_CONFIG_TYPE_NUMBER, NULL}, #endif + {"cgi3_buffering", MG_CONFIG_TYPE_BOOLEAN, "yes"}, - {"cgi2_pattern", MG_CONFIG_TYPE_EXT_PATTERN, NULL}, + {"cgi4_pattern", MG_CONFIG_TYPE_EXT_PATTERN, NULL}, {"cgi4_environment", MG_CONFIG_TYPE_STRING_LIST, NULL}, {"cgi4_interpreter", MG_CONFIG_TYPE_FILE, NULL}, {"cgi4_interpreter_args", MG_CONFIG_TYPE_STRING, NULL}, #if defined(USE_TIMERS) {"cgi4_timeout_ms", MG_CONFIG_TYPE_NUMBER, NULL}, #endif + {"cgi4_buffering", MG_CONFIG_TYPE_BOOLEAN, "yes"}, + #endif {"put_delete_auth_file", MG_CONFIG_TYPE_FILE, NULL}, @@ -2127,6 +2149,7 @@ static const struct mg_option config_options[] = { {"enable_auth_domain_check", MG_CONFIG_TYPE_BOOLEAN, "yes"}, {"ssi_pattern", MG_CONFIG_TYPE_EXT_PATTERN, "**.shtml$|**.shtm$"}, {"enable_directory_listing", MG_CONFIG_TYPE_BOOLEAN, "yes"}, + {"enable_webdav", MG_CONFIG_TYPE_BOOLEAN, "no"}, {"global_auth_file", MG_CONFIG_TYPE_FILE, NULL}, {"index_files", MG_CONFIG_TYPE_STRING_LIST, @@ -2214,7 +2237,7 @@ struct mg_handler_info { /* handler type */ int handler_type; - /* Handler for http/https or authorization requests. */ + /* Handler for http/https or requests. */ mg_request_handler handler; unsigned int refcount; int removing; @@ -2275,9 +2298,9 @@ struct mg_domain_context { typedef ptrdiff_t volatile stop_flag_t; static int -STOP_FLAG_IS_ZERO(stop_flag_t *f) +STOP_FLAG_IS_ZERO(const stop_flag_t *f) { - stop_flag_t sf = mg_atomic_add(f, 0); + stop_flag_t sf = mg_atomic_add((stop_flag_t *)f, 0); return (sf == 0); } @@ -2307,6 +2330,22 @@ typedef int volatile stop_flag_t; #endif /* STOP_FLAG_NEEDS_LOCK */ +#if !defined(NUM_WEBDAV_LOCKS) +#define NUM_WEBDAV_LOCKS 10 +#endif +#if !defined(LOCK_DURATION_S) +#define LOCK_DURATION_S 60 +#endif + + +struct twebdav_lock { + uint64_t locktime; + char token[33]; + char path[UTF8_PATH_MAX * 2]; + char user[UTF8_PATH_MAX * 2]; +}; + + struct mg_context { /* Part 1 - Physical context: @@ -2369,6 +2408,9 @@ struct mg_context { struct mg_memory_stat ctx_memory; #endif + /* WebDAV lock structures */ + struct twebdav_lock webdav_lock[NUM_WEBDAV_LOCKS]; + /* Operating system related */ char *systemName; /* What operating system is running */ time_t start_time; /* Server start time, used for authentication @@ -2472,7 +2514,7 @@ struct mg_connection { #if defined(USE_SERVER_STATS) time_t conn_close_time; /* Time (wall clock) when connection was * closed (or 0 if still open) */ - double processing_time; /* Procesing time for one request. */ + double processing_time; /* Processing time for one request. */ #endif struct timespec req_time; /* Time (since system start) when the request * was received */ @@ -2537,7 +2579,6 @@ struct mg_connection { /* Directory entry */ struct de { - struct mg_connection *conn; char *file_name; struct mg_file_stat file; }; @@ -2769,7 +2810,7 @@ mg_set_thread_name(const char *name) #elif defined(_GNU_SOURCE) && defined(__GLIBC__) \ && ((__GLIBC__ > 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 12))) /* pthread_setname_np first appeared in glibc in version 2.12 */ -#if defined(__MACH__) +#if defined(__MACH__) && defined(__APPLE__) /* OS X only current thread name can be changed */ (void)pthread_setname_np(threadName); #else @@ -2785,14 +2826,14 @@ mg_set_thread_name(const char *name) #endif } #else /* !defined(NO_THREAD_NAME) */ -void +static void mg_set_thread_name(const char *threadName) { } #endif -const struct mg_option * +CIVETWEB_API const struct mg_option * mg_get_valid_options(void) { return config_options; @@ -2978,7 +3019,7 @@ lowercase(const char *s) } -int +CIVETWEB_API int mg_strncasecmp(const char *s1, const char *s2, size_t len) { int diff = 0; @@ -2993,7 +3034,7 @@ mg_strncasecmp(const char *s1, const char *s2, size_t len) } -int +CIVETWEB_API int mg_strcasecmp(const char *s1, const char *s2) { int diff; @@ -3132,7 +3173,7 @@ get_option_index(const char *name) } -const char * +CIVETWEB_API const char * mg_get_option(const struct mg_context *ctx, const char *name) { int i; @@ -3147,28 +3188,28 @@ mg_get_option(const struct mg_context *ctx, const char *name) #define mg_get_option DO_NOT_USE_THIS_FUNCTION_INTERNALLY__access_directly -struct mg_context * +CIVETWEB_API struct mg_context * mg_get_context(const struct mg_connection *conn) { return (conn == NULL) ? (struct mg_context *)NULL : (conn->phys_ctx); } -void * +CIVETWEB_API void * mg_get_user_data(const struct mg_context *ctx) { return (ctx == NULL) ? NULL : ctx->user_data; } -void * +CIVETWEB_API void * mg_get_user_context_data(const struct mg_connection *conn) { return mg_get_user_data(mg_get_context(conn)); } -void * +CIVETWEB_API void * mg_get_thread_pointer(const struct mg_connection *conn) { /* both methods should return the same pointer */ @@ -3184,7 +3225,7 @@ mg_get_thread_pointer(const struct mg_connection *conn) } -void +CIVETWEB_API void mg_set_user_connection_data(const struct mg_connection *const_conn, void *data) { if (const_conn != NULL) { @@ -3197,7 +3238,7 @@ mg_set_user_connection_data(const struct mg_connection *const_conn, void *data) } -void * +CIVETWEB_API void * mg_get_user_connection_data(const struct mg_connection *conn) { if (conn != NULL) { @@ -3207,7 +3248,7 @@ mg_get_user_connection_data(const struct mg_connection *conn) } -int +CIVETWEB_API int mg_get_server_ports(const struct mg_context *ctx, int size, struct mg_server_port *ports) @@ -3292,8 +3333,7 @@ sockaddr_to_string(char *buf, size_t len, const union usa *usa) 0, NI_NUMERICHOST); */ - strncpy(buf, UNIX_DOMAIN_SOCKET_SERVER_NAME, len); - buf[len] = 0; + mg_strlcpy(buf, UNIX_DOMAIN_SOCKET_SERVER_NAME, len); } #endif } @@ -3319,7 +3359,6 @@ gmt_time_string(char *buf, size_t buf_len, time_t *t) strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S GMT", tm); } else { mg_strlcpy(buf, "Thu, 01 Jan 1970 00:00:00 GMT", buf_len); - buf[buf_len - 1] = '\0'; } } @@ -3462,7 +3501,7 @@ mg_cry_internal_wrap(const struct mg_connection *conn, } -void +CIVETWEB_API void mg_cry(const struct mg_connection *conn, const char *fmt, ...) { va_list ap; @@ -3475,14 +3514,14 @@ mg_cry(const struct mg_connection *conn, const char *fmt, ...) #define mg_cry DO_NOT_USE_THIS_FUNCTION__USE_mg_cry_internal -const char * +CIVETWEB_API const char * mg_version(void) { return CIVETWEB_VERSION; } -const struct mg_request_info * +CIVETWEB_API const struct mg_request_info * mg_get_request_info(const struct mg_connection *conn) { if (!conn) { @@ -3522,7 +3561,7 @@ mg_get_request_info(const struct mg_connection *conn) } -const struct mg_response_info * +CIVETWEB_API const struct mg_response_info * mg_get_response_info(const struct mg_connection *conn) { if (!conn) { @@ -3573,6 +3612,7 @@ mg_construct_local_link(const struct mg_connection *conn, if ((buflen < 1) || (buf == 0) || (conn == 0)) { return -1; } else { + int i, j; int truncated = 0; const struct mg_request_info *ri = &conn->request_info; @@ -3584,11 +3624,31 @@ mg_construct_local_link(const struct mg_connection *conn, : ((ri->request_uri != NULL) ? ri->request_uri : ri->local_uri); int port = (define_port > 0) ? define_port : ri->server_port; int default_port = 80; + char *uri_encoded; + size_t uri_encoded_len; if (uri == NULL) { return -1; } + uri_encoded_len = strlen(uri) * 3 + 1; + uri_encoded = (char *)mg_malloc_ctx(uri_encoded_len, conn->phys_ctx); + if (uri_encoded == NULL) { + return -1; + } + mg_url_encode(uri, uri_encoded, uri_encoded_len); + + /* Directory separator should be preserved. */ + for (i = j = 0; uri_encoded[i]; j++) { + if (!strncmp(uri_encoded + i, "%2f", 3)) { + uri_encoded[j] = '/'; + i += 3; + } else { + uri_encoded[j] = uri_encoded[i++]; + } + } + uri_encoded[j] = '\0'; + #if defined(USE_X_DOM_SOCKET) if (conn->client.lsa.sa.sa_family == AF_UNIX) { /* TODO: Define and document a link for UNIX domain sockets. */ @@ -3610,6 +3670,7 @@ mg_construct_local_link(const struct mg_connection *conn, server_name, ri->local_uri); default_port = 0; + mg_free(uri_encoded); return 0; } #endif @@ -3672,8 +3733,9 @@ mg_construct_local_link(const struct mg_connection *conn, server_domain, #endif portstr, - ri->local_uri); + uri_encoded); + mg_free(uri_encoded); if (truncated) { return -1; } @@ -3683,7 +3745,7 @@ mg_construct_local_link(const struct mg_connection *conn, } -int +CIVETWEB_API int mg_get_request_link(const struct mg_connection *conn, char *buf, size_t buflen) { return mg_construct_local_link(conn, buf, buflen, NULL, -1, NULL); @@ -3710,8 +3772,7 @@ skip_quoted(char **buf, if (end_word > begin_word) { p = end_word - 1; while (*p == quotechar) { - /* While the delimiter is quoted, look for the next delimiter. - */ + /* While the delimiter is quoted, look for the next delimiter. */ /* This happens, e.g., in calls from parse_auth_header, * if the user name contains a " character. */ @@ -3773,7 +3834,6 @@ get_header(const struct mg_header *hdr, int num_hdr, const char *name) } -#if defined(USE_WEBSOCKET) /* Retrieve requested HTTP header multiple values, and return the number of * found occurrences */ static int @@ -3793,10 +3853,9 @@ get_req_headers(const struct mg_request_info *ri, } return cnt; } -#endif -const char * +CIVETWEB_API const char * mg_get_header(const struct mg_connection *conn, const char *name) { if (!conn) { @@ -3915,60 +3974,11 @@ header_has_option(const char *header, const char *option) } -/* Perform case-insensitive match of string against pattern */ -static ptrdiff_t -match_prefix(const char *pattern, size_t pattern_len, const char *str) -{ - const char *or_str; - ptrdiff_t i, j, len, res; +/* Sorting function implemented in a separate file */ +#include "sort.inl" - if ((or_str = (const char *)memchr(pattern, '|', pattern_len)) != NULL) { - res = match_prefix(pattern, (size_t)(or_str - pattern), str); - return (res > 0) ? res - : match_prefix(or_str + 1, - (size_t)((pattern + pattern_len) - - (or_str + 1)), - str); - } - - for (i = 0, j = 0; (i < (ptrdiff_t)pattern_len); i++, j++) { - if ((pattern[i] == '?') && (str[j] != '\0')) { - continue; - } else if (pattern[i] == '$') { - return (str[j] == '\0') ? j : -1; - } else if (pattern[i] == '*') { - i++; - if (pattern[i] == '*') { - i++; - len = (ptrdiff_t)strlen(str + j); - } else { - len = (ptrdiff_t)strcspn(str + j, "/"); - } - if (i == (ptrdiff_t)pattern_len) { - return j + len; - } - do { - res = match_prefix(pattern + i, - (pattern_len - (size_t)i), - str + j + len); - } while (res == -1 && len-- > 0); - return (res == -1) ? -1 : j + res + len; - } else if (lowercase(&pattern[i]) != lowercase(&str[j])) { - return -1; - } - } - return (ptrdiff_t)j; -} - - -static ptrdiff_t -match_prefix_strlen(const char *pattern, const char *str) -{ - if (pattern == NULL) { - return -1; - } - return match_prefix(pattern, strlen(pattern), str); -} +/* Pattern matching has been reimplemented in a new file */ +#include "match.inl" /* HTTP 1.1 assumes keep alive if "Connection:" header is not set @@ -4139,6 +4149,26 @@ send_additional_header(struct mg_connection *conn) } +static void +send_cors_header(struct mg_connection *conn) +{ + const char *origin_hdr = mg_get_header(conn, "Origin"); + const char *cors_orig_cfg = + conn->dom_ctx->config[ACCESS_CONTROL_ALLOW_ORIGIN]; + + if (cors_orig_cfg && *cors_orig_cfg && origin_hdr && *origin_hdr) { + /* Cross-origin resource sharing (CORS), see + * http://www.html5rocks.com/en/tutorials/cors/, + * http://www.html5rocks.com/static/images/cors_server_flowchart.png + * CORS preflight is not supported for files. */ + mg_response_header_add(conn, + "Access-Control-Allow-Origin", + cors_orig_cfg, + -1); + } +} + + #if !defined(NO_FILESYSTEMS) static void handle_file_based_request(struct mg_connection *conn, const char *path, @@ -4146,7 +4176,7 @@ static void handle_file_based_request(struct mg_connection *conn, #endif /* NO_FILESYSTEMS */ -const char * +CIVETWEB_API const char * mg_get_response_code_text(const struct mg_connection *conn, int response_code) { /* See IANA HTTP status code assignment: @@ -4460,7 +4490,7 @@ mg_send_http_error_impl(struct mg_connection *conn, len = (int)sizeof(path_buf) - 32; } - /* Start with the file extenstion from the configuration. */ + /* Start with the file extension from the configuration. */ tstr = strchr(error_page_file_ext, '.'); while (tstr) { @@ -4486,7 +4516,7 @@ mg_send_http_error_impl(struct mg_connection *conn, DEBUG_TRACE("Check error page %s - not found", path_buf); - /* Continue with the next file extenstion from the + /* Continue with the next file extension from the * configuration (if there is a next one). */ tstr = strchr(tstr + i, '.'); } @@ -4507,6 +4537,7 @@ mg_send_http_error_impl(struct mg_connection *conn, mg_response_header_start(conn, status); send_no_cache_header(conn); send_additional_header(conn); + send_cors_header(conn); if (has_body) { mg_response_header_add(conn, "Content-Type", @@ -4531,7 +4562,7 @@ mg_send_http_error_impl(struct mg_connection *conn, } -int +CIVETWEB_API int mg_send_http_error(struct mg_connection *conn, int status, const char *fmt, ...) { va_list ap; @@ -4545,7 +4576,7 @@ mg_send_http_error(struct mg_connection *conn, int status, const char *fmt, ...) } -int +CIVETWEB_API int mg_send_http_ok(struct mg_connection *conn, const char *mime_type, long long content_length) @@ -4558,6 +4589,7 @@ mg_send_http_ok(struct mg_connection *conn, mg_response_header_start(conn, 200); send_no_cache_header(conn); send_additional_header(conn); + send_cors_header(conn); mg_response_header_add(conn, "Content-Type", mime_type, -1); if (content_length < 0) { /* Size not known. Use chunked encoding (HTTP/1.x) */ @@ -4586,7 +4618,7 @@ mg_send_http_ok(struct mg_connection *conn, } -int +CIVETWEB_API int mg_send_http_redirect(struct mg_connection *conn, const char *target_url, int redirect_code) @@ -4602,11 +4634,11 @@ mg_send_http_redirect(struct mg_connection *conn, * 307 | temporary | always keep method | HTTP/1.1 * 308 | permanent | always keep method | HTTP/1.1 */ - const char *redirect_text; - int ret; - size_t content_len = 0; + #if defined(MG_SEND_REDIRECT_BODY) - char reply[MG_BUF_LEN]; + char redirect_body[MG_BUF_LEN]; + size_t content_len = 0; + char content_len_text[32]; #endif /* In case redirect_code=0, use 307. */ @@ -4622,9 +4654,6 @@ mg_send_http_redirect(struct mg_connection *conn, return -2; } - /* Get proper text for response code */ - redirect_text = mg_get_response_code_text(conn, redirect_code); - /* If target_url is not defined, redirect to "/". */ if ((target_url == NULL) || (*target_url == 0)) { target_url = "/"; @@ -4656,39 +4685,45 @@ mg_send_http_redirect(struct mg_connection *conn, mg_snprintf( conn, NULL /* ignore truncation */, - reply, - sizeof(reply), + redirect_body, + sizeof(redirect_body), "%s%s", redirect_text, target_url, target_url); content_len = strlen(reply); + sprintf(content_len_text, "%lu", (unsigned long)content_len); #endif - /* Do not send any additional header. For all other options, - * including caching, there are suitable defaults. */ - ret = mg_printf(conn, - "HTTP/1.1 %i %s\r\n" - "Location: %s\r\n" - "Content-Length: %u\r\n" - "Connection: %s\r\n\r\n", - redirect_code, - redirect_text, - target_url, - (unsigned int)content_len, - suggest_connection_header(conn)); + /* Send all required headers */ + mg_response_header_start(conn, redirect_code); + mg_response_header_add(conn, "Location", target_url, -1); + if ((redirect_code == 301) || (redirect_code == 308)) { + /* Permanent redirect */ + send_static_cache_header(conn); + } else { + /* Temporary redirect */ + send_no_cache_header(conn); + } + send_additional_header(conn); + send_cors_header(conn); +#if defined(MG_SEND_REDIRECT_BODY) + mg_response_header_add(conn, "Content-Type", "text/html", -1); + mg_response_header_add(conn, "Content-Length", content_len_text, -1); +#else + mg_response_header_add(conn, "Content-Length", "0", 1); +#endif + mg_response_header_send(conn); #if defined(MG_SEND_REDIRECT_BODY) /* Send response body */ - if (ret > 0) { - /* ... unless it is a HEAD request */ - if (0 != strcmp(conn->request_info.request_method, "HEAD")) { - ret = mg_write(conn, reply, content_len); - } + /* ... unless it is a HEAD request */ + if (0 != strcmp(conn->request_info.request_method, "HEAD")) { + ret = mg_write(conn, redirect_body, content_len); } #endif - return (ret > 0) ? ret : -1; + return 1; } @@ -5262,7 +5297,7 @@ set_close_on_exec(SOCKET sock, } -int +CIVETWEB_API int mg_start_thread(mg_thread_func_t f, void *p) { #if defined(USE_STACK_SIZE) && (USE_STACK_SIZE > 1) @@ -5665,7 +5700,7 @@ set_close_on_exec(int fd, } -int +CIVETWEB_API int mg_start_thread(mg_thread_func_t func, void *param) { pthread_t thread_id; @@ -5807,7 +5842,7 @@ spawn_process(struct mg_connection *conn, interp = conn->dom_ctx->config[CGI_INTERPRETER + cgi_config_idx]; if (interp == NULL) { - /* no interpreter configured, call the programm directly */ + /* no interpreter configured, call the program directly */ (void)execle(prog, prog, NULL, envp); mg_cry_internal(conn, "%s: execle(%s): %s", @@ -5935,15 +5970,18 @@ mg_poll(struct mg_pollfd *pfd, result = poll(pfd, n, ms_now); if (result != 0) { - /* Poll returned either success (1) or error (-1). - * Forward both to the caller. */ - if ((check_pollerr) - && ((pfd[0].revents & (POLLIN | POLLOUT | POLLERR)) - == POLLERR)) { - /* One and only file descriptor returned error */ - return -1; + int err = ERRNO; + if ((result == 1) || (!ERROR_TRY_AGAIN(err))) { + /* Poll returned either success (1) or error (-1). + * Forward both to the caller. */ + if ((check_pollerr) + && ((pfd[0].revents & (POLLIN | POLLOUT | POLLERR)) + == POLLERR)) { + /* One and only file descriptor returned error */ + return -1; + } + return result; } - return result; } /* Poll returned timeout (0). */ @@ -6054,17 +6092,10 @@ push_inner(struct mg_context *ctx, } else { n = (int)send(sock, buf, (len_t)len, MSG_NOSIGNAL); err = (n < 0) ? ERRNO : 0; -#if defined(_WIN32) - if (err == WSAEWOULDBLOCK) { - err = 0; - n = 0; - } -#else if (ERROR_TRY_AGAIN(err)) { err = 0; n = 0; } -#endif if (n < 0) { /* shutdown of the socket at client side */ return -2; @@ -6343,7 +6374,7 @@ pull_inner(FILE *fp, return -2; } } else if (pollres < 0) { - /* error callint poll */ + /* error calling poll */ return -2; } else { /* pollres = 0 means timeout */ @@ -6565,15 +6596,16 @@ handle_request_stat_log(struct mg_connection *conn) #if defined(USE_HTTP2) #if defined(NO_SSL) -#error "HTTP2 requires ALPN, APLN requires SSL/TLS" +#error "HTTP2 requires ALPN, ALPN requires SSL/TLS" #endif #define USE_ALPN -#include "mod_http2.inl" +#include "http2.inl" /* Not supported with HTTP/2 */ #define HTTP1_only \ { \ if (conn->protocol_type == PROTOCOL_TYPE_HTTP2) { \ http2_must_use_http1(conn); \ + DEBUG_TRACE("%s", "must use HTTP/1.x"); \ return; \ } \ } @@ -6582,7 +6614,7 @@ handle_request_stat_log(struct mg_connection *conn) #endif -int +CIVETWEB_API int mg_read(struct mg_connection *conn, void *buf, size_t len) { if (len > INT_MAX) { @@ -6644,6 +6676,22 @@ mg_read(struct mg_connection *conn, void *buf, size_t len) if (mg_read_inner(conn, lenbuf + i, 1) != 1) { lenbuf[i] = 0; } + if ((i > 0) && (lenbuf[i] == ';')) + { + // chunk extension --> skip chars until next CR + // + // RFC 2616, 3.6.1 Chunked Transfer Coding + // (https://www.rfc-editor.org/rfc/rfc2616#page-25) + // + // chunk = chunk-size [ chunk-extension ] CRLF + // chunk-data CRLF + // ... + // chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) + do + ++conn->content_len; + while (mg_read_inner(conn, lenbuf + i, 1) == 1 && + lenbuf[i] != '\r'); + } if ((i > 0) && (lenbuf[i] == '\r') && (lenbuf[i - 1] != '\r')) { continue; @@ -6669,13 +6717,56 @@ mg_read(struct mg_connection *conn, void *buf, size_t len) conn->is_chunked = 2; return -1; } - if (chunkSize == 0) { + if (conn->is_chunked == 3) { /* try discarding trailer for keep-alive */ - conn->content_len += 2; - if ((mg_read_inner(conn, lenbuf, 2) == 2) - && (lenbuf[0] == '\r') && (lenbuf[1] == '\n')) { - conn->is_chunked = 4; + + // We found the last chunk (length 0) including the + // CRLF that terminates that chunk. Now follows a possibly + // empty trailer and a final CRLF. + // + // see RFC 2616, 3.6.1 Chunked Transfer Coding + // (https://www.rfc-editor.org/rfc/rfc2616#page-25) + // + // Chunked-Body = *chunk + // last-chunk + // trailer + // CRLF + // ... + // last-chunk = 1*("0") [ chunk-extension ] CRLF + // ... + // trailer = *(entity-header CRLF) + + int crlf_count = 2; // one CRLF already determined + + while (crlf_count < 4 && conn->is_chunked == 3) { + ++conn->content_len; + if (mg_read_inner(conn, lenbuf, 1) == 1) { + if ((crlf_count == 0 || crlf_count == 2)) { + if (lenbuf[0] == '\r') + ++crlf_count; + else + crlf_count = 0; + } + else { + // previous character was a CR + // --> next character must be LF + + if (lenbuf[0] == '\n') + ++crlf_count; + else + conn->is_chunked = 2; + } + } + else + // premature end of trailer + conn->is_chunked = 2; } + + if (conn->is_chunked == 2) + return -1; + else + conn->is_chunked = 4; + break; } @@ -6690,7 +6781,7 @@ mg_read(struct mg_connection *conn, void *buf, size_t len) } -int +CIVETWEB_API int mg_write(struct mg_connection *conn, const void *buf, size_t len) { time_t now; @@ -6771,7 +6862,7 @@ mg_write(struct mg_connection *conn, const void *buf, size_t len) /* Send a chunk, if "Transfer-Encoding: chunked" is used */ -int +CIVETWEB_API int mg_send_chunk(struct mg_connection *conn, const char *chunk, unsigned int chunk_len) @@ -6907,6 +6998,20 @@ alloc_vprintf(char **out_buf, } +static int +alloc_printf(char **out_buf, const char *fmt, ...) +{ + va_list ap; + int result; + + va_start(ap, fmt); + result = alloc_vprintf(out_buf, NULL, 0, fmt, ap); + va_end(ap); + + return result; +} + + #if defined(GCC_DIAGNOSTIC) /* Enable format-nonliteral warning again. */ #pragma GCC diagnostic pop @@ -6931,7 +7036,7 @@ mg_vprintf(struct mg_connection *conn, const char *fmt, va_list ap) } -int +CIVETWEB_API int mg_printf(struct mg_connection *conn, const char *fmt, ...) { va_list ap; @@ -6945,7 +7050,7 @@ mg_printf(struct mg_connection *conn, const char *fmt, ...) } -int +CIVETWEB_API int mg_url_decode(const char *src, int src_len, char *dst, @@ -6985,7 +7090,7 @@ url_decode_in_place(char *buf) } -int +CIVETWEB_API int mg_get_var(const char *data, size_t data_len, const char *name, @@ -6996,7 +7101,7 @@ mg_get_var(const char *data, } -int +CIVETWEB_API int mg_get_var2(const char *data, size_t data_len, const char *name, @@ -7054,7 +7159,7 @@ mg_get_var2(const char *data, /* split a string "key1=val1&key2=val2" into key/value pairs */ -int +CIVETWEB_API int mg_split_form_urlencoded(char *data, struct mg_header *form_fields, unsigned num_form_fields) @@ -7152,8 +7257,8 @@ mg_split_form_urlencoded(char *data, } -/* HCP24: some changes to compare hole var_name */ -int +/* HCP24: some changes to compare whole var_name */ +CIVETWEB_API int mg_get_cookie(const char *cookie_header, const char *var_name, char *dst, @@ -7202,13 +7307,30 @@ mg_get_cookie(const char *cookie_header, } -#if defined(USE_WEBSOCKET) || defined(USE_LUA) -static void -base64_encode(const unsigned char *src, int src_len, char *dst) +CIVETWEB_API int +mg_base64_encode(const unsigned char *src, + size_t src_len, + char *dst, + size_t *dst_len) { static const char *b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - int i, j, a, b, c; + size_t i, j; + int a, b, c; + + if (dst_len != NULL) { + /* Expected length including 0 termination: */ + /* IN 1 -> OUT 5, IN 2 -> OUT 5, IN 3 -> OUT 5, IN 4 -> OUT 9, + * IN 5 -> OUT 9, IN 6 -> OUT 9, IN 7 -> OUT 13, etc. */ + size_t expected_len = ((src_len + 2) / 3) * 4 + 1; + if (*dst_len < expected_len) { + if (*dst_len > 0) { + dst[0] = '\0'; + } + *dst_len = expected_len; + return 0; + } + } for (i = j = 0; i < src_len; i += 3) { a = src[i]; @@ -7228,11 +7350,16 @@ base64_encode(const unsigned char *src, int src_len, char *dst) dst[j++] = '='; } dst[j++] = '\0'; + + if (dst_len != NULL) { + *dst_len = (size_t)j; + } + + /* Return -1 for "OK" */ + return -1; } -#endif -#if defined(USE_LUA) static unsigned char b64reverse(char letter) { @@ -7258,46 +7385,82 @@ b64reverse(char letter) } -static int -base64_decode(const unsigned char *src, int src_len, char *dst, size_t *dst_len) +CIVETWEB_API int +mg_base64_decode(const char *src, + size_t src_len, + unsigned char *dst, + size_t *dst_len) { - int i; + size_t i; unsigned char a, b, c, d; + size_t dst_len_limit = (size_t)-1; + size_t dst_len_used = 0; - *dst_len = 0; + if (dst_len != NULL) { + dst_len_limit = *dst_len; + *dst_len = 0; + } for (i = 0; i < src_len; i += 4) { + /* Read 4 characters from BASE64 string */ a = b64reverse(src[i]); if (a >= 254) { - return i; + return (int)i; } b = b64reverse(((i + 1) >= src_len) ? 0 : src[i + 1]); if (b >= 254) { - return i + 1; + return (int)i + 1; } c = b64reverse(((i + 2) >= src_len) ? 0 : src[i + 2]); if (c == 254) { - return i + 2; + return (int)i + 2; } d = b64reverse(((i + 3) >= src_len) ? 0 : src[i + 3]); if (d == 254) { - return i + 3; + return (int)i + 3; } - dst[(*dst_len)++] = (a << 2) + (b >> 4); + /* Add first (of 3) decoded character */ + if (dst_len_used < dst_len_limit) { + dst[dst_len_used] = (a << 2) + (b >> 4); + } + dst_len_used++; + if (c != 255) { - dst[(*dst_len)++] = (b << 4) + (c >> 2); + if (dst_len_used < dst_len_limit) { + + dst[dst_len_used] = (b << 4) + (c >> 2); + } + dst_len_used++; if (d != 255) { - dst[(*dst_len)++] = (c << 6) + d; + if (dst_len_used < dst_len_limit) { + dst[dst_len_used] = (c << 6) + d; + } + dst_len_used++; } } } + + /* Add terminating zero */ + if (dst_len_used < dst_len_limit) { + dst[dst_len_used] = '\0'; + } + dst_len_used++; + if (dst_len != NULL) { + *dst_len = dst_len_used; + } + + if (dst_len_used > dst_len_limit) { + /* Out of memory */ + return 0; + } + + /* Return -1 for "OK" */ return -1; } -#endif static int @@ -7305,9 +7468,35 @@ is_put_or_delete_method(const struct mg_connection *conn) { if (conn) { const char *s = conn->request_info.request_method; - return (s != NULL) - && (!strcmp(s, "PUT") || !strcmp(s, "DELETE") - || !strcmp(s, "MKCOL") || !strcmp(s, "PATCH")); + if (s != NULL) { + /* PUT, DELETE, MKCOL, PATCH, LOCK, UNLOCK, PROPPATCH, MOVE, COPY */ + return (!strcmp(s, "PUT") || !strcmp(s, "DELETE") + || !strcmp(s, "MKCOL") || !strcmp(s, "PATCH") + || !strcmp(s, "LOCK") || !strcmp(s, "UNLOCK") + || !strcmp(s, "PROPPATCH") || !strcmp(s, "MOVE") + || !strcmp(s, "COPY")); + } + } + return 0; +} + + +static int +is_civetweb_webdav_method(const struct mg_connection *conn) +{ + /* Note: Here we only have to identify the WebDav methods that need special + * handling in the CivetWeb code - not all methods used in WebDav. In + * particular, methods used on directories (when using Windows Explorer as + * WebDav client). + */ + if (conn) { + const char *s = conn->request_info.request_method; + if (s != NULL) { + /* These are the civetweb builtin DAV methods */ + return (!strcmp(s, "PROPFIND") || !strcmp(s, "PROPPATCH") + || !strcmp(s, "LOCK") || !strcmp(s, "UNLOCK") + || !strcmp(s, "MOVE") || !strcmp(s, "COPY")); + } } return 0; } @@ -7439,8 +7628,9 @@ interpret_uri(struct mg_connection *conn, /* in/out: request (must be valid) */ struct mg_file_stat *filestat, /* out: file status structure */ int *is_found, /* out: file found (directly) */ int *is_script_resource, /* out: handled by a script? */ - int *is_websocket_request, /* out: websocket connetion? */ + int *is_websocket_request, /* out: websocket connection? */ int *is_put_or_delete_request, /* out: put/delete a file? */ + int *is_webdav_request, /* out: webdav request? */ int *is_template_text /* out: SSI file or LSP file? */ ) { @@ -7470,15 +7660,19 @@ interpret_uri(struct mg_connection *conn, /* in/out: request (must be valid) */ *is_script_resource = 0; *is_template_text = 0; - /* Step 2: Check if the request attempts to modify the file system */ + /* Step 2: Classify the request method */ + /* Step 2a: Check if the request attempts to modify the file system */ *is_put_or_delete_request = is_put_or_delete_method(conn); + /* Step 2b: Check if the request uses WebDav method that requires special + * handling */ + *is_webdav_request = is_civetweb_webdav_method(conn); /* Step 3: Check if it is a websocket request, and modify the document * root if required */ #if defined(USE_WEBSOCKET) *is_websocket_request = (conn->protocol_type == PROTOCOL_TYPE_WEBSOCKET); #if !defined(NO_FILES) - if (*is_websocket_request && conn->dom_ctx->config[WEBSOCKET_ROOT]) { + if ((*is_websocket_request) && conn->dom_ctx->config[WEBSOCKET_ROOT]) { root = conn->dom_ctx->config[WEBSOCKET_ROOT]; } #endif /* !NO_FILES */ @@ -7575,7 +7769,9 @@ interpret_uri(struct mg_connection *conn, /* in/out: request (must be valid) */ /* 8.4: If the request target is a directory, there could be * a substitute file (index.html, index.cgi, ...). */ - if (filestat->is_directory && is_uri_end_slash) { + /* But do not substitute a directory for a WebDav request */ + if (filestat->is_directory && is_uri_end_slash + && (!*is_webdav_request)) { /* Use a local copy here, since substitute_index_file will * change the content of the file status */ struct mg_file_stat tmp_filestat; @@ -7647,6 +7843,11 @@ interpret_uri(struct mg_connection *conn, /* in/out: request (must be valid) */ allow_substitute_script_subresources = !mg_strcasecmp(conn->dom_ctx->config[ALLOW_INDEX_SCRIPT_SUB_RES], "yes"); + if (*is_webdav_request) { + /* TO BE DEFINED: Should scripts handle special WebDAV methods lile + * PROPFIND for their subresources? */ + /* allow_substitute_script_subresources = 0; */ + } sep_pos = tmp_str_len; while (sep_pos > 0) { @@ -8130,7 +8331,7 @@ static const struct { {NULL, 0, NULL}}; -const char * +CIVETWEB_API const char * mg_get_builtin_mime_type(const char *path) { const char *ext; @@ -8202,7 +8403,7 @@ bin2str(char *to, const unsigned char *p, size_t len) /* Return stringified MD5 hash for list of strings. Buffer must be 33 bytes. */ -char * +CIVETWEB_API char * mg_md5(char buf[33], ...) { md5_byte_t hash[16]; @@ -8226,14 +8427,14 @@ mg_md5(char buf[33], ...) /* Check the user's password, return 1 if OK */ static int -check_password(const char *method, - const char *ha1, - const char *uri, - const char *nonce, - const char *nc, - const char *cnonce, - const char *qop, - const char *response) +check_password_digest(const char *method, + const char *ha1, + const char *uri, + const char *nonce, + const char *nc, + const char *cnonce, + const char *qop, + const char *response) { char ha2[32 + 1], expected_response[32 + 1]; @@ -8345,7 +8546,10 @@ open_auth_file(struct mg_connection *conn, /* Parsed Authorization header */ struct ah { - char *user, *uri, *cnonce, *response, *qop, *nc, *nonce; + char *user; + int type; /* 1 = basic, 2 = digest */ + char *plain_password; /* Basic only */ + char *uri, *cnonce, *response, *qop, *nc, *nonce; /* Digest only */ }; @@ -8365,8 +8569,44 @@ parse_auth_header(struct mg_connection *conn, } (void)memset(ah, 0, sizeof(*ah)); - if (((auth_header = mg_get_header(conn, "Authorization")) == NULL) - || mg_strncasecmp(auth_header, "Digest ", 7) != 0) { + auth_header = mg_get_header(conn, "Authorization"); + + if (auth_header == NULL) { + /* No Authorization header at all */ + return 0; + } + if (0 == mg_strncasecmp(auth_header, "Basic ", 6)) { + /* Basic Auth (we never asked for this, but some client may send it) */ + char *split; + const char *userpw_b64 = auth_header + 6; + size_t userpw_b64_len = strlen(userpw_b64); + size_t buf_len_r = buf_size; + if (mg_base64_decode( + userpw_b64, userpw_b64_len, (unsigned char *)buf, &buf_len_r) + != -1) { + return 0; /* decode error */ + } + split = strchr(buf, ':'); + if (!split) { + return 0; /* Format error */ + } + + /* Separate string at ':' */ + *split = 0; + + /* User name is before ':', Password is after ':' */ + ah->user = buf; + ah->type = 1; + ah->plain_password = split + 1; + + return 1; + + } else if (0 == mg_strncasecmp(auth_header, "Digest ", 7)) { + /* Digest Auth ... implemented below */ + ah->type = 2; + + } else { + /* Unknown or invalid Auth method */ return 0; } @@ -8451,15 +8691,7 @@ parse_auth_header(struct mg_connection *conn, (void)nonce; #endif - /* CGI needs it as REMOTE_USER */ - if (ah->user != NULL) { - conn->request_info.remote_user = - mg_strdup_ctx(ah->user, conn->phys_ctx); - } else { - return 0; - } - - return 1; + return (ah->user != NULL); } @@ -8594,14 +8826,32 @@ read_auth_file(struct mg_file *filep, if (!strcmp(workdata->ah.user, workdata->f_user) && !strcmp(workdata->domain, workdata->f_domain)) { - return check_password(workdata->conn->request_info.request_method, - workdata->f_ha1, - workdata->ah.uri, - workdata->ah.nonce, - workdata->ah.nc, - workdata->ah.cnonce, - workdata->ah.qop, - workdata->ah.response); + switch (workdata->ah.type) { + case 1: /* Basic */ + { + char md5[33]; + mg_md5(md5, + workdata->f_user, + ":", + workdata->domain, + ":", + workdata->ah.plain_password, + NULL); + return 0 == memcmp(workdata->f_ha1, md5, 33); + } + case 2: /* Digest */ + return check_password_digest( + workdata->conn->request_info.request_method, + workdata->f_ha1, + workdata->ah.uri, + workdata->ah.nonce, + workdata->ah.nc, + workdata->ah.cnonce, + workdata->ah.qop, + workdata->ah.response); + default: /* None/Other/Unknown */ + return 0; + } } } @@ -8627,6 +8877,10 @@ authorize(struct mg_connection *conn, struct mg_file *filep, const char *realm) return 0; } + /* CGI needs it as REMOTE_USER */ + conn->request_info.remote_user = + mg_strdup_ctx(workdata.ah.user, conn->phys_ctx); + if (realm) { workdata.domain = realm; } else { @@ -8638,7 +8892,7 @@ authorize(struct mg_connection *conn, struct mg_file *filep, const char *realm) /* Public function to check http digest authentication header */ -int +CIVETWEB_API int mg_check_digest_access_authentication(struct mg_connection *conn, const char *realm, const char *filename) @@ -8767,7 +9021,7 @@ send_authorization_request(struct mg_connection *conn, const char *realm) /* Interface function. Parameters are provided by the user, so do * at least some basic checks. */ -int +CIVETWEB_API int mg_send_digest_access_authentication_request(struct mg_connection *conn, const char *realm) { @@ -8783,42 +9037,41 @@ mg_send_digest_access_authentication_request(struct mg_connection *conn, static int is_authorized_for_put(struct mg_connection *conn) { + int ret = 0; + if (conn) { struct mg_file file = STRUCT_FILE_INITIALIZER; const char *passfile = conn->dom_ctx->config[PUT_DELETE_PASSWORDS_FILE]; - int ret = 0; if (passfile != NULL && mg_fopen(conn, passfile, MG_FOPEN_MODE_READ, &file)) { ret = authorize(conn, &file, NULL); (void)mg_fclose(&file.access); /* ignore error on read only file */ } - - return ret; } - return 0; + + DEBUG_TRACE("file write authorization: %i", ret); + return ret; } #endif -static int -modify_passwords_file(const char *fname, - const char *domain, - const char *user, - const char *pass, - const char *ha1) +CIVETWEB_API int +mg_modify_passwords_file_ha1(const char *fname, + const char *domain, + const char *user, + const char *ha1) { - int found, i; - char line[512], u[512] = "", d[512] = "", ha1buf[33], - tmp[UTF8_PATH_MAX + 8]; - FILE *fp, *fp2; - - found = 0; - fp = fp2 = NULL; + int found = 0, i, result = 1; + char line[512], u[256], d[256], h[256]; + struct stat st = {0}; + FILE *fp = NULL; + char *temp_file = NULL; + int temp_file_offs = 0; /* Regard empty password as no password - remove user record. */ - if ((pass != NULL) && (pass[0] == '\0')) { - pass = NULL; + if ((ha1 != NULL) && (ha1[0] == '\0')) { + ha1 = NULL; } /* Other arguments must not be empty */ @@ -8827,8 +9080,7 @@ modify_passwords_file(const char *fname, } /* Using the given file format, user name and domain must not contain - * ':' - */ + * the ':' character */ if (strchr(user, ':') != NULL) { return 0; } @@ -8844,7 +9096,7 @@ modify_passwords_file(const char *fname, } } if (user[i]) { - return 0; + return 0; /* user name too long */ } for (i = 0; ((i < 255) && (domain[i] != 0)); i++) { if (iscntrl((unsigned char)domain[i])) { @@ -8852,92 +9104,130 @@ modify_passwords_file(const char *fname, } } if (domain[i]) { - return 0; + return 0; /* domain name too long */ } /* The maximum length of the path to the password file is limited */ - if ((strlen(fname) + 4) >= UTF8_PATH_MAX) { + if (strlen(fname) >= UTF8_PATH_MAX) { return 0; } - /* Create a temporary file name. Length has been checked before. */ - strcpy(tmp, fname); - strcat(tmp, ".tmp"); + /* Check if the file exists, and get file size */ + if (0 == stat(fname, &st)) { + int temp_buf_len = (int)st.st_size + 1024; - /* Create the file if does not exist */ - /* Use of fopen here is OK, since fname is only ASCII */ - if ((fp = fopen(fname, "a+")) != NULL) { - (void)fclose(fp); - } - - /* Open the given file and temporary file */ - if ((fp = fopen(fname, "r")) == NULL) { - return 0; - } else if ((fp2 = fopen(tmp, "w+")) == NULL) { - fclose(fp); - return 0; - } - - /* Copy the stuff to temporary file */ - while (fgets(line, sizeof(line), fp) != NULL) { - if (sscanf(line, "%255[^:]:%255[^:]:%*s", u, d) != 2) { - continue; + /* Allocate memory (instead of using a temporary file) */ + temp_file = (char *)mg_calloc(temp_buf_len, 1); + if (!temp_file) { + /* Out of memory */ + return 0; } - u[255] = 0; - d[255] = 0; - if (!strcmp(u, user) && !strcmp(d, domain)) { - found++; - if (pass != NULL) { - mg_md5(ha1buf, user, ":", domain, ":", pass, NULL); - fprintf(fp2, "%s:%s:%s\n", user, domain, ha1buf); - } else if (ha1 != NULL) { - fprintf(fp2, "%s:%s:%s\n", user, domain, ha1); + /* File exists. Read it into a memory buffer. */ + fp = fopen(fname, "r"); + if (fp == NULL) { + /* Cannot read file. No permission? */ + mg_free(temp_file); + return 0; + } + + /* Read content and store in memory */ + while ((fgets(line, sizeof(line), fp) != NULL) + && ((temp_file_offs + 600) < temp_buf_len)) { + /* file format is "user:domain:hash\n" */ + if (sscanf(line, "%255[^:]:%255[^:]:%255s", u, d, h) != 3) { + continue; } - } else { - fprintf(fp2, "%s", line); + u[255] = 0; + d[255] = 0; + h[255] = 0; + + if (!strcmp(u, user) && !strcmp(d, domain)) { + /* Found the user: change the password hash or drop the user + */ + if ((ha1 != NULL) && (!found)) { + i = sprintf(temp_file + temp_file_offs, + "%s:%s:%s\n", + user, + domain, + ha1); + if (i < 1) { + fclose(fp); + mg_free(temp_file); + return 0; + } + temp_file_offs += i; + } + found = 1; + } else { + /* Copy existing user, including password hash */ + i = sprintf(temp_file + temp_file_offs, "%s:%s:%s\n", u, d, h); + if (i < 1) { + fclose(fp); + mg_free(temp_file); + return 0; + } + temp_file_offs += i; + } + } + fclose(fp); + } + + /* Create new file */ + fp = fopen(fname, "w"); + if (!fp) { + mg_free(temp_file); + return 0; + } + +#if !defined(_WIN32) + /* On Linux & co., restrict file read/write permissions to the owner */ + if (fchmod(fileno(fp), S_IRUSR | S_IWUSR) != 0) { + result = 0; + } +#endif + + if ((temp_file != NULL) && (temp_file_offs > 0)) { + /* Store buffered content of old file */ + if (fwrite(temp_file, 1, temp_file_offs, fp) + != (size_t)temp_file_offs) { + result = 0; } } /* If new user, just add it */ - if (!found) { - if (pass != NULL) { - mg_md5(ha1buf, user, ":", domain, ":", pass, NULL); - fprintf(fp2, "%s:%s:%s\n", user, domain, ha1buf); - } else if (ha1 != NULL) { - fprintf(fp2, "%s:%s:%s\n", user, domain, ha1); + if ((ha1 != NULL) && (!found)) { + if (fprintf(fp, "%s:%s:%s\n", user, domain, ha1) < 6) { + result = 0; } } - /* Close files */ - fclose(fp); - fclose(fp2); + /* All data written */ + if (fclose(fp) != 0) { + result = 0; + } - /* Put the temp file in place of real file */ - IGNORE_UNUSED_RESULT(remove(fname)); - IGNORE_UNUSED_RESULT(rename(tmp, fname)); - - return 1; + mg_free(temp_file); + return result; } -int +CIVETWEB_API int mg_modify_passwords_file(const char *fname, const char *domain, const char *user, const char *pass) { - return modify_passwords_file(fname, domain, user, pass, NULL); -} + char ha1buf[33]; + if ((fname == NULL) || (domain == NULL) || (user == NULL)) { + return 0; + } + if ((pass == NULL) || (pass[0] == 0)) { + return mg_modify_passwords_file_ha1(fname, domain, user, NULL); + } - -int -mg_modify_passwords_file_ha1(const char *fname, - const char *domain, - const char *user, - const char *ha1) -{ - return modify_passwords_file(fname, domain, user, NULL, ha1); + mg_md5(ha1buf, user, ":", domain, ":", pass, NULL); + return mg_modify_passwords_file_ha1(fname, domain, user, ha1buf); } @@ -8995,8 +9285,7 @@ connect_socket( const char *host, int port, /* 1..65535, or -99 for domain sockets (may be changed) */ int use_ssl, /* 0 or 1 */ - char *ebuf, - size_t ebuf_len, + struct mg_error_data *error, SOCKET *sock /* output: socket, must not be NULL */, union usa *sa /* output: socket address, must not be NULL */ ) @@ -9007,17 +9296,16 @@ connect_socket( *sock = INVALID_SOCKET; memset(sa, 0, sizeof(*sa)); - if (ebuf_len > 0) { - *ebuf = 0; - } - if (host == NULL) { - mg_snprintf(NULL, - NULL, /* No truncation check for ebuf */ - ebuf, - ebuf_len, - "%s", - "NULL host"); + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_INVALID_PARAM; + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + error->text, + error->text_buffer_size, + "%s", + "NULL host"); + } return 0; } @@ -9026,45 +9314,57 @@ connect_socket( /* Unix domain socket */ size_t hostlen = strlen(host); if (hostlen >= sizeof(sa->sun.sun_path)) { - mg_snprintf(NULL, - NULL, /* No truncation check for ebuf */ - ebuf, - ebuf_len, - "%s", - "host length exceeds limit"); + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_INVALID_PARAM; + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + error->text, + error->text_buffer_size, + "%s", + "host length exceeds limit"); + } return 0; } } else #endif if ((port <= 0) || !is_valid_port((unsigned)port)) { - mg_snprintf(NULL, - NULL, /* No truncation check for ebuf */ - ebuf, - ebuf_len, - "%s", - "invalid port"); + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_INVALID_PARAM; + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + error->text, + error->text_buffer_size, + "%s", + "invalid port"); + } return 0; } #if !defined(NO_SSL) && !defined(USE_MBEDTLS) && !defined(NO_SSL_DL) #if defined(OPENSSL_API_1_1) || defined(OPENSSL_API_3_0) if (use_ssl && (TLS_client_method == NULL)) { - mg_snprintf(NULL, - NULL, /* No truncation check for ebuf */ - ebuf, - ebuf_len, - "%s", - "SSL is not initialized"); + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_INIT_LIBRARY_FAILED; + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + error->text, + error->text_buffer_size, + "%s", + "SSL is not initialized"); + } return 0; } #else if (use_ssl && (SSLv23_client_method == NULL)) { - mg_snprintf(NULL, - NULL, /* No truncation check for ebuf */ - ebuf, - ebuf_len, - "%s", - "SSL is not initialized"); + if (error != 0) { + error->code = MG_ERROR_DATA_CODE_INIT_LIBRARY_FAILED; + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + error->text, + error->text_buffer_size, + "%s", + "SSL is not initialized"); + } return 0; } #endif /* OPENSSL_API_1_1 || OPENSSL_API_3_0*/ @@ -9072,7 +9372,6 @@ connect_socket( (void)use_ssl; #endif /* NO SSL */ - #if defined(USE_X_DOM_SOCKET) if (port == -99) { size_t hostlen = strlen(host); @@ -9107,12 +9406,15 @@ connect_socket( } if (ip_ver == 0) { - mg_snprintf(NULL, - NULL, /* No truncation check for ebuf */ - ebuf, - ebuf_len, - "%s", - "host not found"); + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_HOST_NOT_FOUND; + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + error->text, + error->text_buffer_size, + "%s", + "host not found"); + } return 0; } @@ -9131,22 +9433,30 @@ connect_socket( #endif if (*sock == INVALID_SOCKET) { - mg_snprintf(NULL, - NULL, /* No truncation check for ebuf */ - ebuf, - ebuf_len, - "socket(): %s", - strerror(ERRNO)); + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_OS_ERROR; + error->code_sub = ERRNO; + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + error->text, + error->text_buffer_size, + "socket(): %s", + strerror(ERRNO)); + } return 0; } if (0 != set_non_blocking_mode(*sock)) { - mg_snprintf(NULL, - NULL, /* No truncation check for ebuf */ - ebuf, - ebuf_len, - "Cannot set socket to non-blocking: %s", - strerror(ERRNO)); + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_OS_ERROR; + error->code_sub = ERRNO; + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + error->text, + error->text_buffer_size, + "Cannot set socket to non-blocking: %s", + strerror(ERRNO)); + } closesocket(*sock); *sock = INVALID_SOCKET; return 0; @@ -9214,13 +9524,16 @@ connect_socket( if (pollres != 1) { /* Not connected */ - mg_snprintf(NULL, - NULL, /* No truncation check for ebuf */ - ebuf, - ebuf_len, - "connect(%s:%d): timeout", - host, - port); + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_CONNECT_TIMEOUT; + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + error->text, + error->text_buffer_size, + "connect(%s:%d): timeout", + host, + port); + } closesocket(*sock); *sock = INVALID_SOCKET; return 0; @@ -9239,14 +9552,18 @@ connect_socket( if (conn_ret != 0) { /* Not connected */ - mg_snprintf(NULL, - NULL, /* No truncation check for ebuf */ - ebuf, - ebuf_len, - "connect(%s:%d): error %s", - host, - port, - strerror(sockerr)); + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_CONNECT_FAILED; + error->code_sub = ERRNO; + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + error->text, + error->text_buffer_size, + "connect(%s:%d): error %s", + host, + port, + strerror(sockerr)); + } closesocket(*sock); *sock = INVALID_SOCKET; return 0; @@ -9256,7 +9573,7 @@ connect_socket( } -int +CIVETWEB_API int mg_url_encode(const char *src, char *dst, size_t dst_len) { static const char *dont_escape = "._-$,;~()"; @@ -9285,7 +9602,7 @@ mg_url_encode(const char *src, char *dst, size_t dst_len) /* Return 0 on success, non-zero if an error occurs. */ static int -print_dir_entry(struct de *de) +print_dir_entry(struct mg_connection *conn, struct de *de) { size_t namesize, escsize, i; char *href, *esc, *p; @@ -9322,7 +9639,7 @@ print_dir_entry(struct de *de) } if (de->file.is_directory) { - mg_snprintf(de->conn, + mg_snprintf(conn, NULL, /* Buffer is big enough */ size, sizeof(size), @@ -9332,28 +9649,28 @@ print_dir_entry(struct de *de) /* We use (signed) cast below because MSVC 6 compiler cannot * convert unsigned __int64 to double. Sigh. */ if (de->file.size < 1024) { - mg_snprintf(de->conn, + mg_snprintf(conn, NULL, /* Buffer is big enough */ size, sizeof(size), "%d", (int)de->file.size); } else if (de->file.size < 0x100000) { - mg_snprintf(de->conn, + mg_snprintf(conn, NULL, /* Buffer is big enough */ size, sizeof(size), "%.1fk", (double)de->file.size / 1024.0); } else if (de->file.size < 0x40000000) { - mg_snprintf(de->conn, + mg_snprintf(conn, NULL, /* Buffer is big enough */ size, sizeof(size), "%.1fM", (double)de->file.size / 1048576); } else { - mg_snprintf(de->conn, + mg_snprintf(conn, NULL, /* Buffer is big enough */ size, sizeof(size), @@ -9374,9 +9691,8 @@ print_dir_entry(struct de *de) strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", tm); } else { mg_strlcpy(mod, "01-Jan-1970 00:00", sizeof(mod)); - mod[sizeof(mod) - 1] = '\0'; } - mg_printf(de->conn, + mg_printf(conn, "%s%s" " %s  %s\n", href, @@ -9391,28 +9707,28 @@ print_dir_entry(struct de *de) /* This function is called from send_directory() and used for - * sorting directory entries by size, or name, or modification time. - * On windows, __cdecl specification is needed in case if project is built - * with __stdcall convention. qsort always requires __cdels callback. */ -static int WINCDECL -compare_dir_entries(const void *p1, const void *p2) + * sorting directory entries by size, name, or modification time. */ +static int +compare_dir_entries(const void *p1, const void *p2, void *arg) { + const char *query_string = (const char *)(arg != NULL ? arg : ""); if (p1 && p2) { const struct de *a = (const struct de *)p1, *b = (const struct de *)p2; - const char *query_string = a->conn->request_info.query_string; int cmp_result = 0; if ((query_string == NULL) || (query_string[0] == '\0')) { query_string = "n"; } + /* Sort Directories vs Files */ if (a->file.is_directory && !b->file.is_directory) { return -1; /* Always put directories on top */ } else if (!a->file.is_directory && b->file.is_directory) { return 1; /* Always put directories on top */ - } else if (*query_string == 'n') { - cmp_result = strcmp(a->file_name, b->file_name); - } else if (*query_string == 's') { + } + + /* Sort by size or date */ + if (*query_string == 's') { cmp_result = (a->file.size == b->file.size) ? 0 : ((a->file.size > b->file.size) ? 1 : -1); @@ -9424,6 +9740,15 @@ compare_dir_entries(const void *p1, const void *p2) : -1); } + /* Sort by name: + * if (*query_string == 'n') ... + * but also sort files of same size/date by name as secondary criterion. + */ + if (cmp_result == 0) { + cmp_result = strcmp(a->file_name, b->file_name); + } + + /* For descending order, invert result */ return (query_string[1] == 'd') ? -cmp_result : cmp_result; } return 0; @@ -9459,7 +9784,6 @@ scan_directory(struct mg_connection *conn, if ((dirp = mg_opendir(conn, dir)) == NULL) { return 0; } else { - de.conn = conn; while ((dp = mg_readdir(dirp)) != NULL) { /* Do not show current dir and hidden files */ @@ -9517,7 +9841,6 @@ remove_directory(struct mg_connection *conn, const char *dir) if ((dirp = mg_opendir(conn, dir)) == NULL) { return 0; } else { - de.conn = conn; while ((dp = mg_readdir(dirp)) != NULL) { /* Do not show current dir (but show hidden files as they will @@ -9605,7 +9928,6 @@ dir_scan_callback(struct de *de, void *data) return 1; } entries[dsd->num_entries].file = de->file; - entries[dsd->num_entries].conn = de->conn; dsd->num_entries++; return 0; @@ -9705,12 +10027,13 @@ handle_directory_request(struct mg_connection *conn, const char *dir) /* Sort and print directory entries */ if (data.entries != NULL) { - qsort(data.entries, - data.num_entries, - sizeof(data.entries[0]), - compare_dir_entries); + mg_sort(data.entries, + data.num_entries, + sizeof(data.entries[0]), + compare_dir_entries, + (void *)conn->request_info.query_string); for (i = 0; i < data.num_entries; i++) { - print_dir_entry(&data.entries[i]); + print_dir_entry(conn, &data.entries[i]); mg_free(data.entries[i].file_name); } mg_free(data.entries); @@ -9727,7 +10050,8 @@ static void send_file_data(struct mg_connection *conn, struct mg_file *filep, int64_t offset, - int64_t len) + int64_t len, + int no_buffering) { char buf[MG_BUF_LEN]; int to_read, num_read, num_written; @@ -9800,8 +10124,17 @@ send_file_data(struct mg_connection *conn, "Error: Unable to access file at requested position."); } else { while (len > 0) { - /* Calculate how much to read from the file in the buffer */ - to_read = sizeof(buf); + /* Calculate how much to read from the file into the buffer. */ + /* If no_buffering is set, we should not wait until the + * CGI->Server buffer is filled, but send everything + * immediately. In theory buffering could be turned off using + * setbuf(filep->access.fp, NULL); + * setvbuf(filep->access.fp, NULL, _IONBF, 0); + * but in practice this does not work. A "Linux only" solution + * may be to use select(). The only portable way is to read byte + * by byte, but this is quite inefficient from a performance + * point of view. */ + to_read = no_buffering ? 1 : sizeof(buf); if ((int64_t)to_read > len) { to_read = (int)len; } @@ -9893,9 +10226,6 @@ handle_static_file_request(struct mg_connection *conn, int n, truncated; char gz_path[UTF8_PATH_MAX]; const char *encoding = 0; - const char *origin_hdr; - const char *cors_orig_cfg; - const char *cors1, *cors2; int is_head_request; #if defined(USE_ZLIB) @@ -10036,21 +10366,6 @@ handle_static_file_request(struct mg_connection *conn, } #endif - /* Standard CORS header */ - cors_orig_cfg = conn->dom_ctx->config[ACCESS_CONTROL_ALLOW_ORIGIN]; - origin_hdr = mg_get_header(conn, "Origin"); - if (cors_orig_cfg && *cors_orig_cfg && origin_hdr) { - /* Cross-origin resource sharing (CORS), see - * http://www.html5rocks.com/en/tutorials/cors/, - * http://www.html5rocks.com/static/images/cors_server_flowchart.png - * - - * preflight is not supported for files. */ - cors1 = "Access-Control-Allow-Origin"; - cors2 = cors_orig_cfg; - } else { - cors1 = cors2 = ""; - } - /* Prepare Etag, and Last-Modified headers. */ gmt_time_string(lm, sizeof(lm), &filep->stat.last_modified); construct_etag(etag, sizeof(etag), &filep->stat); @@ -10059,13 +10374,11 @@ handle_static_file_request(struct mg_connection *conn, mg_response_header_start(conn, conn->status_code); send_static_cache_header(conn); send_additional_header(conn); + send_cors_header(conn); mg_response_header_add(conn, "Content-Type", mime_vec.ptr, (int)mime_vec.len); - if (cors1[0] != 0) { - mg_response_header_add(conn, cors1, cors2, -1); - } mg_response_header_add(conn, "Last-Modified", lm, -1); mg_response_header_add(conn, "Etag", etag, -1); @@ -10122,14 +10435,14 @@ handle_static_file_request(struct mg_connection *conn, #endif { /* Send file directly */ - send_file_data(conn, filep, r1, cl); + send_file_data(conn, filep, r1, cl, 0); /* send static file */ } } (void)mg_fclose(&filep->access); /* ignore error on read only file */ } -int +CIVETWEB_API int mg_send_file_body(struct mg_connection *conn, const char *path) { struct mg_file file = STRUCT_FILE_INITIALIZER; @@ -10137,7 +10450,7 @@ mg_send_file_body(struct mg_connection *conn, const char *path) return -1; } fclose_on_exec(&file.access, conn); - send_file_data(conn, &file, 0, INT64_MAX); + send_file_data(conn, &file, 0, INT64_MAX, 0); /* send static file */ (void)mg_fclose(&file.access); /* Ignore errors for readonly files */ return 0; /* >= 0 for OK */ } @@ -10188,14 +10501,14 @@ handle_not_modified_static_file_request(struct mg_connection *conn, #if !defined(NO_FILESYSTEMS) -void +CIVETWEB_API void mg_send_file(struct mg_connection *conn, const char *path) { mg_send_mime_file2(conn, path, NULL, NULL); } -void +CIVETWEB_API void mg_send_mime_file(struct mg_connection *conn, const char *path, const char *mime_type) @@ -10204,7 +10517,7 @@ mg_send_mime_file(struct mg_connection *conn, } -void +CIVETWEB_API void mg_send_mime_file2(struct mg_connection *conn, const char *path, const char *mime_type, @@ -10272,7 +10585,7 @@ put_dir(struct mg_connection *conn, const char *path) /* Try to create intermediate directory */ DEBUG_TRACE("mkdir(%s)", buf); if (!mg_stat(conn, buf, &file.stat) && mg_mkdir(conn, buf, 0755) != 0) { - /* path does not exixt and can not be created */ + /* path does not exist and can not be created */ res = -2; break; } @@ -10300,7 +10613,7 @@ remove_bad_file(const struct mg_connection *conn, const char *path) } -long long +CIVETWEB_API long long mg_store_body(struct mg_connection *conn, const char *path) { char buf[MG_BUF_LEN]; @@ -10530,9 +10843,10 @@ static const struct mg_http_method_info http_methods[] = { {"LOCK", 1, 1, 0, 0, 0}, {"UNLOCK", 1, 0, 0, 0, 0}, {"PROPPATCH", 1, 1, 0, 0, 0}, + {"COPY", 1, 0, 0, 0, 0}, + {"MOVE", 1, 1, 0, 0, 0}, /* Unsupported WEBDAV Methods: */ - /* COPY, MOVE (RFC 2518) */ /* + 11 methods from RFC 3253 */ /* ORDERPATCH (RFC 3648) */ /* ACL (RFC 3744) */ @@ -10553,6 +10867,10 @@ static const struct mg_http_method_info http_methods[] = { }; +/* All method names */ +static char *all_methods = NULL; /* Built by mg_init_library */ + + static const struct mg_http_method_info * get_http_method_info(const char *method) { @@ -10686,7 +11004,7 @@ parse_http_response(char *buf, int len, struct mg_response_info *ri) ri->http_version = ri->status_text = NULL; ri->num_headers = ri->status_code = 0; - /* RFC says that all initial whitespaces should be ingored */ + /* RFC says that all initial whitespaces should be ignored */ /* This included all leading \r and \n (isspace) */ /* See table: http://www.cplusplus.com/reference/cctype/ */ while ((len > 0) && isspace((unsigned char)*buf)) { @@ -10764,7 +11082,6 @@ parse_http_response(char *buf, int len, struct mg_response_info *ri) buf++; } while (isspace((unsigned char)*buf)); - /* Parse all HTTP headers */ ri->num_headers = parse_http_headers(&buf, ri->http_headers); if (ri->num_headers < 0) { @@ -11270,7 +11587,7 @@ handle_cgi_request(struct mg_connection *conn, size_t buflen; int headers_len, data_len, i, truncated; int fdin[2] = {-1, -1}, fdout[2] = {-1, -1}, fderr[2] = {-1, -1}; - const char *status, *status_text, *connection_state; + const char *status, *status_text; char *pbuf, dir[UTF8_PATH_MAX], *p; struct mg_request_info ri; struct cgi_environment blk; @@ -11278,6 +11595,8 @@ handle_cgi_request(struct mg_connection *conn, struct mg_file fout = STRUCT_FILE_INITIALIZER; pid_t pid = (pid_t)-1; struct process_control_data *proc = NULL; + char *cfg_buffering = conn->dom_ctx->config[CGI_BUFFERING + cgi_config_idx]; + int no_buffering = 0; #if defined(USE_TIMERS) double cgi_timeout; @@ -11289,8 +11608,12 @@ handle_cgi_request(struct mg_connection *conn, cgi_timeout = atof(config_options[REQUEST_TIMEOUT].default_value) * 0.001; } - #endif + if (cfg_buffering != NULL) { + if (!mg_strcasecmp(cfg_buffering, "no")) { + no_buffering = 1; + } + } buf = NULL; buflen = conn->phys_ctx->max_request_size; @@ -11512,9 +11835,8 @@ handle_cgi_request(struct mg_connection *conn, } else { conn->status_code = 200; } - connection_state = - get_header(ri.http_headers, ri.num_headers, "Connection"); - if (!header_has_option(connection_state, "keep-alive")) { + + if (!should_keep_alive(conn)) { conn->must_close = 1; } @@ -11539,7 +11861,7 @@ handle_cgi_request(struct mg_connection *conn, /* Read the rest of CGI output and send to the client */ DEBUG_TRACE("CGI: %s", "forward all data"); - send_file_data(conn, &fout, 0, INT64_MAX); + send_file_data(conn, &fout, 0, INT64_MAX, no_buffering); /* send CGI data */ DEBUG_TRACE("CGI: %s", "all data sent"); done: @@ -11585,7 +11907,7 @@ done: #if !defined(NO_FILES) static void -mkcol(struct mg_connection *conn, const char *path) +dav_mkcol(struct mg_connection *conn, const char *path) { int rc, body_len; struct de de; @@ -11623,9 +11945,8 @@ mkcol(struct mg_connection *conn, const char *path) } rc = mg_mkdir(conn, path, 0755); - + DEBUG_TRACE("mkdir %s: %i", path, rc); if (rc == 0) { - /* Create 201 "Created" response */ mg_response_header_start(conn, 201); send_static_cache_header(conn); @@ -11634,21 +11955,196 @@ mkcol(struct mg_connection *conn, const char *path) /* Send all headers - there is no body */ mg_response_header_send(conn); - } else { - if (errno == EEXIST) { - mg_send_http_error( - conn, 405, "Error: mkcol(%s): %s", path, strerror(ERRNO)); - } else if (errno == EACCES) { - mg_send_http_error( - conn, 403, "Error: mkcol(%s): %s", path, strerror(ERRNO)); - } else if (errno == ENOENT) { - mg_send_http_error( - conn, 409, "Error: mkcol(%s): %s", path, strerror(ERRNO)); - } else { - mg_send_http_error( - conn, 500, "fopen(%s): %s", path, strerror(ERRNO)); + int http_status = 500; + switch (errno) { + case EEXIST: + http_status = 405; + break; + case EACCES: + http_status = 403; + break; + case ENOENT: + http_status = 409; + break; } + + mg_send_http_error(conn, + http_status, + "Error processing %s: %s", + path, + strerror(ERRNO)); + } +} + + +/* Forward decrlaration */ +static int get_uri_type(const char *uri); +static const char * +get_rel_url_at_current_server(const char *uri, + const struct mg_connection *conn); + + +static void +dav_move_file(struct mg_connection *conn, const char *path, int do_copy) +{ + const char *overwrite_hdr; + const char *destination_hdr; + const char *root; + int rc, dest_uri_type; + int http_status = 400; + int do_overwrite = 0; + int destination_ok = 0; + char dest_path[UTF8_PATH_MAX]; + struct mg_file_stat ignored; + + if (conn == NULL) { + return; + } + + root = conn->dom_ctx->config[DOCUMENT_ROOT]; + overwrite_hdr = mg_get_header(conn, "Overwrite"); + destination_hdr = mg_get_header(conn, "Destination"); + if ((overwrite_hdr != NULL) && (toupper(overwrite_hdr[0]) == 'T')) { + do_overwrite = 1; + } + + if ((destination_hdr == NULL) || (destination_hdr[0] == 0)) { + mg_send_http_error(conn, 400, "%s", "Missing destination"); + return; + } + + if (root != NULL) { + char *local_dest = NULL; + dest_uri_type = get_uri_type(destination_hdr); + if (dest_uri_type == 2) { + local_dest = mg_strdup_ctx(destination_hdr, conn->phys_ctx); + } else if ((dest_uri_type == 3) || (dest_uri_type == 4)) { + const char *h = + get_rel_url_at_current_server(destination_hdr, conn); + if (h) { + int len = (int)strlen(h); + local_dest = mg_malloc_ctx(len + 1, conn->phys_ctx); + mg_url_decode(h, len, local_dest, len + 1, 0); + } + } + if (local_dest != NULL) { + remove_dot_segments(local_dest); + if (local_dest[0] == '/') { + int trunc_check = 0; + mg_snprintf(conn, + &trunc_check, + dest_path, + sizeof(dest_path), + "%s/%s", + root, + local_dest); + if (trunc_check == 0) { + destination_ok = 1; + } + } + mg_free(local_dest); + } + } + + if (!destination_ok) { + mg_send_http_error(conn, 502, "%s", "Illegal destination"); + return; + } + + /* Check now if this file exists */ + if (mg_stat(conn, dest_path, &ignored)) { + /* File exists */ + if (do_overwrite) { + /* Overwrite allowed: delete the file first */ + if (0 != remove(dest_path)) { + /* No overwrite: return error */ + mg_send_http_error(conn, + 403, + "Cannot overwrite file: %s", + dest_path); + return; + } + } else { + /* No overwrite: return error */ + mg_send_http_error(conn, + 412, + "Destination already exists: %s", + dest_path); + return; + } + } + + /* Copy / Move / Rename operation. */ + DEBUG_TRACE("%s %s to %s", (do_copy ? "copy" : "move"), path, dest_path); +#if defined(_WIN32) + { + /* For Windows, we need to convert from UTF-8 to UTF-16 first. */ + wchar_t wSource[UTF16_PATH_MAX]; + wchar_t wDest[UTF16_PATH_MAX]; + BOOL ok; + + path_to_unicode(conn, path, wSource, ARRAY_SIZE(wSource)); + path_to_unicode(conn, dest_path, wDest, ARRAY_SIZE(wDest)); + if (do_copy) { + ok = CopyFileW(wSource, wDest, do_overwrite ? FALSE : TRUE); + } else { + ok = MoveFileExW(wSource, + wDest, + do_overwrite ? MOVEFILE_REPLACE_EXISTING : 0); + } + if (ok) { + rc = 0; + } else { + DWORD lastErr = GetLastError(); + if (lastErr == ERROR_ALREADY_EXISTS) { + mg_send_http_error(conn, + 412, + "Destination already exists: %s", + dest_path); + return; + } + rc = -1; + http_status = 400; + } + } + +#else + { + /* Linux uses already UTF-8, we don't need to convert file names. */ + + if (do_copy) { + /* TODO: COPY for Linux. */ + mg_send_http_error(conn, 403, "%s", "COPY forbidden"); + return; + } + + rc = rename(path, dest_path); + if (rc) { + switch (errno) { + case EEXIST: + http_status = 412; + break; + case EACCES: + http_status = 403; + break; + case ENOENT: + http_status = 409; + break; + } + } + } +#endif + + if (rc == 0) { + /* Create 204 "No Content" response */ + mg_response_header_start(conn, 204); + mg_response_header_add(conn, "Content-Length", "0", -1); + + /* Send all headers - there is no body */ + mg_response_header_send(conn); + } else { + mg_send_http_error(conn, http_status, "Operation failed"); } } @@ -11665,6 +12161,8 @@ put_file(struct mg_connection *conn, const char *path) return; } + DEBUG_TRACE("store %s", path); + if (mg_stat(conn, path, &file.stat)) { /* File already exists */ conn->status_code = 200; @@ -11735,7 +12233,7 @@ put_file(struct mg_connection *conn, const char *path) } /* A file should be created or overwritten. */ - /* Currently CivetWeb does not nead read+write access. */ + /* Currently CivetWeb does not need read+write access. */ if (!mg_fopen(conn, path, MG_FOPEN_MODE_WRITE, &file) || file.access.fp == NULL) { (void)mg_fclose(&file.access); @@ -11752,7 +12250,13 @@ put_file(struct mg_connection *conn, const char *path) r1 = r2 = 0; if ((range != NULL) && parse_range_header(range, &r1, &r2) > 0) { conn->status_code = 206; /* Partial content */ - fseeko(file.access.fp, r1, SEEK_SET); + if (0 != fseeko(file.access.fp, r1, SEEK_SET)) { + mg_send_http_error(conn, + 500, + "Error: Internal error processing file %s", + path); + return; + } } if (!forward_body_data(conn, file.access.fp, INVALID_SOCKET, NULL)) { @@ -11794,6 +12298,8 @@ delete_file(struct mg_connection *conn, const char *path) return; } + DEBUG_TRACE("delete %s", path); + if (de.file.is_directory) { if (remove_directory(conn, path)) { /* Delete is successful: Return 204 without content. */ @@ -11881,7 +12387,7 @@ do_ssi_include(struct mg_connection *conn, } else if ((sscanf(tag, " file=\"%511[^\"]\"", file_name) == 1) || (sscanf(tag, " \"%511[^\"]\"", file_name) == 1)) { - /* File name is relative to the currect document */ + /* File name is relative to the current document */ file_name[511] = 0; (void)mg_snprintf(conn, &truncated, path, sizeof(path), "%s", ssi); @@ -11920,7 +12426,7 @@ do_ssi_include(struct mg_connection *conn, > 0) { send_ssi_file(conn, path, &file, include_level + 1); } else { - send_file_data(conn, &file, 0, INT64_MAX); + send_file_data(conn, &file, 0, INT64_MAX, 0); /* send static file */ } (void)mg_fclose(&file.access); /* Ignore errors for readonly files */ } @@ -11944,7 +12450,7 @@ do_ssi_exec(struct mg_connection *conn, char *tag) cmd, strerror(ERRNO)); } else { - send_file_data(conn, &file, 0, INT64_MAX); + send_file_data(conn, &file, 0, INT64_MAX, 0); /* send static file */ pclose(file.access.fp); } } @@ -12080,22 +12586,11 @@ handle_ssi_file_request(struct mg_connection *conn, { char date[64]; time_t curtime = time(NULL); - const char *cors_orig_cfg; - const char *cors1, *cors2; if ((conn == NULL) || (path == NULL) || (filep == NULL)) { return; } - cors_orig_cfg = conn->dom_ctx->config[ACCESS_CONTROL_ALLOW_ORIGIN]; - if (cors_orig_cfg && *cors_orig_cfg && mg_get_header(conn, "Origin")) { - /* Cross-origin resource sharing (CORS). */ - cors1 = "Access-Control-Allow-Origin"; - cors2 = cors_orig_cfg; - } else { - cors1 = cors2 = ""; - } - if (!mg_fopen(conn, path, MG_FOPEN_MODE_READ, filep)) { /* File exists (precondition for calling this function), * but can not be opened by the server. */ @@ -12115,10 +12610,8 @@ handle_ssi_file_request(struct mg_connection *conn, mg_response_header_start(conn, 200); send_no_cache_header(conn); send_additional_header(conn); + send_cors_header(conn); mg_response_header_add(conn, "Content-Type", "text/html", -1); - if (cors1[0]) { - mg_response_header_add(conn, cors1, cors2, -1); - } mg_response_header_send(conn); /* Header sent, now send body */ @@ -12133,7 +12626,7 @@ handle_ssi_file_request(struct mg_connection *conn, static void send_options(struct mg_connection *conn) { - if (!conn) { + if (!conn || !all_methods) { return; } @@ -12143,13 +12636,10 @@ send_options(struct mg_connection *conn) mg_response_header_start(conn, 200); mg_response_header_add(conn, "Content-Type", "text/html", -1); + if (conn->protocol_type == PROTOCOL_TYPE_HTTP1) { /* Use the same as before */ - mg_response_header_add( - conn, - "Allow", - "GET, POST, HEAD, CONNECT, PUT, DELETE, OPTIONS, PROPFIND, MKCOL", - -1); + mg_response_header_add(conn, "Allow", all_methods, -1); mg_response_header_add(conn, "DAV", "1", -1); } else { /* TODO: Check this later for HTTP/2 */ @@ -12167,34 +12657,37 @@ print_props(struct mg_connection *conn, const char *name, struct mg_file_stat *filep) { - size_t href_size, i, j; - int len; - char *href, mtime[64]; + size_t i; + char mtime[64]; + char link_buf[UTF8_PATH_MAX * 2]; /* Path + server root */ + char *link_concat; + size_t link_concat_len; if ((conn == NULL) || (uri == NULL) || (name == NULL) || (filep == NULL)) { return 0; } - /* Estimate worst case size for encoding */ - href_size = (strlen(uri) + strlen(name)) * 3 + 1; - href = (char *)mg_malloc(href_size); - if (href == NULL) { + + link_concat_len = strlen(uri) + strlen(name) + 1; + link_concat = mg_malloc_ctx(link_concat_len, conn->phys_ctx); + if (!link_concat) { return 0; } - len = mg_url_encode(uri, href, href_size); - if (len >= 0) { - /* Append an extra string */ - mg_url_encode(name, href + len, href_size - (size_t)len); - } - /* Directory separator should be preserved. */ - for (i = j = 0; href[i]; j++) { - if (!strncmp(href + i, "%2f", 3)) { - href[j] = '/'; - i += 3; - } else { - href[j] = href[i++]; - } - } - href[j] = '\0'; + strcpy(link_concat, uri); + strcat(link_concat, name); + + /* Get full link used in request */ + mg_construct_local_link( + conn, link_buf, sizeof(link_buf), NULL, 0, link_concat); + + /* + OutputDebugStringA("print_props:\n uri: "); + OutputDebugStringA(uri); + OutputDebugStringA("\n name: "); + OutputDebugStringA(name); + OutputDebugStringA("\n link: "); + OutputDebugStringA(link_buf); + OutputDebugStringA("\n"); + */ gmt_time_string(mtime, sizeof(mtime), &filep->last_modified); mg_printf(conn, @@ -12205,15 +12698,40 @@ print_props(struct mg_connection *conn, "%s" "%" INT64_FMT "" "%s" - "" - "HTTP/1.1 200 OK" - "" - "\n", - href, + "", + link_buf, filep->is_directory ? "" : "", filep->size, mtime); - mg_free(href); + + for (i = 0; i < NUM_WEBDAV_LOCKS; i++) { + struct twebdav_lock *dav_lock = conn->phys_ctx->webdav_lock; + if (!strcmp(dav_lock[i].path, link_buf)) { + mg_printf(conn, + "" + "" + "" + "0" + "%s" + "Second-%u" + "" + "%s" + "" + "\n", + dav_lock[i].user, + (unsigned)LOCK_DURATION_S, + dav_lock[i].token); + } + } + + mg_printf(conn, + "" + "" + "HTTP/1.1 200 OK" + "" + "\n"); + + mg_free(link_concat); return 1; } @@ -12238,22 +12756,20 @@ handle_propfind(struct mg_connection *conn, struct mg_file_stat *filep) { const char *depth = mg_get_header(conn, "Depth"); - char date[64]; - time_t curtime = time(NULL); - - gmt_time_string(date, sizeof(date), &curtime); if (!conn || !path || !filep || !conn->dom_ctx) { return; } - conn->must_close = 1; - /* return 207 "Multi-Status" */ + conn->must_close = 1; mg_response_header_start(conn, 207); send_static_cache_header(conn); send_additional_header(conn); - mg_response_header_add(conn, "Content-Type", "text/xml; charset=utf-8", -1); + mg_response_header_add(conn, + "Content-Type", + "application/xml; charset=utf-8", + -1); mg_response_header_send(conn); /* Content */ @@ -12275,9 +12791,207 @@ handle_propfind(struct mg_connection *conn, mg_printf(conn, "%s\n", ""); } + + +static void +dav_lock_file(struct mg_connection *conn, const char *path) +{ + /* internal function - therefore conn is assumed to be valid */ + char link_buf[UTF8_PATH_MAX * 2]; /* Path + server root */ + uint64_t new_locktime; + int lock_index = -1; + int i; + uint64_t LOCK_DURATION_NS = + (uint64_t)(LOCK_DURATION_S) * (uint64_t)1000000000; + struct twebdav_lock *dav_lock = conn->phys_ctx->webdav_lock; + + if (!path || !conn->dom_ctx || !conn->request_info.remote_user) { + return; + } + mg_get_request_link(conn, link_buf, sizeof(link_buf)); + + /* const char *refresh = mg_get_header(conn, "If"); */ + /* Link refresh should have an "If" header: + * http://www.webdav.org/specs/rfc2518.html#n-example---refreshing-a-write-lock + * But it seems Windows Explorer does not send them. + */ + + mg_lock_context(conn->phys_ctx); + new_locktime = mg_get_current_time_ns(); + + /* Find a slot for a lock */ + while (lock_index < 0) { + /* find existing lock */ + for (i = 0; i < NUM_WEBDAV_LOCKS; i++) { + if (!strcmp(dav_lock[i].path, link_buf)) { + if (!strcmp(conn->request_info.remote_user, dav_lock[i].user)) { + /* locked by the same user */ + dav_lock[i].locktime = new_locktime; + lock_index = i; + break; + } else { + /* already locked by someone else */ + if (new_locktime + > (dav_lock[i].locktime + LOCK_DURATION_NS)) { + /* Lock expired */ + dav_lock[i].path[0] = 0; + } else { + /* Lock still valid */ + mg_unlock_context(conn->phys_ctx); + mg_send_http_error(conn, 423, "%s", "Already locked"); + return; + } + } + } + } + + /* create new lock token */ + for (i = 0; i < NUM_WEBDAV_LOCKS; i++) { + if (dav_lock[i].path[0] == 0) { + char s[32]; + dav_lock[i].locktime = mg_get_current_time_ns(); + sprintf(s, "%" UINT64_FMT, (uint64_t)dav_lock[i].locktime); + mg_md5(dav_lock[i].token, + link_buf, + "\x01", + s, + "\x01", + conn->request_info.remote_user, + NULL); + mg_strlcpy(dav_lock[i].path, + link_buf, + sizeof(dav_lock[i].path)); + mg_strlcpy(dav_lock[i].user, + conn->request_info.remote_user, + sizeof(dav_lock[i].user)); + lock_index = i; + break; + } + } + if (lock_index < 0) { + /* too many locks. Find oldest lock */ + uint64_t oldest_locktime = dav_lock[0].locktime; + lock_index = 0; + for (i = 1; i < NUM_WEBDAV_LOCKS; i++) { + if (dav_lock[i].locktime < oldest_locktime) { + oldest_locktime = dav_lock[i].locktime; + lock_index = i; + } + } + /* invalidate oldest lock */ + dav_lock[lock_index].path[0] = 0; + } + } + mg_unlock_context(conn->phys_ctx); + + /* return 200 "OK" */ + conn->must_close = 1; + mg_response_header_start(conn, 200); + send_static_cache_header(conn); + send_additional_header(conn); + mg_response_header_add(conn, + "Content-Type", + "application/xml; charset=utf-8", + -1); + mg_response_header_add(conn, "Lock-Token", dav_lock[lock_index].token, -1); + mg_response_header_send(conn); + + /* Content */ + mg_printf(conn, + "" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " %s\n" + " \n" + " Second-%u\n" + " %s\n" + " \n" + " %s\n" + " \n" + " \n" + " \n" + " \n", + dav_lock[lock_index].user, + (LOCK_DURATION_S), + dav_lock[lock_index].token, + dav_lock[lock_index].path); +} + + +static void +dav_unlock_file(struct mg_connection *conn, const char *path) +{ + /* internal function - therefore conn is assumed to be valid */ + char link_buf[UTF8_PATH_MAX * 2]; /* Path + server root */ + struct twebdav_lock *dav_lock = conn->phys_ctx->webdav_lock; + int lock_index; + + if (!path || !conn->dom_ctx || !conn->request_info.remote_user) { + return; + } + + mg_get_request_link(conn, link_buf, sizeof(link_buf)); + + mg_lock_context(conn->phys_ctx); + /* find existing lock */ + for (lock_index = 0; lock_index < NUM_WEBDAV_LOCKS; lock_index++) { + if (!strcmp(dav_lock[lock_index].path, link_buf)) { + /* Success: return 204 "No Content" */ + mg_unlock_context(conn->phys_ctx); + conn->must_close = 1; + mg_response_header_start(conn, 204); + mg_response_header_send(conn); + return; + } + } + mg_unlock_context(conn->phys_ctx); + + /* Error: Cannot unlock a resource that is not locked */ + mg_send_http_error(conn, 423, "%s", "Lock not found"); +} + + +static void +dav_proppatch(struct mg_connection *conn, const char *path) +{ + char link_buf[UTF8_PATH_MAX * 2]; /* Path + server root */ + + if (!conn || !path || !conn->dom_ctx) { + return; + } + + /* return 207 "Multi-Status" */ + conn->must_close = 1; + mg_response_header_start(conn, 207); + send_static_cache_header(conn); + send_additional_header(conn); + mg_response_header_add(conn, + "Content-Type", + "application/xml; charset=utf-8", + -1); + mg_response_header_send(conn); + + mg_get_request_link(conn, link_buf, sizeof(link_buf)); + + /* Content */ + mg_printf(conn, + "" + "\n" + "\n%s\n", + link_buf); + mg_printf(conn, + "HTTP/1.1 403 " + "Forbidden\n"); + mg_printf(conn, "%s\n", ""); +} #endif -void + +CIVETWEB_API void mg_lock_connection(struct mg_connection *conn) { if (conn) { @@ -12285,7 +12999,8 @@ mg_lock_connection(struct mg_connection *conn) } } -void + +CIVETWEB_API void mg_unlock_connection(struct mg_connection *conn) { if (conn) { @@ -12293,7 +13008,8 @@ mg_unlock_connection(struct mg_connection *conn) } } -void + +CIVETWEB_API void mg_lock_context(struct mg_context *ctx) { if (ctx && (ctx->context_type == CONTEXT_SERVER)) { @@ -12301,7 +13017,8 @@ mg_lock_context(struct mg_context *ctx) } } -void + +CIVETWEB_API void mg_unlock_context(struct mg_context *ctx) { if (ctx && (ctx->context_type == CONTEXT_SERVER)) { @@ -12321,16 +13038,21 @@ mg_unlock_context(struct mg_context *ctx) #if defined(USE_WEBSOCKET) #if !defined(NO_SSL_DL) +#if !defined(OPENSSL_API_3_0) #define SHA_API static #include "sha1.inl" #endif +#endif static int send_websocket_handshake(struct mg_connection *conn, const char *websock_key) { static const char *magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; char buf[100], sha[20], b64_sha[sizeof(sha) * 2]; + size_t dst_len = sizeof(b64_sha); +#if !defined(OPENSSL_API_3_0) SHA_CTX sha_ctx; +#endif int truncated; /* Calculate Sec-WebSocket-Accept reply from Sec-WebSocket-Key. */ @@ -12342,10 +13064,19 @@ send_websocket_handshake(struct mg_connection *conn, const char *websock_key) DEBUG_TRACE("%s", "Send websocket handshake"); +#if defined(OPENSSL_API_3_0) + EVP_Digest((unsigned char *)buf, + (uint32_t)strlen(buf), + (unsigned char *)sha, + NULL, + EVP_get_digestbyname("sha1"), + NULL); +#else SHA1_Init(&sha_ctx); SHA1_Update(&sha_ctx, (unsigned char *)buf, (uint32_t)strlen(buf)); SHA1_Final((unsigned char *)sha, &sha_ctx); - base64_encode((unsigned char *)sha, sizeof(sha), b64_sha); +#endif + mg_base64_encode((unsigned char *)sha, sizeof(sha), b64_sha, &dst_len); mg_printf(conn, "HTTP/1.1 101 Switching Protocols\r\n" "Upgrade: websocket\r\n" @@ -12411,7 +13142,6 @@ read_websocket(struct mg_connection *conn, unsigned char mem[4096]; unsigned char mop; /* mask flag and opcode */ - /* Variables used for connection monitoring */ double timeout = -1.0; int enable_ping_pong = 0; @@ -12592,7 +13322,6 @@ read_websocket(struct mg_connection *conn, break; } - } else { /* Exit the loop if callback signals to exit (server side), * or "connection close" opcode received (client side). */ @@ -12817,7 +13546,7 @@ mg_websocket_write_exec(struct mg_connection *conn, header[0] = 0xC0u | (unsigned char)((unsigned)opcode & 0xf); conn->websocket_deflate_state.avail_in = (uInt)dataLen; conn->websocket_deflate_state.next_in = (unsigned char *)data; - deflated_size = (Bytef *)compressBound((uLong)dataLen); + deflated_size = (size_t)compressBound((uLong)dataLen); deflated = mg_calloc(deflated_size, sizeof(Bytef)); if (deflated == NULL) { mg_cry_internal( @@ -12891,7 +13620,8 @@ mg_websocket_write_exec(struct mg_connection *conn, return retval; } -int + +CIVETWEB_API int mg_websocket_write(struct mg_connection *conn, int opcode, const char *data, @@ -12927,7 +13657,7 @@ mask_data(const char *in, size_t in_len, uint32_t masking_key, char *out) } -int +CIVETWEB_API int mg_websocket_client_write(struct mg_connection *conn, int opcode, const char *data, @@ -13019,7 +13749,7 @@ handle_websocket_request(struct mg_connection *conn, return; } - /* Step 1.3: Could check for "Host", but we do not really nead this + /* Step 1.3: Could check for "Host", but we do not really need this * value for anything, so just ignore it. */ /* Step 2: If a callback is responsible, call it. */ @@ -13030,28 +13760,35 @@ handle_websocket_request(struct mg_connection *conn, "Sec-WebSocket-Protocol", protocols, 64); + if ((nbSubprotocolHeader > 0) && subprotocols) { - int cnt = 0; - int idx; - unsigned long len; + + int headerNo, idx; + size_t len; const char *sep, *curSubProtocol, *acceptedWebSocketSubprotocol = NULL; - /* look for matching subprotocol */ - do { - const char *protocol = protocols[cnt]; + for (headerNo = 0; headerNo < nbSubprotocolHeader; headerNo++) { + /* There might be multiple headers ... */ + const char *protocol = protocols[headerNo]; + curSubProtocol = protocol; - do { - sep = strchr(protocol, ','); - curSubProtocol = protocol; - len = sep ? (unsigned long)(sep - protocol) - : (unsigned long)strlen(protocol); - while (sep && isspace((unsigned char)*++sep)) - ; // ignore leading whitespaces - protocol = sep; + /* ... and in every header there might be a , separated list */ + while (!acceptedWebSocketSubprotocol && (*curSubProtocol)) { + + while ((*curSubProtocol == ' ') || (*curSubProtocol == ',')) + curSubProtocol++; + sep = strchr(curSubProtocol, ','); + if (sep) { + len = (size_t)(sep - curSubProtocol); + } else { + len = strlen(curSubProtocol); + } for (idx = 0; idx < subprotocols->nb_subprotocols; idx++) { + // COMPARE: curSubProtocol == + // subprotocols->subprotocols[idx] if ((strlen(subprotocols->subprotocols[idx]) == len) && (strncmp(curSubProtocol, subprotocols->subprotocols[idx], @@ -13062,38 +13799,12 @@ handle_websocket_request(struct mg_connection *conn, break; } } - } while (sep && !acceptedWebSocketSubprotocol); - } while (++cnt < nbSubprotocolHeader - && !acceptedWebSocketSubprotocol); + curSubProtocol += len; + } + } conn->request_info.acceptedWebSocketSubprotocol = acceptedWebSocketSubprotocol; - - } else if (nbSubprotocolHeader > 0) { - /* keep legacy behavior */ - const char *protocol = protocols[0]; - - /* The protocol is a comma separated list of names. */ - /* The server must only return one value from this list. */ - /* First check if it is a list or just a single value. */ - const char *sep = strrchr(protocol, ','); - if (sep == NULL) { - /* Just a single protocol -> accept it. */ - conn->request_info.acceptedWebSocketSubprotocol = protocol; - } else { - /* Multiple protocols -> accept the last one. */ - /* This is just a quick fix if the client offers multiple - * protocols. The handler should have a list of accepted - * protocols on his own - * and use it to select one protocol among those the client - * has - * offered. - */ - while (isspace((unsigned char)*++sep)) { - ; /* ignore leading whitespaces */ - } - conn->request_info.acceptedWebSocketSubprotocol = sep; - } } #if defined(USE_ZLIB) && defined(MG_EXPERIMENTAL_INTERFACES) @@ -13120,7 +13831,7 @@ handle_websocket_request(struct mg_connection *conn, conn->dom_ctx->config[LUA_WEBSOCKET_EXTENSIONS], path); } - if (lua_websock) { + if (lua_websock > 0) { /* Step 3.2: Lua is responsible: call it. */ conn->lua_websocket_state = lua_websocket_new(path, conn); if (!conn->lua_websocket_state) { @@ -13195,30 +13906,40 @@ handle_websocket_request(struct mg_connection *conn, static int should_switch_to_protocol(const struct mg_connection *conn) { - const char *upgrade, *connection; + const char *connection_headers[8]; + const char *upgrade_to; + int connection_header_count, i, should_upgrade; - /* A websocket protocoll has the following HTTP headers: + /* A websocket protocol has the following HTTP headers: * * Connection: Upgrade * Upgrade: Websocket + * + * It seems some clients use multiple headers: + * see https://github.com/civetweb/civetweb/issues/1083 */ - - connection = mg_get_header(conn, "Connection"); - if (connection == NULL) { - return PROTOCOL_TYPE_HTTP1; + connection_header_count = get_req_headers(&conn->request_info, + "Connection", + connection_headers, + 8); + should_upgrade = 0; + for (i = 0; i < connection_header_count; i++) { + if (0 != mg_strcasestr(connection_headers[i], "upgrade")) { + should_upgrade = 1; + } } - if (!mg_strcasestr(connection, "upgrade")) { + if (!should_upgrade) { return PROTOCOL_TYPE_HTTP1; } - upgrade = mg_get_header(conn, "Upgrade"); - if (upgrade == NULL) { + upgrade_to = mg_get_header(conn, "Upgrade"); + if (upgrade_to == NULL) { /* "Connection: Upgrade" without "Upgrade" Header --> Error */ return -1; } /* Upgrade to ... */ - if (0 != mg_strcasestr(upgrade, "websocket")) { + if (0 != mg_strcasestr(upgrade_to, "websocket")) { /* The headers "Host", "Sec-WebSocket-Key", "Sec-WebSocket-Protocol" and * "Sec-WebSocket-Version" are also required. * Don't check them here, since even an unsupported websocket protocol @@ -13227,7 +13948,7 @@ should_switch_to_protocol(const struct mg_connection *conn) */ return PROTOCOL_TYPE_WEBSOCKET; /* Websocket */ } - if (0 != mg_strcasestr(upgrade, "h2")) { + if (0 != mg_strcasestr(upgrade_to, "h2")) { return PROTOCOL_TYPE_HTTP2; /* Websocket */ } @@ -13387,7 +14108,7 @@ set_throttle(const char *spec, const union usa *rsa, const char *uri) } -/* The mg_upload function is superseeded by mg_handle_form_request. */ +/* The mg_upload function is superseded by mg_handle_form_request. */ #include "handle_form.inl" @@ -13736,7 +14457,7 @@ mg_set_handler_type(struct mg_context *phys_ctx, } -void +CIVETWEB_API void mg_set_request_handler(struct mg_context *ctx, const char *uri, mg_request_handler handler, @@ -13758,7 +14479,7 @@ mg_set_request_handler(struct mg_context *ctx, } -void +CIVETWEB_API void mg_set_websocket_handler(struct mg_context *ctx, const char *uri, mg_websocket_connect_handler connect_handler, @@ -13778,7 +14499,7 @@ mg_set_websocket_handler(struct mg_context *ctx, } -void +CIVETWEB_API void mg_set_websocket_handler_with_subprotocols( struct mg_context *ctx, const char *uri, @@ -13808,7 +14529,7 @@ mg_set_websocket_handler_with_subprotocols( } -void +CIVETWEB_API void mg_set_auth_handler(struct mg_context *ctx, const char *uri, mg_authorization_handler handler, @@ -13982,7 +14703,7 @@ handle_request(struct mg_connection *conn) int uri_len, ssl_index; int is_found = 0, is_script_resource = 0, is_websocket_request = 0, is_put_or_delete_request = 0, is_callback_resource = 0, - is_template_text_file = 0; + is_template_text_file = 0, is_webdav_request = 0; int i; struct mg_file file = STRUCT_FILE_INITIALIZER; mg_request_handler callback_handler = NULL; @@ -14036,11 +14757,11 @@ handle_request(struct mg_connection *conn) /* 1.3. decode url (if config says so) */ if (should_decode_url(conn)) { - mg_url_decode( - ri->local_uri, uri_len, (char *)ri->local_uri, uri_len + 1, 0); + url_decode_in_place((char *)ri->local_uri); } - /* URL decode the query-string only if explicity set in the configuration */ + /* URL decode the query-string only if explicitly set in the configuration + */ if (conn->request_info.query_string) { if (should_decode_query_string(conn)) { url_decode_in_place((char *)conn->request_info.query_string); @@ -14062,7 +14783,7 @@ handle_request(struct mg_connection *conn) ri->local_uri = tmp; /* step 1. completed, the url is known now */ - DEBUG_TRACE("URL: %s", ri->local_uri); + DEBUG_TRACE("REQUEST: %s %s", ri->request_method, ri->local_uri); /* 2. if this ip has limited speed, set it for this connection */ conn->throttle = set_throttle(conn->dom_ctx->config[THROTTLE], @@ -14082,11 +14803,13 @@ handle_request(struct mg_connection *conn) if (!conn->must_close) { discard_unread_request_data(conn); } + DEBUG_TRACE("%s", "begin_request handled request"); return; } else if (i == 0) { /* civetweb should process the request */ } else { /* unspecified - may change with the next version */ + DEBUG_TRACE("%s", "done (undocumented behavior)"); return; } } @@ -14155,8 +14878,8 @@ handle_request(struct mg_connection *conn) } } mg_printf(conn, "Access-Control-Max-Age: 60\r\n"); - mg_printf(conn, "\r\n"); + DEBUG_TRACE("%s", "OPTIONS done"); return; } } @@ -14195,6 +14918,9 @@ handle_request(struct mg_connection *conn) is_callback_resource = 1; is_script_resource = 1; is_put_or_delete_request = is_put_or_delete_method(conn); + /* Never handle a C callback according to File WebDav rules, + * even if it is a webdav method */ + is_webdav_request = 0; /* is_civetweb_webdav_method(conn); */ } else { no_callback_resource: @@ -14210,9 +14936,24 @@ handle_request(struct mg_connection *conn) &is_script_resource, &is_websocket_request, &is_put_or_delete_request, + &is_webdav_request, &is_template_text_file); } + /* 5.3. A webdav request (PROPFIND/PROPPATCH/LOCK/UNLOCK) */ + if (is_webdav_request) { + /* TODO: Do we need a config option? */ + const char *webdav_enable = conn->dom_ctx->config[ENABLE_WEBDAV]; + if (webdav_enable[0] != 'y') { + mg_send_http_error(conn, + 405, + "%s method not allowed", + conn->request_info.request_method); + DEBUG_TRACE("%s", "webdav rejected"); + return; + } + } + /* 6. authorization check */ /* 6.1. a custom authorization handler is installed */ if (get_request_handler(conn, @@ -14230,7 +14971,7 @@ handle_request(struct mg_connection *conn) /* Callback handler will not be used anymore. Release it */ release_handler_ref(conn, handler_info); - + DEBUG_TRACE("%s", "auth handler rejected request"); return; } } else if (is_put_or_delete_request && !is_script_resource @@ -14241,7 +14982,8 @@ handle_request(struct mg_connection *conn) #if defined(NO_FILES) if (1) { #else - if (conn->dom_ctx->config[DOCUMENT_ROOT] == NULL) { + if (conn->dom_ctx->config[DOCUMENT_ROOT] == NULL + || conn->dom_ctx->config[PUT_DELETE_PASSWORDS_FILE] == NULL) { #endif /* This code path will not be called for request handlers */ DEBUG_ASSERT(handler_info == NULL); @@ -14252,6 +14994,7 @@ handle_request(struct mg_connection *conn) 405, "%s method not allowed", conn->request_info.request_method); + DEBUG_TRACE("%s", "all file based put/delete requests rejected"); return; } @@ -14261,6 +15004,7 @@ handle_request(struct mg_connection *conn) */ if (!is_authorized_for_put(conn)) { send_authorization_request(conn, NULL); + DEBUG_TRACE("%s", "file write needs authorization"); return; } #endif @@ -14274,7 +15018,7 @@ handle_request(struct mg_connection *conn) /* Callback handler will not be used anymore. Release it */ release_handler_ref(conn, handler_info); - + DEBUG_TRACE("%s", "access authorization required"); return; } } @@ -14316,15 +15060,6 @@ handle_request(struct mg_connection *conn) /* For the moment, use option c: We look for a proper file, * but since a file request is not always a script resource, * the authorization check might be different. */ - interpret_uri(conn, - path, - sizeof(path), - &file.stat, - &is_found, - &is_script_resource, - &is_websocket_request, - &is_put_or_delete_request, - &is_template_text_file); callback_handler = NULL; /* Here we are at a dead end: @@ -14351,6 +15086,7 @@ handle_request(struct mg_connection *conn) callback_data); #endif } + DEBUG_TRACE("%s", "websocket handling done"); return; } @@ -14378,6 +15114,7 @@ handle_request(struct mg_connection *conn) } else { mg_send_http_error(conn, 404, "%s", "Not found"); } + DEBUG_TRACE("%s", "websocket script done"); return; } else #endif @@ -14393,6 +15130,7 @@ handle_request(struct mg_connection *conn) * by a script file. Thus, a DOCUMENT_ROOT must exist. */ if (conn->dom_ctx->config[DOCUMENT_ROOT] == NULL) { mg_send_http_error(conn, 404, "%s", "Not Found"); + DEBUG_TRACE("%s", "no document root available"); return; } @@ -14400,34 +15138,89 @@ handle_request(struct mg_connection *conn) if (is_script_resource) { HTTP1_only; handle_file_based_request(conn, path, &file); + DEBUG_TRACE("%s", "script handling done"); return; } + /* Request was not handled by a callback or script. It will be + * handled by a server internal method. */ + /* 11. Handle put/delete/mkcol requests */ if (is_put_or_delete_request) { HTTP1_only; /* 11.1. PUT method */ if (!strcmp(ri->request_method, "PUT")) { put_file(conn, path); + DEBUG_TRACE("handling %s request to %s done", + ri->request_method, + path); return; } /* 11.2. DELETE method */ if (!strcmp(ri->request_method, "DELETE")) { delete_file(conn, path); + DEBUG_TRACE("handling %s request to %s done", + ri->request_method, + path); return; } /* 11.3. MKCOL method */ if (!strcmp(ri->request_method, "MKCOL")) { - mkcol(conn, path); + dav_mkcol(conn, path); + DEBUG_TRACE("handling %s request to %s done", + ri->request_method, + path); return; } - /* 11.4. PATCH method + /* 11.4. MOVE method */ + if (!strcmp(ri->request_method, "MOVE")) { + dav_move_file(conn, path, 0); + DEBUG_TRACE("handling %s request to %s done", + ri->request_method, + path); + return; + } + if (!strcmp(ri->request_method, "COPY")) { + dav_move_file(conn, path, 1); + DEBUG_TRACE("handling %s request to %s done", + ri->request_method, + path); + return; + } + /* 11.5. LOCK method */ + if (!strcmp(ri->request_method, "LOCK")) { + dav_lock_file(conn, path); + DEBUG_TRACE("handling %s request to %s done", + ri->request_method, + path); + return; + } + /* 11.6. UNLOCK method */ + if (!strcmp(ri->request_method, "UNLOCK")) { + dav_unlock_file(conn, path); + DEBUG_TRACE("handling %s request to %s done", + ri->request_method, + path); + return; + } + /* 11.7. PROPPATCH method */ + if (!strcmp(ri->request_method, "PROPPATCH")) { + dav_proppatch(conn, path); + DEBUG_TRACE("handling %s request to %s done", + ri->request_method, + path); + return; + } + /* 11.8. Other methods, e.g.: PATCH * This method is not supported for static resources, * only for scripts (Lua, CGI) and callbacks. */ mg_send_http_error(conn, 405, "%s method not allowed", conn->request_info.request_method); + DEBUG_TRACE("method %s on %s is not supported", + ri->request_method, + path); return; } @@ -14435,30 +15228,40 @@ handle_request(struct mg_connection *conn) * hidden */ if (!is_found || (must_hide_file(conn, path))) { mg_send_http_error(conn, 404, "%s", "Not found"); + DEBUG_TRACE("handling %s request to %s: file not found", + ri->request_method, + path); return; } /* 12. Directory uris should end with a slash */ - if (file.stat.is_directory && (uri_len > 0) + if (file.stat.is_directory && ((uri_len = (int)strlen(ri->local_uri)) > 0) && (ri->local_uri[uri_len - 1] != '/')) { - size_t len = strlen(ri->request_uri); - size_t lenQS = ri->query_string ? strlen(ri->query_string) + 1 : 0; - char *new_path = (char *)mg_malloc_ctx(len + lenQS + 2, conn->phys_ctx); + /* Path + server root */ + size_t buflen = UTF8_PATH_MAX * 2 + 2; + char *new_path; + + if (ri->query_string) { + buflen += strlen(ri->query_string); + } + new_path = (char *)mg_malloc_ctx(buflen, conn->phys_ctx); if (!new_path) { mg_send_http_error(conn, 500, "out or memory"); } else { - memcpy(new_path, ri->request_uri, len); - new_path[len] = '/'; - new_path[len + 1] = 0; + mg_get_request_link(conn, new_path, buflen - 1); + strcat(new_path, "/"); if (ri->query_string) { - new_path[len + 1] = '?'; - /* Copy query string including terminating zero */ - memcpy(new_path + len + 2, ri->query_string, lenQS); + /* Append ? and query string */ + strcat(new_path, "?"); + strcat(new_path, ri->query_string); } mg_send_http_redirect(conn, new_path, 301); mg_free(new_path); } + DEBUG_TRACE("%s request to %s: directory redirection sent", + ri->request_method, + path); return; } @@ -14466,6 +15269,7 @@ handle_request(struct mg_connection *conn) /* 13.1. Handle PROPFIND */ if (!strcmp(ri->request_method, "PROPFIND")) { handle_propfind(conn, path, &file.stat); + DEBUG_TRACE("handling %s request to %s done", ri->request_method, path); return; } /* 13.2. Handle OPTIONS for files */ @@ -14476,6 +15280,7 @@ handle_request(struct mg_connection *conn) * Lua and CGI scripts may fully support CORS this way (including * preflights). */ send_options(conn); + DEBUG_TRACE("handling %s request to %s done", ri->request_method, path); return; } /* 13.3. everything but GET and HEAD (e.g. POST) */ @@ -14485,6 +15290,7 @@ handle_request(struct mg_connection *conn) 405, "%s method not allowed", conn->request_info.request_method); + DEBUG_TRACE("handling %s request to %s done", ri->request_method, path); return; } @@ -14502,6 +15308,7 @@ handle_request(struct mg_connection *conn) "%s", "Error: Directory listing denied"); } + DEBUG_TRACE("handling %s request to %s done", ri->request_method, path); return; } @@ -14509,6 +15316,9 @@ handle_request(struct mg_connection *conn) if (is_template_text_file) { HTTP1_only; handle_file_based_request(conn, path, &file); + DEBUG_TRACE("handling %s request to %s done (template)", + ri->request_method, + path); return; } @@ -14517,12 +15327,18 @@ handle_request(struct mg_connection *conn) if ((!conn->in_error_handler) && is_not_modified(conn, &file.stat)) { /* Send 304 "Not Modified" - this must not send any body data */ handle_not_modified_static_file_request(conn, &file); + DEBUG_TRACE("handling %s request to %s done (not modified)", + ri->request_method, + path); return; } #endif /* !NO_CACHING */ /* 17. Static file - not cached */ handle_static_file_request(conn, path, &file, NULL, NULL); + DEBUG_TRACE("handling %s request to %s done (static)", + ri->request_method, + path); #endif /* !defined(NO_FILES) */ } @@ -14597,7 +15413,7 @@ handle_file_based_request(struct mg_connection *conn, > 0) { if (is_in_script_path(conn, path)) { /* CGI scripts may support all HTTP methods */ - handle_cgi_request(conn, path, 0); + handle_cgi_request(conn, path, cgi_config_idx); } else { /* Script was in an illegal path */ mg_send_http_error(conn, 403, "%s", "Forbidden"); @@ -14761,7 +15577,7 @@ parse_port_string(const struct vec *vec, struct socket *so, int *ip_version) *ip_version = 4; } else if ((cb = strchr(vec->ptr, ':')) != NULL) { - /* String could be a hostname. This check algotithm + /* String could be a hostname. This check algorithm * will only work for RFC 952 compliant hostnames, * starting with a letter, containing only letters, * digits and hyphen ('-'). Newer specs may allow @@ -15244,7 +16060,12 @@ log_access(const struct mg_connection *conn) const struct mg_request_info *ri; struct mg_file fi; char date[64], src_addr[IP_ADDR_STR_LEN]; +#if defined(REENTRANT_TIME) + struct tm _tm; + struct tm *tm = &_tm; +#else struct tm *tm; +#endif const char *referer; const char *user_agent; @@ -15322,12 +16143,15 @@ log_access(const struct mg_connection *conn) /* If we did not get a log message from Lua, create it here. */ if (!log_buf[0]) { +#if defined(REENTRANT_TIME) + localtime_r(&conn->conn_birth_time, tm); +#else tm = localtime(&conn->conn_birth_time); +#endif if (tm != NULL) { strftime(date, sizeof(date), "%d/%b/%Y:%H:%M:%S %z", tm); } else { mg_strlcpy(date, "01/Jan/1970:00:00:00 +0000", sizeof(date)); - date[sizeof(date) - 1] = '\0'; } ri = &conn->request_info; @@ -15359,7 +16183,7 @@ log_access(const struct mg_connection *conn) /* Here we have a log message in log_buf. Call the callback */ if (conn->phys_ctx->callbacks.log_access) { if (conn->phys_ctx->callbacks.log_access(conn, log_buf)) { - /* do not log if callack returns non-zero */ + /* do not log if callback returns non-zero */ if (fi.access.fp) { mg_fclose(&fi.access); } @@ -15716,7 +16540,7 @@ sslize(struct mg_connection *conn, pollres = mg_poll(&pfd, 1, 50, &(conn->phys_ctx->stop_flag)); if (pollres < 0) { - /* Break if error occured (-1) + /* Break if error occurred (-1) * or server shutdown (-2) */ break; } @@ -16215,7 +17039,7 @@ ssl_get_protocol(int version_id) * examples, however some (maybe most, but not all) headers of OpenSSL * versions / OpenSSL compatibility layers have it. Having a different * definition will cause a warning in C and an error in C++. Use "const SSL - * *", while automatical conversion from "SSL *" works for all compilers, + * *", while automatic conversion from "SSL *" works for all compilers, * but not other way around */ static void ssl_info_callback(const SSL *ssl, int what, int ret) @@ -16494,7 +17318,7 @@ init_ssl_ctx_impl(struct mg_context *phys_ctx, phys_ctx->user_data)); /* If callback returns 0, civetweb sets up the SSL certificate. - * If it returns 1, civetweb assumes the calback already did this. + * If it returns 1, civetweb assumes the callback already did this. * If it returns -1, initializing ssl fails. */ if (callback_ret < 0) { mg_cry_ctx_internal(phys_ctx, @@ -16516,7 +17340,7 @@ init_ssl_ctx_impl(struct mg_context *phys_ctx, phys_ctx->user_data)); /* If domain callback returns 0, civetweb sets up the SSL certificate. - * If it returns 1, civetweb assumes the calback already did this. + * If it returns 1, civetweb assumes the callback already did this. * If it returns -1, initializing ssl fails. */ if (callback_ret < 0) { mg_cry_ctx_internal(phys_ctx, @@ -16931,7 +17755,6 @@ close_socket_gracefully(struct mg_connection *conn) /* Send FIN to the client */ shutdown(conn->client.sock, SHUTDOWN_WR); - #if defined(_WIN32) /* Read and discard pending incoming data. If we do not do that and * close @@ -17063,7 +17886,6 @@ close_connection(struct mg_connection *conn) * it must be done in the connection_close callback. */ mg_set_user_connection_data(conn, NULL); - #if defined(USE_SERVER_STATS) conn->conn_state = 7; /* closing */ #endif @@ -17107,7 +17929,7 @@ close_connection(struct mg_connection *conn) } -void +CIVETWEB_API void mg_close_connection(struct mg_connection *conn) { if ((conn == NULL) || (conn->phys_ctx == NULL)) { @@ -17173,8 +17995,8 @@ mg_close_connection(struct mg_connection *conn) static struct mg_connection * mg_connect_client_impl(const struct mg_client_options *client_options, int use_ssl, - char *ebuf, - size_t ebuf_len) + struct mg_init_data *init, + struct mg_error_data *error) { struct mg_connection *conn = NULL; SOCKET sock; @@ -17188,18 +18010,31 @@ mg_connect_client_impl(const struct mg_client_options *client_options, /* Size of structures, aligned to 8 bytes */ size_t conn_size = ((sizeof(struct mg_connection) + 7) >> 3) << 3; size_t ctx_size = ((sizeof(struct mg_context) + 7) >> 3) << 3; + size_t alloc_size = conn_size + ctx_size + max_req_size; - conn = - (struct mg_connection *)mg_calloc(1, - conn_size + ctx_size + max_req_size); + (void)init; /* TODO: Implement required options */ + + conn = (struct mg_connection *)mg_calloc(1, alloc_size); + + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_OK; + error->code_sub = 0; + if (error->text_buffer_size > 0) { + error->text[0] = 0; + } + } if (conn == NULL) { - mg_snprintf(NULL, - NULL, /* No truncation check for ebuf */ - ebuf, - ebuf_len, - "calloc(): %s", - strerror(ERRNO)); + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_OUT_OF_MEMORY; + error->code_sub = (unsigned)alloc_size; + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + error->text, + error->text_buffer_size, + "calloc(): %s", + strerror(ERRNO)); + } return NULL; } @@ -17224,12 +18059,11 @@ mg_connect_client_impl(const struct mg_client_options *client_options, client_options->host, client_options->port, use_ssl, - ebuf, - ebuf_len, + error, &sock, &sa)) { - /* ebuf is set by connect_socket, - * free all memory and return NULL; */ + /* "error" will be set by connect_socket. */ + /* free all memory and return NULL; */ mg_free(conn); return NULL; } @@ -17237,37 +18071,48 @@ mg_connect_client_impl(const struct mg_client_options *client_options, #if !defined(NO_SSL) && !defined(USE_MBEDTLS) // TODO: mbedTLS client #if (defined(OPENSSL_API_1_1) || defined(OPENSSL_API_3_0)) \ && !defined(NO_SSL_DL) + if (use_ssl && (conn->dom_ctx->ssl_ctx = SSL_CTX_new(TLS_client_method())) == NULL) { - mg_snprintf(NULL, - NULL, /* No truncation check for ebuf */ - ebuf, - ebuf_len, - "SSL_CTX_new error: %s", - ssl_error()); + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_INIT_TLS_FAILED; + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + error->text, + error->text_buffer_size, + "SSL_CTX_new error: %s", + ssl_error()); + } + closesocket(sock); mg_free(conn); return NULL; } + #else + if (use_ssl && (conn->dom_ctx->ssl_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) { - mg_snprintf(NULL, - NULL, /* No truncation check for ebuf */ - ebuf, - ebuf_len, - "SSL_CTX_new error: %s", - ssl_error()); + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_INIT_TLS_FAILED; + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + error->text, + error->text_buffer_size, + "SSL_CTX_new error: %s", + ssl_error()); + } + closesocket(sock); mg_free(conn); return NULL; } + #endif /* OPENSSL_API_1_1 || OPENSSL_API_3_0 */ #endif /* NO_SSL */ - #if defined(USE_IPV6) len = (sa.sa.sa_family == AF_INET) ? sizeof(conn->client.rsa.sin) : sizeof(conn->client.rsa.sin6); @@ -17291,11 +18136,15 @@ mg_connect_client_impl(const struct mg_client_options *client_options, conn->client.is_ssl = use_ssl ? 1 : 0; if (0 != pthread_mutex_init(&conn->mutex, &pthread_mutex_attr)) { - mg_snprintf(NULL, - NULL, /* No truncation check for ebuf */ - ebuf, - ebuf_len, - "Can not create mutex"); + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_OS_ERROR; + error->code_sub = (unsigned)ERRNO; + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + error->text, + error->text_buffer_size, + "Can not create mutex"); + } #if !defined(NO_SSL) && !defined(USE_MBEDTLS) // TODO: mbedTLS client SSL_CTX_free(conn->dom_ctx->ssl_ctx); #endif @@ -17304,7 +18153,6 @@ mg_connect_client_impl(const struct mg_client_options *client_options, return NULL; } - #if !defined(NO_SSL) && !defined(USE_MBEDTLS) // TODO: mbedTLS client if (use_ssl) { /* TODO: Check ssl_verify_peer and ssl_ca_path here. @@ -17319,11 +18167,15 @@ mg_connect_client_impl(const struct mg_client_options *client_options, conn->dom_ctx, client_options->client_cert, NULL)) { - mg_snprintf(NULL, - NULL, /* No truncation check for ebuf */ - ebuf, - ebuf_len, - "Can not use SSL client certificate"); + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_TLS_CLIENT_CERT_ERROR; + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + error->text, + error->text_buffer_size, + "Can not use SSL client certificate"); + } + SSL_CTX_free(conn->dom_ctx->ssl_ctx); closesocket(sock); mg_free(conn); @@ -17336,9 +18188,15 @@ mg_connect_client_impl(const struct mg_client_options *client_options, client_options->server_cert, NULL) != 1) { - mg_cry_internal(conn, - "SSL_CTX_load_verify_locations error: %s ", - ssl_error()); + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_TLS_SERVER_CERT_ERROR; + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + error->text, + error->text_buffer_size, + "SSL_CTX_load_verify_locations error: %s", + ssl_error()); + } SSL_CTX_free(conn->dom_ctx->ssl_ctx); closesocket(sock); mg_free(conn); @@ -17350,11 +18208,14 @@ mg_connect_client_impl(const struct mg_client_options *client_options, } if (!sslize(conn, SSL_connect, client_options)) { - mg_snprintf(NULL, - NULL, /* No truncation check for ebuf */ - ebuf, - ebuf_len, - "SSL connection error"); + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_TLS_CONNECT_ERROR; + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + error->text, + error->text_buffer_size, + "SSL connection error"); + } SSL_CTX_free(conn->dom_ctx->ssl_ctx); closesocket(sock); mg_free(conn); @@ -17372,14 +18233,18 @@ mg_connect_client_secure(const struct mg_client_options *client_options, char *error_buffer, size_t error_buffer_size) { - return mg_connect_client_impl(client_options, - 1, - error_buffer, - error_buffer_size); + struct mg_init_data init; + struct mg_error_data error; + + memset(&init, 0, sizeof(init)); + memset(&error, 0, sizeof(error)); + error.text_buffer_size = error_buffer_size; + error.text = error_buffer; + return mg_connect_client_impl(client_options, 1, &init, &error); } -struct mg_connection * +CIVETWEB_API struct mg_connection * mg_connect_client(const char *host, int port, int use_ssl, @@ -17387,18 +18252,28 @@ mg_connect_client(const char *host, size_t error_buffer_size) { struct mg_client_options opts; + struct mg_init_data init; + struct mg_error_data error; + + memset(&init, 0, sizeof(init)); + + memset(&error, 0, sizeof(error)); + error.text_buffer_size = error_buffer_size; + error.text = error_buffer; + memset(&opts, 0, sizeof(opts)); opts.host = host; opts.port = port; - return mg_connect_client_impl(&opts, - use_ssl, - error_buffer, - error_buffer_size); + if (use_ssl) { + opts.host_name = host; + } + + return mg_connect_client_impl(&opts, use_ssl, &init, &error); } #if defined(MG_EXPERIMENTAL_INTERFACES) -struct mg_connection * +CIVETWEB_API struct mg_connection * mg_connect_client2(const char *host, const char *protocol, int port, @@ -17406,18 +18281,22 @@ mg_connect_client2(const char *host, struct mg_init_data *init, struct mg_error_data *error) { + (void)path; + int is_ssl, is_ws; /* void *user_data = (init != NULL) ? init->user_data : NULL; -- TODO */ if (error != NULL) { - error->code = 0; + error->code = MG_ERROR_DATA_CODE_OK; + error->code_sub = 0; if (error->text_buffer_size > 0) { *error->text = 0; } } if ((host == NULL) || (protocol == NULL)) { - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_INVALID_PARAM; mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -17428,7 +18307,7 @@ mg_connect_client2(const char *host, return NULL; } - /* check all known protocolls */ + /* check all known protocols */ if (!mg_strcasecmp(protocol, "http")) { is_ssl = 0; is_ws = 0; @@ -17444,7 +18323,8 @@ mg_connect_client2(const char *host, is_ws = 1; #endif } else { - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_INVALID_PARAM; mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -17473,18 +18353,18 @@ mg_connect_client2(const char *host, experimental_websocket_client_close_wrapper, (void *)init->callbacks); } +#else + (void)is_ws; #endif /* TODO: all additional options */ struct mg_client_options opts; + memset(&opts, 0, sizeof(opts)); opts.host = host; opts.port = port; - return mg_connect_client_impl(&opts, - is_ssl, - ((error != NULL) ? error->text : NULL), - ((error != NULL) ? error->text_buffer_size - : 0)); + + return mg_connect_client_impl(&opts, is_ssl, init, error); } #endif @@ -17933,7 +18813,7 @@ get_response(struct mg_connection *conn, char *ebuf, size_t ebuf_len, int *err) } -int +CIVETWEB_API int mg_get_response(struct mg_connection *conn, char *ebuf, size_t ebuf_len, @@ -17985,7 +18865,7 @@ mg_get_response(struct mg_connection *conn, } -struct mg_connection * +CIVETWEB_API struct mg_connection * mg_download(const char *host, int port, int use_ssl, @@ -18129,16 +19009,21 @@ mg_connect_websocket_client_impl(const struct mg_client_options *client_options, const char *host = client_options->host; int i; + struct mg_init_data init; + struct mg_error_data error; + + memset(&init, 0, sizeof(init)); + memset(&error, 0, sizeof(error)); + error.text_buffer_size = error_buffer_size; + error.text = error_buffer; + #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat-nonliteral" #endif /* Establish the client connection and request upgrade */ - conn = mg_connect_client_impl(client_options, - use_ssl, - error_buffer, - error_buffer_size); + conn = mg_connect_client_impl(client_options, use_ssl, &init, &error); /* Connection object will be null if something goes wrong */ if (conn == NULL) { @@ -18318,7 +19203,7 @@ mg_connect_websocket_client_impl(const struct mg_client_options *client_options, } -struct mg_connection * +CIVETWEB_API struct mg_connection * mg_connect_websocket_client(const char *host, int port, int use_ssl, @@ -18348,7 +19233,7 @@ mg_connect_websocket_client(const char *host, } -struct mg_connection * +CIVETWEB_API struct mg_connection * mg_connect_websocket_client_secure( const struct mg_client_options *client_options, char *error_buffer, @@ -18374,7 +19259,8 @@ mg_connect_websocket_client_secure( user_data); } -struct mg_connection * + +CIVETWEB_API struct mg_connection * mg_connect_websocket_client_extensions(const char *host, int port, int use_ssl, @@ -18404,7 +19290,8 @@ mg_connect_websocket_client_extensions(const char *host, user_data); } -struct mg_connection * + +CIVETWEB_API struct mg_connection * mg_connect_websocket_client_secure_extensions( const struct mg_client_options *client_options, char *error_buffer, @@ -18431,6 +19318,7 @@ mg_connect_websocket_client_secure_extensions( user_data); } + /* Prepare connection data structure */ static void init_connection(struct mg_connection *conn) @@ -18448,6 +19336,7 @@ init_connection(struct mg_connection *conn) conn->data_len = 0; conn->handled_requests = 0; conn->connection_type = CONNECTION_TYPE_INVALID; + conn->request_info.acceptedWebSocketSubprotocol = NULL; mg_set_user_connection_data(conn, NULL); #if defined(USE_SERVER_STATS) @@ -18902,7 +19791,7 @@ worker_thread_run(struct mg_connection *conn) sizeof(conn->request_info.remote_addr), &conn->client.rsa); - DEBUG_TRACE("Incomming %sconnection from %s", + DEBUG_TRACE("Incoming %sconnection from %s", (conn->client.is_ssl ? "SSL " : ""), conn->request_info.remote_addr); @@ -19297,6 +20186,7 @@ master_thread_run(struct mg_context *ctx) "stop"); } } + DEBUG_TRACE("Close Lua background state %p", lstate); lua_close(lstate); ctx->lua_background_state = 0; @@ -19428,7 +20318,7 @@ free_context(struct mg_context *ctx) if (callback_ret == 0) { SSL_CTX_free(ctx->dd.ssl_ctx); } - /* else: ignore error and ommit SSL_CTX_free in case + /* else: ignore error and omit SSL_CTX_free in case * callback_ret is 1 */ } #endif /* !NO_SSL */ @@ -19447,7 +20337,7 @@ free_context(struct mg_context *ctx) } -void +CIVETWEB_API void mg_stop(struct mg_context *ctx) { pthread_t mt; @@ -19526,7 +20416,6 @@ get_system_name(char **sysName) *sysName = mg_strdup(name); - #elif defined(__ZEPHYR__) *sysName = mg_strdup("Zephyr OS"); #else @@ -19566,7 +20455,7 @@ legacy_init(const char **options) } -struct mg_context * +CIVETWEB_API struct mg_context * mg_start2(struct mg_init_data *init, struct mg_error_data *error) { struct mg_context *ctx; @@ -19581,7 +20470,8 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) struct mg_workerTLS tls; if (error != NULL) { - error->code = 0; + error->code = MG_ERROR_DATA_CODE_OK; + error->code_sub = 0; if (error->text_buffer_size > 0) { *error->text = 0; } @@ -19594,7 +20484,8 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) legacy_init(options); } if (mg_init_library_called == 0) { - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_INIT_LIBRARY_FAILED; mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -19606,8 +20497,11 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) } /* Allocate context and initialize reasonable general case defaults. */ - if ((ctx = (struct mg_context *)mg_calloc(1, sizeof(*ctx))) == NULL) { - if ((error != NULL) && (error->text_buffer_size > 0)) { + ctx = (struct mg_context *)mg_calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_OUT_OF_MEMORY; + error->code_sub = (unsigned)sizeof(*ctx); mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -19644,13 +20538,16 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) ok &= (0 == pthread_mutex_init(&ctx->lua_bg_mutex, &pthread_mutex_attr)); #endif if (!ok) { + unsigned error_id = (unsigned)ERRNO; const char *err_msg = "Cannot initialize thread synchronization objects"; /* Fatal error - abort start. However, this situation should never * occur in practice. */ mg_cry_ctx_internal(ctx, "%s", err_msg); - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_OS_ERROR; + error->code_sub = error_id; mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -19683,9 +20580,12 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) /* Store options */ while (options && (name = *options++) != NULL) { - if ((idx = get_option_index(name)) == -1) { + idx = get_option_index(name); + if (idx == -1) { mg_cry_ctx_internal(ctx, "Invalid option: %s", name); - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_INVALID_OPTION; + error->code_sub = (unsigned)-1; mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -19693,12 +20593,16 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) "Invalid configuration option: %s", name); } + free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; + } else if ((value = *options++) == NULL) { mg_cry_ctx_internal(ctx, "%s: option value cannot be NULL", name); - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_INVALID_OPTION; + error->code_sub = (unsigned)idx; mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -19706,6 +20610,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) "Invalid configuration option value: %s", name); } + free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; @@ -19734,7 +20639,9 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) mg_cry_ctx_internal(ctx, "%s too small", config_options[MAX_REQUEST_SIZE].name); - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_INVALID_OPTION; + error->code_sub = (unsigned)MAX_REQUEST_SIZE; mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -19742,6 +20649,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) "Invalid configuration option value: %s", config_options[MAX_REQUEST_SIZE].name); } + free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; @@ -19755,7 +20663,9 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) mg_cry_ctx_internal(ctx, "%s too small", config_options[CONNECTION_QUEUE_SIZE].name); - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_INVALID_OPTION; + error->code_sub = CONNECTION_QUEUE_SIZE; mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -19763,6 +20673,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) "Invalid configuration option value: %s", config_options[CONNECTION_QUEUE_SIZE].name); } + free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; @@ -19773,7 +20684,9 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) mg_cry_ctx_internal(ctx, "Out of memory: Cannot allocate %s", config_options[CONNECTION_QUEUE_SIZE].name); - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_OUT_OF_MEMORY; + error->code_sub = (unsigned)itmp * (unsigned)sizeof(struct socket); mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -19781,6 +20694,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) "Out of memory: Cannot allocate %s", config_options[CONNECTION_QUEUE_SIZE].name); } + free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; @@ -19797,7 +20711,9 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) } else { mg_cry_ctx_internal(ctx, "%s", "Too many worker threads"); } - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_INVALID_OPTION; + error->code_sub = NUM_THREADS; mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -19805,6 +20721,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) "Invalid configuration option value: %s", config_options[NUM_THREADS].name); } + free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; @@ -19814,7 +20731,9 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) #if defined(NO_FILES) if (ctx->dd.config[DOCUMENT_ROOT] != NULL) { mg_cry_ctx_internal(ctx, "%s", "Document root must not be set"); - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_INVALID_OPTION; + error->code_sub = (unsigned)DOCUMENT_ROOT; mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -19822,6 +20741,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) "Invalid configuration option value: %s", config_options[DOCUMENT_ROOT].name); } + free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; @@ -19849,7 +20769,8 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) mg_cry_ctx_internal(ctx, "lua_background_script load error: %s", ebuf); - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_SCRIPT_ERROR; mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -19858,6 +20779,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) config_options[LUA_BACKGROUND_SCRIPT].name, ebuf); } + pthread_mutex_unlock(&ctx->lua_bg_mutex); free_context(ctx); @@ -19893,7 +20815,8 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) mg_cry_ctx_internal(ctx, "lua_background_script start error: %s", ebuf); - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_SCRIPT_ERROR; mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -19925,7 +20848,8 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) /* Fatal error - abort start. */ mg_cry_ctx_internal(ctx, "%s", err_msg); - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_INVALID_PASS_FILE; mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -19945,7 +20869,8 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) /* Fatal error - abort start. */ mg_cry_ctx_internal(ctx, "%s", err_msg); - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_INIT_TLS_FAILED; mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -19953,6 +20878,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) "%s", err_msg); } + free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; @@ -19964,7 +20890,8 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) /* Fatal error - abort start. */ mg_cry_ctx_internal(ctx, "%s", err_msg); - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_INIT_TLS_FAILED; mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -19972,6 +20899,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) "%s", err_msg); } + free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; @@ -19983,7 +20911,8 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) /* Fatal error - abort start. */ mg_cry_ctx_internal(ctx, "%s", err_msg); - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_INIT_PORTS_FAILED; mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -19991,19 +20920,20 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) "%s", err_msg); } + free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; } - #if !defined(_WIN32) && !defined(__ZEPHYR__) if (!set_uid_option(ctx)) { const char *err_msg = "Failed to run as configured user"; /* Fatal error - abort start. */ mg_cry_ctx_internal(ctx, "%s", err_msg); - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_INIT_USER_FAILED; mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -20011,6 +20941,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) "%s", err_msg); } + free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; @@ -20022,7 +20953,8 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) /* Fatal error - abort start. */ mg_cry_ctx_internal(ctx, "%s", err_msg); - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_INIT_ACL_FAILED; mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -20030,6 +20962,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) "%s", err_msg); } + free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; @@ -20044,7 +20977,10 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) const char *err_msg = "Not enough memory for worker thread ID array"; mg_cry_ctx_internal(ctx, "%s", err_msg); - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_OUT_OF_MEMORY; + error->code_sub = + (unsigned)ctx->cfg_worker_threads * (unsigned)sizeof(pthread_t); mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -20052,6 +20988,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) "%s", err_msg); } + free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; @@ -20065,7 +21002,10 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) "Not enough memory for worker thread connection array"; mg_cry_ctx_internal(ctx, "%s", err_msg); - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_OUT_OF_MEMORY; + error->code_sub = (unsigned)ctx->cfg_worker_threads + * (unsigned)sizeof(struct mg_connection); mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -20073,6 +21013,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) "%s", err_msg); } + free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; @@ -20088,7 +21029,10 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) mg_cry_ctx_internal(ctx, "%s", err_msg); mg_free(ctx->worker_threadids); - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_OUT_OF_MEMORY; + error->code_sub = (unsigned)ctx->cfg_worker_threads + * (unsigned)sizeof(ctx->client_wait_events[0]); mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -20096,6 +21040,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) "%s", err_msg); } + free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; @@ -20111,7 +21056,10 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) mg_free(ctx->client_wait_events); mg_free(ctx->worker_threadids); - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_OUT_OF_MEMORY; + error->code_sub = (unsigned)ctx->cfg_worker_threads + * (unsigned)sizeof(ctx->client_socks[0]); mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -20119,6 +21067,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) "%s", err_msg); } + free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; @@ -20137,7 +21086,9 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) mg_free(ctx->client_wait_events); mg_free(ctx->worker_threadids); - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_OS_ERROR; + error->code_sub = ERRNO; mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -20145,6 +21096,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) err_msg, i); } + free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; @@ -20157,7 +21109,9 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) const char *err_msg = "Error creating timers"; mg_cry_ctx_internal(ctx, "%s", err_msg); - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_OS_ERROR; + error->code_sub = ERRNO; mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -20165,6 +21119,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) "%s", err_msg); } + free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; @@ -20213,7 +21168,9 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) "Cannot create threads: error %ld", error_no); - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_OS_ERROR; + error->code_sub = (unsigned)error_no; mg_snprintf( NULL, NULL, /* No truncation check for error buffers */ @@ -20222,6 +21179,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) "Cannot create first worker thread: error %ld", error_no); } + free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; @@ -20238,7 +21196,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error) } -struct mg_context * +CIVETWEB_API struct mg_context * mg_start(const struct mg_callbacks *callbacks, void *user_data, const char **options) @@ -20253,7 +21211,7 @@ mg_start(const struct mg_callbacks *callbacks, /* Add an additional domain to an already running web server. */ -int +CIVETWEB_API int mg_start_domain2(struct mg_context *ctx, const char **options, struct mg_error_data *error) @@ -20266,14 +21224,16 @@ mg_start_domain2(struct mg_context *ctx, int idx, i; if (error != NULL) { - error->code = 0; + error->code = MG_ERROR_DATA_CODE_OK; + error->code_sub = 0; if (error->text_buffer_size > 0) { *error->text = 0; } } if ((ctx == NULL) || (options == NULL)) { - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_INVALID_PARAM; mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -20285,7 +21245,8 @@ mg_start_domain2(struct mg_context *ctx, } if (!STOP_FLAG_IS_ZERO(&ctx->stop_flag)) { - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_SERVER_STOPPED; mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -20293,7 +21254,7 @@ mg_start_domain2(struct mg_context *ctx, "%s", "Server already stopped"); } - return -1; + return -7; } new_dom = (struct mg_domain_context *) @@ -20301,7 +21262,9 @@ mg_start_domain2(struct mg_context *ctx, if (!new_dom) { /* Out of memory */ - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_OUT_OF_MEMORY; + error->code_sub = (unsigned)sizeof(struct mg_domain_context); mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -20314,9 +21277,12 @@ mg_start_domain2(struct mg_context *ctx, /* Store options - TODO: unite duplicate code */ while (options && (name = *options++) != NULL) { - if ((idx = get_option_index(name)) == -1) { + idx = get_option_index(name); + if (idx == -1) { mg_cry_ctx_internal(ctx, "Invalid option: %s", name); - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_INVALID_OPTION; + error->code_sub = (unsigned)-1; mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -20328,7 +21294,9 @@ mg_start_domain2(struct mg_context *ctx, return -2; } else if ((value = *options++) == NULL) { mg_cry_ctx_internal(ctx, "%s: option value cannot be NULL", name); - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_INVALID_OPTION; + error->code_sub = idx; mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -20352,7 +21320,9 @@ mg_start_domain2(struct mg_context *ctx, /* TODO: Maybe use a new option hostname? */ if (!new_dom->config[AUTHENTICATION_DOMAIN]) { mg_cry_ctx_internal(ctx, "%s", "authentication domain required"); - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_MISSING_OPTION; + error->code_sub = AUTHENTICATION_DOMAIN; mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -20376,8 +21346,7 @@ mg_start_domain2(struct mg_context *ctx, new_dom->handlers = NULL; new_dom->next = NULL; new_dom->nonce_count = 0; - new_dom->auth_nonce_mask = - (uint64_t)get_random() ^ ((uint64_t)get_random() << 31); + new_dom->auth_nonce_mask = get_random() ^ (get_random() << 31); #if defined(USE_LUA) && defined(USE_WEBSOCKET) new_dom->shared_lua_websockets = NULL; @@ -20386,7 +21355,8 @@ mg_start_domain2(struct mg_context *ctx, #if !defined(NO_SSL) && !defined(USE_MBEDTLS) if (!init_ssl_ctx(ctx, new_dom)) { /* Init SSL failed */ - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_INIT_TLS_FAILED; mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -20411,7 +21381,8 @@ mg_start_domain2(struct mg_context *ctx, mg_cry_ctx_internal(ctx, "domain %s already in use", new_dom->config[AUTHENTICATION_DOMAIN]); - if ((error != NULL) && (error->text_buffer_size > 0)) { + if (error != NULL) { + error->code = MG_ERROR_DATA_CODE_DUPLICATE_DOMAIN; mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ error->text, @@ -20442,7 +21413,7 @@ mg_start_domain2(struct mg_context *ctx, } -int +CIVETWEB_API int mg_start_domain(struct mg_context *ctx, const char **options) { return mg_start_domain2(ctx, options, NULL); @@ -20450,7 +21421,7 @@ mg_start_domain(struct mg_context *ctx, const char **options) /* Feature check API function */ -unsigned +CIVETWEB_API unsigned mg_check_feature(unsigned feature) { static const unsigned feature_set = 0 @@ -20532,7 +21503,7 @@ mg_str_append(char **dst, char *end, const char *src) /* Get system information. It can be printed or stored by the caller. * Return the size of available information. */ -int +CIVETWEB_API int mg_get_system_info(char *buffer, int buflen) { char *end, *append_eoobj = NULL, block[256]; @@ -20722,7 +21693,6 @@ mg_get_system_info(char *buffer, int buflen) system_info_length += mg_str_append(&buffer, end, block); } - /* Compiler information */ /* http://sourceforge.net/p/predef/wiki/Compilers/ */ { @@ -20864,7 +21834,7 @@ mg_get_system_info(char *buffer, int buflen) /* Get context information. It can be printed or stored by the caller. * Return the size of available information. */ -int +CIVETWEB_API int mg_get_context_info(const struct mg_context *ctx, char *buffer, int buflen) { #if defined(USE_SERVER_STATS) @@ -21063,7 +22033,7 @@ mg_get_context_info(const struct mg_context *ctx, char *buffer, int buflen) } -void +CIVETWEB_API void mg_disable_connection_keep_alive(struct mg_connection *conn) { /* https://github.com/civetweb/civetweb/issues/727 */ @@ -21076,7 +22046,7 @@ mg_disable_connection_keep_alive(struct mg_connection *conn) #if defined(MG_EXPERIMENTAL_INTERFACES) /* Get connection information. It can be printed or stored by the caller. * Return the size of available information. */ -int +CIVETWEB_API int mg_get_connection_info(const struct mg_context *ctx, int idx, char *buffer, @@ -21322,12 +22292,53 @@ mg_get_connection_info(const struct mg_context *ctx, return (int)connection_info_length; } + +#if 0 +/* Get handler information. It can be printed or stored by the caller. + * Return the size of available information. */ +CIVETWEB_API int +mg_get_handler_info(struct mg_context *ctx, + char *buffer, + int buflen) +{ + int handler_info_len = 0; + struct mg_handler_info *tmp_rh; + mg_lock_context(ctx); + + for (tmp_rh = ctx->dd.handlers; tmp_rh != NULL; tmp_rh = tmp_rh->next) { + + if (buflen > handler_info_len+ tmp_rh->uri_len) { + memcpy(buffer+handler_info_len, tmp_rh->uri, tmp_rh->uri_len); + } + handler_info_len += tmp_rh->uri_len; + + switch (tmp_rh->handler_type) { + case REQUEST_HANDLER: + (void)tmp_rh->handler; + break; + case WEBSOCKET_HANDLER: + (void)tmp_rh->connect_handler; + (void) tmp_rh->ready_handler; + (void) tmp_rh->data_handler; + (void) tmp_rh->close_handler; + break; + case AUTH_HANDLER: + (void) tmp_rh->auth_handler; + break; + } + (void)cbdata; + } + + mg_unlock_context(ctx); + return handler_info_len; +} +#endif #endif /* Initialize this library. This function does not need to be thread safe. */ -unsigned +CIVETWEB_API unsigned mg_init_library(unsigned features) { unsigned features_to_init = mg_check_feature(features & 0xFFu); @@ -21343,6 +22354,8 @@ mg_init_library(unsigned features) mg_global_lock(); if (mg_init_library_called <= 0) { + int i, len; + #if defined(_WIN32) int file_mutex_init = 1; int wsa = 1; @@ -21370,7 +22383,6 @@ mg_init_library(unsigned features) #endif } - if (failed) { #if defined(_WIN32) if (wsa == 0) { @@ -21392,16 +22404,40 @@ mg_init_library(unsigned features) return 0; } -#if defined(USE_LUA) - lua_init_optional_libraries(); -#endif + len = 1; + for (i = 0; http_methods[i].name != NULL; i++) { + size_t sl = strlen(http_methods[i].name); + len += (int)sl; + if (i > 0) { + len += 2; + } + } + all_methods = (char *)mg_malloc(len); + if (!all_methods) { + /* Must never happen */ + mg_global_unlock(); + (void)pthread_mutex_destroy(&global_lock_mutex); + return 0; + } + all_methods[0] = 0; + for (i = 0; http_methods[i].name != NULL; i++) { + if (i > 0) { + strcat(all_methods, ", "); + strcat(all_methods, http_methods[i].name); + } else { + strcpy(all_methods, http_methods[i].name); + } + } } - mg_global_unlock(); +#if defined(USE_LUA) + lua_init_optional_libraries(); +#endif #if (defined(OPENSSL_API_1_0) || defined(OPENSSL_API_1_1) \ || defined(OPENSSL_API_3_0)) \ && !defined(NO_SSL) + if (features_to_init & MG_FEATURES_SSL) { if (!mg_openssl_initialized) { char ebuf[128]; @@ -21416,9 +22452,9 @@ mg_init_library(unsigned features) /* ssl already initialized */ } } + #endif - mg_global_lock(); if (mg_init_library_called <= 0) { mg_init_library_called = 1; } else { @@ -21431,7 +22467,7 @@ mg_init_library(unsigned features) /* Un-initialize this library. */ -unsigned +CIVETWEB_API unsigned mg_exit_library(void) { if (mg_init_library_called <= 0) { @@ -21461,6 +22497,8 @@ mg_exit_library(void) #if defined(USE_LUA) lua_exit_optional_libraries(); #endif + mg_free(all_methods); + all_methods = NULL; mg_global_unlock(); (void)pthread_mutex_destroy(&global_lock_mutex); diff --git a/vendor/CivetWeb/handle_form.inl b/vendor/CivetWeb/handle_form.inl index eaad88d7..be477a05 100644 --- a/vendor/CivetWeb/handle_form.inl +++ b/vendor/CivetWeb/handle_form.inl @@ -220,7 +220,7 @@ mg_handle_form_request(struct mg_connection *conn, } /* GET request: form data is in the query string. */ - /* The entire data has already been loaded, so there is no nead to + /* The entire data has already been loaded, so there is no need to * call mg_read. We just need to split the query string into key-value * pairs. */ data = conn->request_info.query_string; diff --git a/vendor/CivetWeb/mod_http2.inl b/vendor/CivetWeb/http2.inl similarity index 97% rename from vendor/CivetWeb/mod_http2.inl rename to vendor/CivetWeb/http2.inl index daa3e998..cea695ed 100644 --- a/vendor/CivetWeb/mod_http2.inl +++ b/vendor/CivetWeb/http2.inl @@ -654,7 +654,7 @@ hpack_getnum(const uint8_t *buf, * the encoded string. */ static char * -hpack_decode(const uint8_t *buf, int *i, struct mg_context *ctx) +hpack_decode(const uint8_t *buf, int *i, int max_i, struct mg_context *ctx) { uint64_t byte_len64; int byte_len; @@ -670,6 +670,11 @@ hpack_decode(const uint8_t *buf, int *i, struct mg_context *ctx) byte_len = (int)byte_len64; bit_len = byte_len * 8; + /* check size */ + if ((*i) + byte_len > max_i) { + return NULL; + } + /* Now read the string */ if (!is_huff) { /* Not huffman encoded: Copy directly */ @@ -718,6 +723,10 @@ hpack_decode(const uint8_t *buf, int *i, struct mg_context *ctx) break; } } + if (bytesStored == sizeof(str)) { + /* too long */ + return 0; + } } } } @@ -820,7 +829,7 @@ hpack_encode(uint8_t *store, const char *load, int lower) static const char http2_pri[] = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; -static unsigned char http2_pri_len = 24; /* = strlen(http2_pri) */ +static const unsigned char http2_pri_len = 24; /* = strlen(http2_pri) */ /* Read and check the HTTP/2 primer/preface: @@ -829,17 +838,19 @@ static int is_valid_http2_primer(struct mg_connection *conn) { size_t pri_len = http2_pri_len; - char buf[32]; + char buf[32]; /* Buffer must hold 24 bytes primer */ - if (pri_len > sizeof(buf)) { - /* Should never be reached - the RFC primer has 24 bytes */ - return 0; - } int read_pri_len = mg_read(conn, buf, pri_len); - if ((read_pri_len != (int)pri_len) - || (0 != memcmp(buf, http2_pri, pri_len))) { + if (read_pri_len != (int)pri_len) { + /* Size does not match. + * This includes cases where mg_read returns error codes */ return 0; } + if (0 != memcmp(buf, http2_pri, pri_len)) { + /* Primer does not match */ + return 0; + } + /* Primer does match */ return 1; } @@ -850,7 +861,7 @@ is_valid_http2_primer(struct mg_connection *conn) (conn)->client.sock, \ (conn)->ssl, \ (const char *)(data), \ - (int)(len)); + (int)(len)) static void @@ -949,7 +960,7 @@ http2_send_response_headers(struct mg_connection *conn) uint16_t header_len = 0; int has_date = 0; int has_connection_header = 0; - int i; + int i, ok; if ((conn->status_code < 100) || (conn->status_code > 999)) { /* Invalid status: Set status to "Internal Server Error" */ @@ -1058,16 +1069,23 @@ http2_send_response_headers(struct mg_connection *conn) http2_header_frame[8] = (conn->http2.stream_id & 0xFFu); /* Send header frame */ - mg_xwrite(conn, http2_header_frame, 9); - mg_xwrite(conn, header_bin, header_len); - - DEBUG_TRACE("HTTP2 response header sent: stream %u", conn->http2.stream_id); - + ok = 1; + if (mg_xwrite(conn, http2_header_frame, 9) != 9) { + ok = 0; + } else if (mg_xwrite(conn, header_bin, header_len) != header_len) { + ok = 0; + } + if (ok) { + DEBUG_TRACE("HTTP2 response header sent: stream %u", + conn->http2.stream_id); + } else { + DEBUG_TRACE("HTTP2 response header sending error: stream %u", + conn->http2.stream_id); + } (void)has_connection_header; /* ignore for the moment */ - - return 42; /* TODO */ + return ok; } @@ -1420,8 +1438,13 @@ handle_http2(struct mg_connection *conn) /* Get Header name "key" */ if (idx == 0) { /* Index 0: Header name encoded in following bytes */ - key = hpack_decode(buf, &i, conn->phys_ctx); + key = + hpack_decode(buf, &i, (int)bytes_read, conn->phys_ctx); CHECK_LEAK_HDR_ALLOC(key); + if (!key) { + DEBUG_TRACE("HTTP2 key decoding error"); + goto clean_http2; + } } else if (/*(idx >= 15) &&*/ (idx <= 61)) { /* Take key name from predefined header table */ key = mg_strdup_ctx(hpack_predefined[idx].name, @@ -1480,8 +1503,16 @@ handle_http2(struct mg_connection *conn) } else { /* Read value from HTTP2 stream */ - val = hpack_decode(buf, &i, conn->phys_ctx); /* leak? */ + val = hpack_decode(buf, + &i, + (int)bytes_read, + conn->phys_ctx); /* leak? */ CHECK_LEAK_HDR_ALLOC(val); + if (!val) { + DEBUG_TRACE("HTTP2 value decoding error"); + mg_free((void *)key); + goto clean_http2; + } if (indexing) { /* Add to index */ @@ -1546,6 +1577,7 @@ handle_http2(struct mg_connection *conn) } else if (!strcmp(":path", key)) { conn->request_info.local_uri = val; conn->request_info.request_uri = val; + conn->request_info.local_uri_raw = val; } else if (!strcmp(":status", key)) { conn->status_code = atoi(val); } @@ -1803,7 +1835,7 @@ HPACK_TABLE_TEST() for (i = 0; i < 256; i++) { if (reverse_map[i] == -1) { - ck_abort_msg("reverse map at %i mising", i); + ck_abort_msg("reverse map at %i missing", i); } } diff --git a/vendor/CivetWeb/include/civetweb.h b/vendor/CivetWeb/include/civetweb.h index ab40c7c2..167c6a76 100644 --- a/vendor/CivetWeb/include/civetweb.h +++ b/vendor/CivetWeb/include/civetweb.h @@ -23,9 +23,9 @@ #ifndef CIVETWEB_HEADER_INCLUDED #define CIVETWEB_HEADER_INCLUDED -#define CIVETWEB_VERSION "1.15" +#define CIVETWEB_VERSION "1.16" #define CIVETWEB_VERSION_MAJOR (1) -#define CIVETWEB_VERSION_MINOR (15) +#define CIVETWEB_VERSION_MINOR (16) #define CIVETWEB_VERSION_PATCH (0) #ifndef CIVETWEB_API @@ -654,7 +654,7 @@ CIVETWEB_API void *mg_get_thread_pointer(const struct mg_connection *conn); or write to the connection. */ /* Note: An alternative is to use the init_connection callback instead to initialize the user connection data pointer. It is - reccomended to supply a pointer to some user defined data structure + recommended to supply a pointer to some user defined data structure as conn_data initializer in init_connection. In case it is required to change some data after the init_connection call, store another data pointer in the user defined data structure and modify that @@ -1128,7 +1128,7 @@ CIVETWEB_API int mg_get_var2(const char *data, required to increase this value at compile time. Parameters: - data: form encoded iput string. Will be modified by this function. + data: form encoded input string. Will be modified by this function. form_fields: output list of name/value-pairs. A buffer with a size specified by num_form_fields must be provided by the caller. @@ -1341,6 +1341,22 @@ CIVETWEB_API int mg_url_decode(const char *src, CIVETWEB_API int mg_url_encode(const char *src, char *dst, size_t dst_len); +/* BASE64-encode input buffer into destination buffer. + returns -1 on OK. */ +CIVETWEB_API int mg_base64_encode(const unsigned char *src, + size_t src_len, + char *dst, + size_t *dst_len); + + +/* BASE64-decode input buffer into destination buffer. + returns -1 on OK. */ +CIVETWEB_API int mg_base64_decode(const char *src, + size_t src_len, + unsigned char *dst, + size_t *dst_len); + + /* MD5 hash given strings. Buffer 'buf' must be 33 bytes long. Varargs is a NULL terminated list of ASCIIz strings. When function returns, buf will contain human-readable @@ -1350,6 +1366,40 @@ CIVETWEB_API int mg_url_encode(const char *src, char *dst, size_t dst_len); CIVETWEB_API char *mg_md5(char buf[33], ...); +#if !defined(MG_MATCH_CONTEXT_MAX_MATCHES) +#define MG_MATCH_CONTEXT_MAX_MATCHES (32) +#endif + +struct mg_match_element { + const char *str; /* First character matching wildcard */ + size_t len; /* Number of character matching wildcard */ +}; + +struct mg_match_context { + int case_sensitive; /* Input: 1 (case sensitive) or 0 (insensitive) */ + size_t num_matches; /* Output: Number of wildcard matches returned. */ + struct mg_match_element match[MG_MATCH_CONTEXT_MAX_MATCHES]; /* Output */ +}; + + +#if defined(MG_EXPERIMENTAL_INTERFACES) +/* Pattern matching and extraction function. + Parameters: + pat: Pattern string (see UserManual.md) + str: String to search for match patterns. + mcx: Match context (optional, can be NULL). + + Return: + Number of characters matched. + -1 if no valid match was found. + Note: 0 characters might be a valid match for some patterns. +*/ +CIVETWEB_API ptrdiff_t mg_match(const char *pat, + const char *str, + struct mg_match_context *mcx); +#endif + + /* Print error message to the opened error log stream. This utilizes the provided logging configuration. conn: connection (not used for sending data, but to get perameters) @@ -1505,6 +1555,7 @@ CIVETWEB_API int mg_get_response(struct mg_connection *conn, * -1: parameter error * -2: invalid connection type * -3: invalid connection status + * -4: network error (only if built with NO_RESPONSE_BUFFERING) */ CIVETWEB_API int mg_response_header_start(struct mg_connection *conn, int status); @@ -1557,6 +1608,7 @@ CIVETWEB_API int mg_response_header_add_lines(struct mg_connection *conn, * -1: parameter error * -2: invalid connection type * -3: invalid connection status + * -4: sending failed (network error) */ CIVETWEB_API int mg_response_header_send(struct mg_connection *conn); @@ -1600,7 +1652,7 @@ CIVETWEB_API unsigned mg_check_feature(unsigned feature); buffer: Store system information as string here. buflen: Length of buffer (including a byte required for a terminating 0). Return: - Available size of system information, exluding a terminating 0. + Available size of system information, excluding a terminating 0. The information is complete, if the return value is smaller than buflen. The result is a JSON formatted string, the exact content may vary. Note: @@ -1617,7 +1669,7 @@ CIVETWEB_API int mg_get_system_info(char *buffer, int buflen); buffer: Store context information here. buflen: Length of buffer (including a byte required for a terminating 0). Return: - Available size of system information, exluding a terminating 0. + Available size of system information, excluding a terminating 0. The information is complete, if the return value is smaller than buflen. The result is a JSON formatted string, the exact content may vary. Note: @@ -1646,7 +1698,7 @@ CIVETWEB_API void mg_disable_connection_keep_alive(struct mg_connection *conn); buffer: Store context information here. buflen: Length of buffer (including a byte required for a terminating 0). Return: - Available size of system information, exluding a terminating 0. + Available size of system information, excluding a terminating 0. The information is complete, if the return value is smaller than buflen. The result is a JSON formatted string, the exact content may vary. Note: @@ -1669,11 +1721,80 @@ CIVETWEB_API int mg_get_connection_info(const struct mg_context *ctx, Note: Experimental interfaces may change */ struct mg_error_data { - unsigned *code; /* error code (number) */ + unsigned code; /* error code (number) */ + unsigned code_sub; /* error sub code (number) */ char *text; /* buffer for error text */ size_t text_buffer_size; /* size of buffer of "text" */ }; + +/* Values for error "code" in mg_error_data */ +enum { + /* No error */ + MG_ERROR_DATA_CODE_OK = 0u, + + /* Caller provided invalid parameter */ + MG_ERROR_DATA_CODE_INVALID_PARAM = 1u, + + /* "configuration_option" contains invalid element */ + MG_ERROR_DATA_CODE_INVALID_OPTION = 2u, + + /* Initializen TLS / SSL library failed */ + MG_ERROR_DATA_CODE_INIT_TLS_FAILED = 3u, + + /* Mandatory "configuration_option" missing */ + MG_ERROR_DATA_CODE_MISSING_OPTION = 4u, + + /* Duplicate "authentication_domain" option */ + MG_ERROR_DATA_CODE_DUPLICATE_DOMAIN = 5u, + + /* Not enough memory */ + MG_ERROR_DATA_CODE_OUT_OF_MEMORY = 6u, + + /* Server already stopped */ + MG_ERROR_DATA_CODE_SERVER_STOPPED = 7u, + + /* mg_init_library must be called first */ + MG_ERROR_DATA_CODE_INIT_LIBRARY_FAILED = 8u, + + /* Operating system function failed */ + MG_ERROR_DATA_CODE_OS_ERROR = 9u, + + /* Failed to bind to server ports */ + MG_ERROR_DATA_CODE_INIT_PORTS_FAILED = 10u, + + /* Failed to switch user (option "run_as_user") */ + MG_ERROR_DATA_CODE_INIT_USER_FAILED = 11u, + + /* Access Control List error */ + MG_ERROR_DATA_CODE_INIT_ACL_FAILED = 12u, + + /* Global password file error */ + MG_ERROR_DATA_CODE_INVALID_PASS_FILE = 13u, + + /* Lua background script init error */ + MG_ERROR_DATA_CODE_SCRIPT_ERROR = 14u, + + /* Client: Host not found, invalid IP to connect */ + MG_ERROR_DATA_CODE_HOST_NOT_FOUND = 15u, + + /* Client: TCP connect timeout */ + MG_ERROR_DATA_CODE_CONNECT_TIMEOUT = 16u, + + /* Client: TCP connect failed */ + MG_ERROR_DATA_CODE_CONNECT_FAILED = 17u, + + /* Error using TLS client certificate */ + MG_ERROR_DATA_CODE_TLS_CLIENT_CERT_ERROR = 18u, + + /* Error setting trusted TLS server certificate for client connection */ + MG_ERROR_DATA_CODE_TLS_SERVER_CERT_ERROR = 19u, + + /* Error establishing TLS connection to HTTPS server */ + MG_ERROR_DATA_CODE_TLS_CONNECT_ERROR = 20u +}; + + struct mg_init_data { const struct mg_callbacks *callbacks; /* callback function pointer */ void *user_data; /* data */ diff --git a/vendor/CivetWeb/match.inl b/vendor/CivetWeb/match.inl new file mode 100644 index 00000000..dd88f6fb --- /dev/null +++ b/vendor/CivetWeb/match.inl @@ -0,0 +1,262 @@ +/* Reimplementation of pattern matching */ +/* This file is part of the CivetWeb web server. + * See https://github.com/civetweb/civetweb/ + */ + + +/* Initialize structure with 0 matches */ +static void +match_context_reset(struct mg_match_context *mcx) +{ + mcx->num_matches = 0; + memset(mcx->match, 0, sizeof(mcx->match)); +} + + +/* Add a new match to the list of matches */ +static void +match_context_push(const char *str, size_t len, struct mg_match_context *mcx) +{ + if (mcx->num_matches < MG_MATCH_CONTEXT_MAX_MATCHES) { + mcx->match[mcx->num_matches].str = str; + mcx->match[mcx->num_matches].len = len; + mcx->num_matches++; + } +} + + +static ptrdiff_t +mg_match_impl(const char *pat, + size_t pat_len, + const char *str, + struct mg_match_context *mcx) +{ + /* Parse string */ + size_t i_pat = 0; /* Pattern index */ + size_t i_str = 0; /* Pattern index */ + + int case_sensitive = ((mcx != NULL) ? mcx->case_sensitive : 0); /* 0 or 1 */ + + while (i_pat < pat_len) { + + /* Pattern ? matches one character, except / and NULL character */ + if ((pat[i_pat] == '?') && (str[i_str] != '\0') + && (str[i_str] != '/')) { + size_t i_str_start = i_str; + do { + /* Advance as long as there are ? */ + i_pat++; + i_str++; + } while ((pat[i_pat] == '?') && (str[i_str] != '\0') + && (str[i_str] != '/') && (i_pat < pat_len)); + + /* If we have a match context, add the substring we just found */ + if (mcx) { + match_context_push(str + i_str_start, i_str - i_str_start, mcx); + } + + /* Reached end of pattern ? */ + if (i_pat == pat_len) { + return (ptrdiff_t)i_str; + } + } + + /* Pattern $ matches end of string */ + if (pat[i_pat] == '$') { + return (str[i_str] == '\0') ? (ptrdiff_t)i_str : -1; + } + + /* Pattern * or ** matches multiple characters */ + if (pat[i_pat] == '*') { + size_t len; /* length matched by "*" or "**" */ + ptrdiff_t ret; + + i_pat++; + if ((pat[i_pat] == '*') && (i_pat < pat_len)) { + /* Pattern ** matches all */ + i_pat++; + len = strlen(str + i_str); + } else { + /* Pattern * matches all except / character */ + len = strcspn(str + i_str, "/"); + } + + if (i_pat == pat_len) { + /* End of pattern reached. Add all to match context. */ + if (mcx) { + match_context_push(str + i_str, len, mcx); + } + return (i_str + len); + } + + /* This loop searches for the longest possible match */ + do { + ret = mg_match_impl(pat + i_pat, + (pat_len - (size_t)i_pat), + str + i_str + len, + mcx); + } while ((ret == -1) && (len-- > 0)); + + /* If we have a match context, add the substring we just found */ + if (ret >= 0) { + if (mcx) { + match_context_push(str + i_str, len, mcx); + } + return (i_str + ret + len); + } + + return -1; + } + + + /* Single character compare */ + if (case_sensitive) { + if (pat[i_pat] != str[i_str]) { + /* case sensitive compare: mismatch */ + return -1; + } + } else if (lowercase(&pat[i_pat]) != lowercase(&str[i_str])) { + /* case insensitive compare: mismatch */ + return -1; + } + + i_pat++; + i_str++; + } + return (ptrdiff_t)i_str; +} + + +static ptrdiff_t +mg_match_alternatives(const char *pat, + size_t pat_len, + const char *str, + struct mg_match_context *mcx) +{ + const char *match_alternative = (const char *)memchr(pat, '|', pat_len); + + if (mcx != NULL) { + match_context_reset(mcx); + } + + while (match_alternative != NULL) { + /* Split at | for alternative match */ + size_t left_size = (size_t)(match_alternative - pat); + + /* Try left string first */ + ptrdiff_t ret = mg_match_impl(pat, left_size, str, mcx); + if (ret >= 0) { + /* A 0-byte match is also valid */ + return ret; + } + + /* Reset possible incomplete match data */ + if (mcx != NULL) { + match_context_reset(mcx); + } + + /* If no match: try right side */ + pat += left_size + 1; + pat_len -= left_size + 1; + match_alternative = (const char *)memchr(pat, '|', pat_len); + } + + /* Handled all | operators. This is the final string. */ + return mg_match_impl(pat, pat_len, str, mcx); +} + + +static int +match_compare(const void *p1, const void *p2, void *user) +{ + const struct mg_match_element *e1 = (const struct mg_match_element *)p1; + const struct mg_match_element *e2 = (const struct mg_match_element *)p2; + + /* unused */ + (void)user; + + if (e1->str > e2->str) { + return +1; + } + if (e1->str < e2->str) { + return -1; + } + return 0; +} + + +#if defined(MG_EXPERIMENTAL_INTERFACES) +CIVETWEB_API +#else +static +#endif +ptrdiff_t +mg_match(const char *pat, const char *str, struct mg_match_context *mcx) +{ + size_t pat_len = strlen(pat); + ptrdiff_t ret = mg_match_alternatives(pat, pat_len, str, mcx); + if (mcx != NULL) { + if (ret < 0) { + /* Remove possible incomplete data */ + match_context_reset(mcx); + } else { + /* Join "?*" to one pattern. */ + size_t i, j; + + /* Use difference of two array elements instead of sizeof, since + * there may be some additional padding bytes. */ + size_t elmsize = + (size_t)(&mcx->match[1]) - (size_t)(&mcx->match[0]); + + /* First sort the matches by address ("str" begin to end) */ + mg_sort(mcx->match, mcx->num_matches, elmsize, match_compare, NULL); + + /* Join consecutive matches */ + i = 1; + while (i < mcx->num_matches) { + if ((mcx->match[i - 1].str + mcx->match[i - 1].len) + == mcx->match[i].str) { + /* Two matches are consecutive. Join length. */ + mcx->match[i - 1].len += mcx->match[i].len; + + /* Shift all list elements. */ + for (j = i + 1; j < mcx->num_matches; j++) { + mcx->match[j - 1].len = mcx->match[j].len; + mcx->match[j - 1].str = mcx->match[j].str; + } + + /* Remove/blank last list element. */ + mcx->num_matches--; + mcx->match[mcx->num_matches].str = NULL; + mcx->match[mcx->num_matches].len = 0; + + } else { + i++; + } + } + } + } + return ret; +} + + +static ptrdiff_t +match_prefix(const char *pattern, size_t pattern_len, const char *str) +{ + if (pattern == NULL) { + return -1; + } + return mg_match_alternatives(pattern, pattern_len, str, NULL); +} + + +static ptrdiff_t +match_prefix_strlen(const char *pattern, const char *str) +{ + if (pattern == NULL) { + return -1; + } + return mg_match_alternatives(pattern, strlen(pattern), str, NULL); +} + +/* End of match.inl */ diff --git a/vendor/CivetWeb/md5.inl b/vendor/CivetWeb/md5.inl index b2f5f971..5dd3a601 100644 --- a/vendor/CivetWeb/md5.inl +++ b/vendor/CivetWeb/md5.inl @@ -131,6 +131,7 @@ MD5_STATIC void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); */ #if !defined(MD5_STATIC) +#include #include #endif @@ -239,7 +240,7 @@ md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) * On little-endian machines, we can process properly aligned * data without copying it. */ - if (!((data - (const md5_byte_t *)0) & 3)) { + if (!(((uintptr_t)data) & 3)) { /* data are properly aligned, a direct assignment is possible */ /* cast through a (void *) should avoid a compiler warning, see diff --git a/vendor/CivetWeb/mod_zlib.inl b/vendor/CivetWeb/mod_zlib.inl index 2c420944..068ec77a 100644 --- a/vendor/CivetWeb/mod_zlib.inl +++ b/vendor/CivetWeb/mod_zlib.inl @@ -12,7 +12,7 @@ zalloc(void *opaque, uInt items, uInt size) { struct mg_connection *conn = (struct mg_connection *)opaque; void *ret = mg_calloc_ctx(items, size, conn->phys_ctx); - (void)conn; /* mg_calloc_ctx makro might not need it */ + (void)conn; /* mg_calloc_ctx macro might not need it */ return ret; } diff --git a/vendor/CivetWeb/response.inl b/vendor/CivetWeb/response.inl index 0c7c00ce..35e6ba82 100644 --- a/vendor/CivetWeb/response.inl +++ b/vendor/CivetWeb/response.inl @@ -37,7 +37,7 @@ free_buffered_response_header_list(struct mg_connection *conn) /* Send first line of HTTP/1.x response */ -static void +static int send_http1_response_status_line(struct mg_connection *conn) { const char *status_txt; @@ -55,7 +55,13 @@ send_http1_response_status_line(struct mg_connection *conn) /* mg_get_response_code_text will never return NULL */ status_txt = mg_get_response_code_text(conn, conn->status_code); - mg_printf(conn, "HTTP/%s %i %s\r\n", http_version, status_code, status_txt); + if (mg_printf( + conn, "HTTP/%s %i %s\r\n", http_version, status_code, status_txt) + < 10) { + /* Network sending failed */ + return 0; + } + return 1; } @@ -68,10 +74,12 @@ send_http1_response_status_line(struct mg_connection *conn) * -1: parameter error * -2: invalid connection type * -3: invalid connection status + * -4: network error (only if built with NO_RESPONSE_BUFFERING) */ int mg_response_header_start(struct mg_connection *conn, int status) { + int ret = 0; if ((conn == NULL) || (status < 100) || (status > 999)) { /* Parameter error */ return -1; @@ -93,11 +101,13 @@ mg_response_header_start(struct mg_connection *conn, int status) #if !defined(NO_RESPONSE_BUFFERING) free_buffered_response_header_list(conn); #else - send_http1_response_status_line(conn); + if (!send_http1_response_status_line(conn)) { + ret = -4; + }; conn->request_state = 1; /* Reset from 10 to 1 */ #endif - return 0; + return ret; } @@ -254,6 +264,7 @@ static int http2_send_response_headers(struct mg_connection *conn); * -1: parameter error * -2: invalid connection type * -3: invalid connection status + * -4: network send failed */ int mg_response_header_send(struct mg_connection *conn) @@ -285,12 +296,16 @@ mg_response_header_send(struct mg_connection *conn) #if defined(USE_HTTP2) if (conn->protocol_type == PROTOCOL_TYPE_HTTP2) { int ret = http2_send_response_headers(conn); - return ret ? 0 : 0; /* todo */ + free_buffered_response_header_list(conn); + return (ret ? 0 : -4); } #endif /* Send */ - send_http1_response_status_line(conn); + if (!send_http1_response_status_line(conn)) { + free_buffered_response_header_list(conn); + return -4; + }; for (i = 0; i < conn->response_info.num_headers; i++) { mg_printf(conn, "%s: %s\r\n", @@ -322,5 +337,6 @@ mg_response_header_send(struct mg_connection *conn) conn->request_state = 3; /* ok */ + free_buffered_response_header_list(conn); return 0; } diff --git a/vendor/CivetWeb/sort.inl b/vendor/CivetWeb/sort.inl new file mode 100644 index 00000000..59180541 --- /dev/null +++ b/vendor/CivetWeb/sort.inl @@ -0,0 +1,48 @@ +/* Sort function. */ +/* from https://github.com/bel2125/sort_r */ + +static void +mg_sort(void *data, + size_t elemcount, + size_t elemsize, + int (*compfunc)(const void *data1, const void *data2, void *userarg), + void *userarg) +{ + /* We cannot use qsort_r here. For a detailed reason, see + * https://github.com/civetweb/civetweb/issues/1048#issuecomment-1047093014 + * https://stackoverflow.com/questions/39560773/different-declarations-of-qsort-r-on-mac-and-linux + */ + + /* We use ShellSort here with this gap sequence: https://oeis.org/A102549 */ + int A102549[9] = {1, 4, 10, 23, 57, 132, 301, 701, 1750}; + int Aidx, gap, i, j, k; + void *tmp = alloca(elemsize); + + for (Aidx = 8; Aidx >= 0; Aidx--) { + gap = A102549[Aidx]; + if (gap > ((int)elemcount / 2)) { + continue; + } + for (i = 0; i < gap; i++) { + for (j = i; j < (int)elemcount; j += gap) { + memcpy(tmp, (void *)((ptrdiff_t)data + elemsize * j), elemsize); + + for (k = j; k >= gap; k -= gap) { + void *cmp = + (void *)((ptrdiff_t)data + elemsize * (k - gap)); + int cmpres = compfunc(cmp, tmp, userarg); + if (cmpres > 0) { + memcpy((void *)((ptrdiff_t)data + elemsize * k), + cmp, + elemsize); + } else { + break; + } + } + memcpy((void *)((ptrdiff_t)data + elemsize * k), tmp, elemsize); + } + } + } +} + +/* end if sort.inl */