diff --git a/configure.ac b/configure.ac index b4a8df9f..eacc24af 100644 --- a/configure.ac +++ b/configure.ac @@ -530,7 +530,7 @@ AC_CHECK_HEADERS([sys/socket.h sys/select.h netinet/in_systm.h netinet/in.h \ # Check for other optional headers AC_CHECK_HEADERS([sys/msg.h sys/mman.h signal.h], [], [], [AC_INCLUDES_DEFAULT]) -AC_CHECK_TYPES([size_t,ssize_t,ptrdiff_t,intptr_t,uintptr_t],[],[], +AC_CHECK_TYPES([size_t,ssize_t,ptrdiff_t,intptr_t,uintptr_t,uint8_t],[],[], [[ /* Keep in sync with src/mhd2/sys_base_types.h */ #include /* uint_fast_XXt, int_fast_XXt */ @@ -554,6 +554,14 @@ AC_CHECK_TYPES([size_t,ssize_t,ptrdiff_t,intptr_t,uintptr_t],[],[], AS_IF([test "x$ac_cv_type_size_t" != "xyes"], [AC_MSG_FAILURE(['size_t' type is not provided by system headers])] ) +AS_IF([test "x$ac_cv_type_uint8_t" != "xyes"], + [AC_MSG_FAILURE(['uint8_t' type is not provided by system headers])] +) +AC_CHECK_SIZEOF([char]) +AS_IF([test "x$ac_cv_sizeof_char" != "x1"], + [AC_MSG_FAILURE(['char' type with size different from '1' is not supported])] +) + AC_CHECK_TYPE([socklen_t], [], diff --git a/src/include/microhttpd2.h b/src/include/microhttpd2.h index 92289663..b1e1b2b2 100644 --- a/src/include/microhttpd2.h +++ b/src/include/microhttpd2.h @@ -475,11 +475,11 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode MHD_SC_UPGRADE_FORWARD_INCOMPLETE = 30100 , /** - * We failed to allocate memory for generating the response from our - * memory pool. Likely the request header was too large to leave + * Failed to allocate memory from our memory pool for processing + * the request. Likely the request fields are too large to leave * enough room. */ - MHD_SC_CONNECTION_POOL_MALLOC_FAILURE = 30130 + MHD_SC_CONNECTION_POOL_MALLOC_FAILURE_REQ = 30130 , /* 40000-level errors are caused by the HTTP client @@ -528,6 +528,31 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode */ MHD_SC_CHUNKED_ENCODING_MALFORMED = 40007 , + /** + * The first header line has whitespace at the start + */ + MHD_SC_REQ_FIRST_HEADER_LINE_SPACE_PREFIXED = 40100 + , + /** + * Wrong bare CR characters has been replaced with space. + */ + MHD_SC_REQ_HEADER_CR_REPLACED = 40120 + , + /** + * Header line has not colon and skipped. + */ + MHD_SC_REQ_HEADER_LINE_NO_COLON = 40121 + , + /** + * Wrong bare CR characters has been replaced with space. + */ + MHD_SC_REQ_FOOTER_CR_REPLACED = 40140 + , + /** + * Footer line has not colon and skipped. + */ + MHD_SC_REQ_FOOTER_LINE_NO_COLON = 40141 + , /* 50000-level errors are because of an error internal to the MHD logic, possibly including our interaction @@ -1004,6 +1029,27 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode */ MHD_SC_RESPONSE_HEADER_MALLOC_FAILED = 50540 , + /** + * Failed to switch TCP_NODELAY option for the socket + */ + MHD_SC_SOCKET_TCP_NODELAY_FAILED = 50600 + , + /** + * Failed to switch TCP_CORK or TCP_NOPUSH option for the socket + */ + MHD_SC_SOCKET_TCP_CORK_NOPUSH_FAILED = 50601 + , + /** + * Failed to force flush the last part of the response header or + * the response content + */ + MHD_SC_SOCKET_FLUSH_LAST_PART_FAILED = 50620 + , + /** + * Failed to push buffered data by zero-sized send() + */ + MHD_SC_SOCKET_ZERO_SEND_FAILED = 50621 + , /** * Something wrong in the internal MHD logic. * This error should be never returned if MHD works as expected. @@ -4672,7 +4718,8 @@ MHD_FN_PAR_NONNULL_ (4) MHD_FN_PAR_OUT_SIZE_ (4,3); * * @param request request to get values from * @param kind what kind of value are we looking for - * @param key the header to look for, NULL to lookup 'trailing' value without a key + * @param key the header to look for, empty to lookup 'trailing' value + * without a key * @return NULL if no such item was found * @ingroup request */ @@ -4690,140 +4737,220 @@ MHD_FN_PAR_NONNULL_ (3) MHD_FN_PAR_CSTR_ (3); * @defgroup httpcode HTTP response codes * @{ */ +/* Registry export date: 2023-09-29 */ /* See http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml */ enum MHD_FIXED_ENUM_APP_SET_ MHD_HTTP_StatusCode { + /* 100 "Continue". RFC9110, Section 15.2.1. */ + MHD_HTTP_STATUS_CONTINUE = 100 + , + /* 101 "Switching Protocols". RFC9110, Section 15.2.2. */ + MHD_HTTP_STATUS_SWITCHING_PROTOCOLS = 101 + , + /* 102 "Processing". RFC2518. */ + MHD_HTTP_STATUS_PROCESSING = 102 + , + /* 103 "Early Hints". RFC8297. */ + MHD_HTTP_STATUS_EARLY_HINTS = 103 + , - MHD_HTTP_STATUS_CONTINUE = 100 + /* 200 "OK". RFC9110, Section 15.3.1. */ + MHD_HTTP_STATUS_OK = 200 , - MHD_HTTP_STATUS_SWITCHING_PROTOCOLS = 101 + /* 201 "Created". RFC9110, Section 15.3.2. */ + MHD_HTTP_STATUS_CREATED = 201 , - MHD_HTTP_STATUS_PROCESSING = 102 - , - MHD_HTTP_STATUS_OK = 200 - , - MHD_HTTP_STATUS_CREATED = 201 - , - MHD_HTTP_STATUS_ACCEPTED = 202 + /* 202 "Accepted". RFC9110, Section 15.3.3. */ + MHD_HTTP_STATUS_ACCEPTED = 202 , + /* 203 "Non-Authoritative Information". RFC9110, Section 15.3.4. */ MHD_HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION = 203 , - MHD_HTTP_STATUS_NO_CONTENT = 204 + /* 204 "No Content". RFC9110, Section 15.3.5. */ + MHD_HTTP_STATUS_NO_CONTENT = 204 , - MHD_HTTP_STATUS_RESET_CONTENT = 205 + /* 205 "Reset Content". RFC9110, Section 15.3.6. */ + MHD_HTTP_STATUS_RESET_CONTENT = 205 , - MHD_HTTP_STATUS_PARTIAL_CONTENT = 206 + /* 206 "Partial Content". RFC9110, Section 15.3.7. */ + MHD_HTTP_STATUS_PARTIAL_CONTENT = 206 , - MHD_HTTP_STATUS_MULTI_STATUS = 207 + /* 207 "Multi-Status". RFC4918. */ + MHD_HTTP_STATUS_MULTI_STATUS = 207 , - MHD_HTTP_STATUS_ALREADY_REPORTED = 208 + /* 208 "Already Reported". RFC5842. */ + MHD_HTTP_STATUS_ALREADY_REPORTED = 208 , - MHD_HTTP_STATUS_IM_USED = 226 + + /* 226 "IM Used". RFC3229. */ + MHD_HTTP_STATUS_IM_USED = 226 , - MHD_HTTP_STATUS_MULTIPLE_CHOICES = 300 + + /* 300 "Multiple Choices". RFC9110, Section 15.4.1. */ + MHD_HTTP_STATUS_MULTIPLE_CHOICES = 300 , - MHD_HTTP_STATUS_MOVED_PERMANENTLY = 301 + /* 301 "Moved Permanently". RFC9110, Section 15.4.2. */ + MHD_HTTP_STATUS_MOVED_PERMANENTLY = 301 , - MHD_HTTP_STATUS_FOUND = 302 + /* 302 "Found". RFC9110, Section 15.4.3. */ + MHD_HTTP_STATUS_FOUND = 302 , - MHD_HTTP_STATUS_SEE_OTHER = 303 + /* 303 "See Other". RFC9110, Section 15.4.4. */ + MHD_HTTP_STATUS_SEE_OTHER = 303 , - MHD_HTTP_STATUS_NOT_MODIFIED = 304 + /* 304 "Not Modified". RFC9110, Section 15.4.5. */ + MHD_HTTP_STATUS_NOT_MODIFIED = 304 , - MHD_HTTP_STATUS_USE_PROXY = 305 + /* 305 "Use Proxy". RFC9110, Section 15.4.6. */ + MHD_HTTP_STATUS_USE_PROXY = 305 , - MHD_HTTP_STATUS_SWITCH_PROXY = 306 /* IANA: unused */ + /* 306 "Switch Proxy". Not used! RFC9110, Section 15.4.7. */ + MHD_HTTP_STATUS_SWITCH_PROXY = 306 , - MHD_HTTP_STATUS_TEMPORARY_REDIRECT = 307 + /* 307 "Temporary Redirect". RFC9110, Section 15.4.8. */ + MHD_HTTP_STATUS_TEMPORARY_REDIRECT = 307 , - MHD_HTTP_STATUS_PERMANENT_REDIRECT = 308 + /* 308 "Permanent Redirect". RFC9110, Section 15.4.9. */ + MHD_HTTP_STATUS_PERMANENT_REDIRECT = 308 , - MHD_HTTP_STATUS_BAD_REQUEST = 400 + + /* 400 "Bad Request". RFC9110, Section 15.5.1. */ + MHD_HTTP_STATUS_BAD_REQUEST = 400 , - MHD_HTTP_STATUS_UNAUTHORIZED = 401 + /* 401 "Unauthorized". RFC9110, Section 15.5.2. */ + MHD_HTTP_STATUS_UNAUTHORIZED = 401 , - MHD_HTTP_STATUS_PAYMENT_REQUIRED = 402 + /* 402 "Payment Required". RFC9110, Section 15.5.3. */ + MHD_HTTP_STATUS_PAYMENT_REQUIRED = 402 , - MHD_HTTP_STATUS_FORBIDDEN = 403 + /* 403 "Forbidden". RFC9110, Section 15.5.4. */ + MHD_HTTP_STATUS_FORBIDDEN = 403 , - MHD_HTTP_STATUS_NOT_FOUND = 404 + /* 404 "Not Found". RFC9110, Section 15.5.5. */ + MHD_HTTP_STATUS_NOT_FOUND = 404 , - MHD_HTTP_STATUS_METHOD_NOT_ALLOWED = 405 + /* 405 "Method Not Allowed". RFC9110, Section 15.5.6. */ + MHD_HTTP_STATUS_METHOD_NOT_ALLOWED = 405 , - MHD_HTTP_STATUS_NOT_ACCEPTABLE = 406 + /* 406 "Not Acceptable". RFC9110, Section 15.5.7. */ + MHD_HTTP_STATUS_NOT_ACCEPTABLE = 406 , + /* 407 "Proxy Authentication Required". RFC9110, Section 15.5.8. */ MHD_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED = 407 , - MHD_HTTP_STATUS_REQUEST_TIMEOUT = 408 + /* 408 "Request Timeout". RFC9110, Section 15.5.9. */ + MHD_HTTP_STATUS_REQUEST_TIMEOUT = 408 , - MHD_HTTP_STATUS_CONFLICT = 409 + /* 409 "Conflict". RFC9110, Section 15.5.10. */ + MHD_HTTP_STATUS_CONFLICT = 409 , - MHD_HTTP_STATUS_GONE = 410 + /* 410 "Gone". RFC9110, Section 15.5.11. */ + MHD_HTTP_STATUS_GONE = 410 , - MHD_HTTP_STATUS_LENGTH_REQUIRED = 411 + /* 411 "Length Required". RFC9110, Section 15.5.12. */ + MHD_HTTP_STATUS_LENGTH_REQUIRED = 411 , - MHD_HTTP_STATUS_PRECONDITION_FAILED = 412 + /* 412 "Precondition Failed". RFC9110, Section 15.5.13. */ + MHD_HTTP_STATUS_PRECONDITION_FAILED = 412 , - MHD_HTTP_STATUS_PAYLOAD_TOO_LARGE = 413 + /* 413 "Content Too Large". RFC9110, Section 15.5.14. */ + MHD_HTTP_STATUS_CONTENT_TOO_LARGE = 413 , - MHD_HTTP_STATUS_URI_TOO_LONG = 414 + /* 414 "URI Too Long". RFC9110, Section 15.5.15. */ + MHD_HTTP_STATUS_URI_TOO_LONG = 414 , - MHD_HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE = 415 + /* 415 "Unsupported Media Type". RFC9110, Section 15.5.16. */ + MHD_HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE = 415 , - MHD_HTTP_STATUS_RANGE_NOT_SATISFIABLE = 416 + /* 416 "Range Not Satisfiable". RFC9110, Section 15.5.17. */ + MHD_HTTP_STATUS_RANGE_NOT_SATISFIABLE = 416 , - MHD_HTTP_STATUS_EXPECTATION_FAILED = 417 + /* 417 "Expectation Failed". RFC9110, Section 15.5.18. */ + MHD_HTTP_STATUS_EXPECTATION_FAILED = 417 , - MHD_HTTP_STATUS_MISDIRECTED_REQUEST = 421 + + + /* 421 "Misdirected Request". RFC9110, Section 15.5.20. */ + MHD_HTTP_STATUS_MISDIRECTED_REQUEST = 421 , - MHD_HTTP_STATUS_UNPROCESSABLE_ENTITY = 422 + /* 422 "Unprocessable Content". RFC9110, Section 15.5.21. */ + MHD_HTTP_STATUS_UNPROCESSABLE_CONTENT = 422 , - MHD_HTTP_STATUS_LOCKED = 423 + /* 423 "Locked". RFC4918. */ + MHD_HTTP_STATUS_LOCKED = 423 , - MHD_HTTP_STATUS_FAILED_DEPENDENCY = 424 + /* 424 "Failed Dependency". RFC4918. */ + MHD_HTTP_STATUS_FAILED_DEPENDENCY = 424 , - MHD_HTTP_STATUS_UNORDERED_COLLECTION = 425 /* IANA: unused */ + /* 425 "Too Early". RFC8470. */ + MHD_HTTP_STATUS_TOO_EARLY = 425 , - MHD_HTTP_STATUS_UPGRADE_REQUIRED = 426 + /* 426 "Upgrade Required". RFC9110, Section 15.5.22. */ + MHD_HTTP_STATUS_UPGRADE_REQUIRED = 426 , - MHD_HTTP_STATUS_PRECONDITION_REQUIRED = 428 + + /* 428 "Precondition Required". RFC6585. */ + MHD_HTTP_STATUS_PRECONDITION_REQUIRED = 428 , - MHD_HTTP_STATUS_TOO_MANY_REQUESTS = 429 + /* 429 "Too Many Requests". RFC6585. */ + MHD_HTTP_STATUS_TOO_MANY_REQUESTS = 429 , + + /* 431 "Request Header Fields Too Large". RFC6585. */ MHD_HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431 , - MHD_HTTP_STATUS_NO_RESPONSE = 444 /* IANA: unused */ - , - MHD_HTTP_STATUS_RETRY_WITH = 449 /* IANA: unused */ - , - MHD_HTTP_STATUS_BLOCKED_BY_WINDOWS_PARENTAL_CONTROLS = 450 /* IANA: unused */ - , + + /* 451 "Unavailable For Legal Reasons". RFC7725. */ MHD_HTTP_STATUS_UNAVAILABLE_FOR_LEGAL_REASONS = 451 , - MHD_HTTP_STATUS_INTERNAL_SERVER_ERROR = 500 - , - MHD_HTTP_STATUS_NOT_IMPLEMENTED = 501 - , - MHD_HTTP_STATUS_BAD_GATEWAY = 502 - , - MHD_HTTP_STATUS_SERVICE_UNAVAILABLE = 503 - , - MHD_HTTP_STATUS_GATEWAY_TIMEOUT = 504 - , - MHD_HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED = 505 - , - MHD_HTTP_STATUS_VARIANT_ALSO_NEGOTIATES = 506 - , - MHD_HTTP_STATUS_INSUFFICIENT_STORAGE = 507 - , - MHD_HTTP_STATUS_LOOP_DETECTED = 508 - , - MHD_HTTP_STATUS_BANDWIDTH_LIMIT_EXCEEDED = 509 /* IANA: unused */ - , - MHD_HTTP_STATUS_NOT_EXTENDED = 510 - , - MHD_HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED = 511 + /* 500 "Internal Server Error". RFC9110, Section 15.6.1. */ + MHD_HTTP_STATUS_INTERNAL_SERVER_ERROR = 500 + , + /* 501 "Not Implemented". RFC9110, Section 15.6.2. */ + MHD_HTTP_STATUS_NOT_IMPLEMENTED = 501 + , + /* 502 "Bad Gateway". RFC9110, Section 15.6.3. */ + MHD_HTTP_STATUS_BAD_GATEWAY = 502 + , + /* 503 "Service Unavailable". RFC9110, Section 15.6.4. */ + MHD_HTTP_STATUS_SERVICE_UNAVAILABLE = 503 + , + /* 504 "Gateway Timeout". RFC9110, Section 15.6.5. */ + MHD_HTTP_STATUS_GATEWAY_TIMEOUT = 504 + , + /* 505 "HTTP Version Not Supported". RFC9110, Section 15.6.6. */ + MHD_HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED = 505 + , + /* 506 "Variant Also Negotiates". RFC2295. */ + MHD_HTTP_STATUS_VARIANT_ALSO_NEGOTIATES = 506 + , + /* 507 "Insufficient Storage". RFC4918. */ + MHD_HTTP_STATUS_INSUFFICIENT_STORAGE = 507 + , + /* 508 "Loop Detected". RFC5842. */ + MHD_HTTP_STATUS_LOOP_DETECTED = 508 + , + + /* 510 "Not Extended". (OBSOLETED) RFC2774; status-change-http-experiments-to-historic. */ + MHD_HTTP_STATUS_NOT_EXTENDED = 510 + , + /* 511 "Network Authentication Required". RFC6585. */ + MHD_HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED = 511 + , + + + /* Not registered non-standard codes */ + /* 449 "Reply With". MS IIS extension. */ + MHD_HTTP_STATUS_RETRY_WITH = 449 + , + + /* 450 "Blocked by Windows Parental Controls". MS extension. */ + MHD_HTTP_STATUS_BLOCKED_BY_WINDOWS_PARENTAL_CONTROLS = 450 + , + + /* 509 "Bandwidth Limit Exceeded". Apache extension. */ + MHD_HTTP_STATUS_BANDWIDTH_LIMIT_EXCEEDED = 509 }; @@ -4864,9 +4991,9 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_HTTP_ProtocolVersion , MHD_HTTP_VERSION_1_1 = 2 , - MHD_HTTP_VERSION_2_0 = 3 + MHD_HTTP_VERSION_2 = 3 , - MHD_HTTP_VERSION_3_0 = 4 + MHD_HTTP_VERSION_3 = 4 , MHD_HTTP_VERSION_FUTURE = 255 }; @@ -4889,22 +5016,22 @@ MHD_FN_CONST_; /** * HTTP/1.0 identification string */ -#define MHD_HTTP_VERSION_1_0 "HTTP/1.0" +#define MHD_HTTP_VERSION_1_0_STR "HTTP/1.0" /** * HTTP/1.1 identification string */ -#define MHD_HTTP_VERSION_1_1 "HTTP/1.1" +#define MHD_HTTP_VERSION_1_1_STR "HTTP/1.1" /** * HTTP/2 identification string. * Not used by the HTTP protocol (except non-TLS handshake), useful for logs and * similar proposes. */ -#define MHD_HTTP_VERSION_2 "HTTP/2" +#define MHD_HTTP_VERSION_2_STR "HTTP/2" /** * HTTP/3 identification string. * Not used by the HTTP protocol, useful for logs and similar proposes. */ -#define MHD_HTTP_VERSION_3 "HTTP/3" +#define MHD_HTTP_VERSION_3_STR "HTTP/3" /** @} */ /* end of group versions */ @@ -4980,7 +5107,7 @@ struct MHD_Response; */ MHD_EXTERN_ const struct MHD_Action * MHD_action_suspend (struct MHD_Request *request) -MHD_FN_RETURNS_NONNULL_ MHD_FN_PAR_NONNULL_ALL_; +MHD_FN_PAR_NONNULL_ALL_; /** diff --git a/src/mhd2/action.c b/src/mhd2/action.c index e841b693..9f37cf12 100644 --- a/src/mhd2/action.c +++ b/src/mhd2/action.c @@ -29,13 +29,17 @@ #include "mhd_action.h" #include "mhd_request.h" #include "mhd_public_api.h" +#include "daemon_logger.h" -MHD_EXTERN_ MHD_FN_RETURNS_NONNULL_ MHD_FN_PAR_NONNULL_ALL_ +MHD_EXTERN_ MHD_FN_PAR_NONNULL_ALL_ const struct MHD_Action * MHD_action_suspend (struct MHD_Request *request) { struct MHD_Action *const restrict head_act = &(request->app_act.head_act); + if (mhd_UPLOAD_ACTION_NO_ACTION != head_act->act) + return (const struct MHD_Action *) NULL; + head_act->act = mhd_ACTION_SUSPEND; return head_act; } @@ -48,7 +52,7 @@ MHD_action_from_response (struct MHD_Request *request, { struct MHD_Action *const restrict head_act = &(request->app_act.head_act); if (mhd_ACTION_NO_ACTION != head_act->act) - return NULL; + return (const struct MHD_Action *) NULL; if (NULL == response) return (const struct MHD_Action *) NULL; @@ -70,18 +74,18 @@ MHD_action_process_upload (struct MHD_Request *request, { struct MHD_Action *const restrict head_act = &(request->app_act.head_act); if (mhd_ACTION_NO_ACTION != head_act->act) - return NULL; + return (const struct MHD_Action *) NULL; if (0 == large_buffer_size) { if (NULL != uc_full) - return NULL; + return (const struct MHD_Action *) NULL; if (NULL == uc_inc) - return NULL; + return (const struct MHD_Action *) NULL; } else { if (NULL == uc_full) - return NULL; + return (const struct MHD_Action *) NULL; } head_act->act = mhd_ACTION_UPLOAD; @@ -108,9 +112,9 @@ MHD_action_post_processor (struct MHD_Request *request, { struct MHD_Action *const restrict head_act = &(request->app_act.head_act); if (mhd_ACTION_NO_ACTION != head_act->act) - return NULL; + return (const struct MHD_Action *) NULL; if (NULL == done_cb) - return NULL; + return (const struct MHD_Action *) NULL; head_act->act = mhd_ACTION_POST_PROCESS; head_act->data.post_process.pp_buffer_size = pp_buffer_size; @@ -131,7 +135,7 @@ MHD_upload_action_suspend (struct MHD_Request *request) { struct MHD_Action *const restrict upl_act = &(request->app_act.upl_act); if (mhd_UPLOAD_ACTION_NO_ACTION != upl_act->act) - return NULL; + return (const struct MHD_UploadAction *) NULL; upl_act->act = mhd_UPLOAD_ACTION_SUSPEND; @@ -146,7 +150,7 @@ MHD_upload_action_from_response (struct MHD_Request *request, { struct MHD_Action *const restrict upl_act = &(request->app_act.upl_act); if (mhd_UPLOAD_ACTION_NO_ACTION != upl_act->act) - return NULL; + return (const struct MHD_UploadAction *) NULL; upl_act->act = mhd_UPLOAD_ACTION_SUSPEND; upl_act->data.response = response; @@ -160,7 +164,7 @@ MHD_upload_action_continue (struct MHD_Request *request) { struct MHD_Action *const restrict upl_act = &(request->app_act.upl_act); if (mhd_UPLOAD_ACTION_NO_ACTION != upl_act->act) - return NULL; + return (const struct MHD_UploadAction *) NULL; upl_act->act = mhd_UPLOAD_ACTION_CONTINUE; diff --git a/src/mhd2/conn_data_process.c b/src/mhd2/conn_data_process.c new file mode 100644 index 00000000..0b364d2b --- /dev/null +++ b/src/mhd2/conn_data_process.c @@ -0,0 +1,180 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2015-2024 Evgeny Grin (Karlson2k) + Copyright (C) 2007-2020 Daniel Pittman and Christian Grothoff + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/data_process.c + * @brief The implementation of data receiving, sending and processing + * functions for connection + * @author Karlson2k (Evgeny Grin) + * + * Based on the MHD v0.x code by Daniel Pittman, Christian Grothoff and other + * contributors. + */ + +#include "mhd_sys_options.h" + +#include "conn_data_process.h" +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include "mhd_assert.h" + +#include "mhd_daemon.h" +#include "mhd_connection.h" + +#include "daemon_logger.h" + +#include "conn_data_recv.h" +#include "conn_data_send.h" + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_conn_process_recv_send_data (struct MHD_Connection *restrict c) +{ + const bool send_ready_state_known = + ((mhd_D_IS_USING_EDGE_TRIG (c->daemon)) || + (0 != (MHD_EVENT_LOOP_INFO_WRITE & c->event_loop_info))); + const bool has_sock_err = + (0 != (mhd_SOCKET_NET_STATE_ERROR_READY & c->sk_ready)); + bool data_processed; + + data_processed = false; + + if (0 != (MHD_EVENT_LOOP_INFO_READ & c->event_loop_info)) + { + bool use_recv; + use_recv = (0 != (mhd_SOCKET_NET_STATE_RECV_READY & c->sk_ready)); + use_recv = use_recv || + (has_sock_err && c->sk_nonblck); + + if (use_recv) + { + mhd_conn_data_recv (c, has_sock_err); + if (! mhd_conn_process_data (c)) + return false; + data_processed = true; + } + } + + if (0 != (MHD_EVENT_LOOP_INFO_WRITE & c->event_loop_info)) + { + bool use_send; + /* Perform sending if: + * + connection is ready for sending or + * + just formed send data, connection send ready status is not known and + * connection socket is non-blocking + * + detected network error on the connection, to check to the error */ + /* Assuming that after finishing receiving phase, connection send system + buffers should have some space as sending was performed before receiving + or has not been performed yet. */ + use_send = (0 != (mhd_SOCKET_NET_STATE_SEND_READY & c->sk_ready)); + use_send = use_send || + (data_processed && (! send_ready_state_known) && c->sk_nonblck); + use_send = use_send || + (has_sock_err && c->sk_nonblck); + + if (use_send) + { + mhd_conn_data_send (c, has_sock_err); + if (! mhd_conn_process_data (c)) + return false; + data_processed = true; + } + } + if (! data_processed) + return mhd_conn_process_data (c); + return true; + +#if 0 // TODO: re-implement fasttrack as a single unified buffer sending + if (! force_close) + { + /* No need to check value of 'ret' here as closed connection + * cannot be in MHD_EVENT_LOOP_INFO_WRITE state. */ + if ( (MHD_EVENT_LOOP_INFO_WRITE == c->event_loop_info) && + write_ready) + { + MHD_connection_handle_write (c); + ret = MHD_connection_handle_idle (c); + states_info_processed = true; + } + } + else + { + MHD_connection_close_ (c, + MHD_REQUEST_TERMINATED_WITH_ERROR); + return MHD_connection_handle_idle (c); + } + + if (! states_info_processed) + { /* Connection is not read or write ready, but external conditions + * may be changed and need to be processed. */ + ret = MHD_connection_handle_idle (c); + } + /* Fast track for fast connections. */ + /* If full request was read by single read_handler() invocation + and headers were completely prepared by single MHD_connection_handle_idle() + then try not to wait for next sockets polling and send response + immediately. + As writeability of socket was not checked and it may have + some data pending in system buffers, use this optimization + only for non-blocking sockets. */ + /* No need to check 'ret' as connection is always in + * MHD_CONNECTION_CLOSED state if 'ret' is equal 'MHD_NO'. */ + else if (on_fasttrack && c->sk_nonblck) + { + if (MHD_CONNECTION_HEADERS_SENDING == c->state) + { + MHD_connection_handle_write (c); + /* Always call 'MHD_connection_handle_idle()' after each read/write. */ + ret = MHD_connection_handle_idle (c); + } + /* If all headers were sent by single write_handler() and + * response body is prepared by single MHD_connection_handle_idle() + * call - continue. */ + if ((MHD_CONNECTION_NORMAL_BODY_READY == c->state) || + (MHD_CONNECTION_CHUNKED_BODY_READY == c->state)) + { + MHD_connection_handle_write (c); + ret = MHD_connection_handle_idle (c); + } + } + + /* All connection's data and states are processed for this turn. + * If connection already has more data to be processed - use + * zero timeout for next select()/poll(). */ + /* Thread-per-connection do not need global zero timeout as + * connections are processed individually. */ + /* Note: no need to check for read buffer availability for + * TLS read-ready connection in 'read info' state as connection + * without space in read buffer will be marked as 'info block'. */ + if ( (! c->daemon->data_already_pending) && + (! MHD_D_IS_USING_THREAD_PER_CONN_ (c->daemon)) ) + { + if (0 != (MHD_EVENT_LOOP_INFO_PROCESS & c->event_loop_info)) + c->daemon->data_already_pending = true; +#ifdef HTTPS_SUPPORT + else if ( (c->tls_read_ready) && + (0 != (MHD_EVENT_LOOP_INFO_READ & c->event_loop_info)) ) + c->daemon->data_already_pending = true; +#endif /* HTTPS_SUPPORT */ + } + return ret; +#endif +} diff --git a/src/mhd2/data_process.h b/src/mhd2/conn_data_process.h similarity index 78% rename from src/mhd2/data_process.h rename to src/mhd2/conn_data_process.h index 6a26b596..1ad1dee2 100644 --- a/src/mhd2/data_process.h +++ b/src/mhd2/conn_data_process.h @@ -39,6 +39,17 @@ struct MHD_Connection; /* forward declaration */ * false if connection needs to be closed */ MHD_INTERNAL bool -mhd_connection_process_recv_send_data (struct MHD_Connection restrict *c); +mhd_conn_process_recv_send_data (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_ ; + +/** + * Finally close and clean-up connection. + * Must be performed only when connection thread (for thread-per-connection) + * has stopped. + * @param c the connection to close + */ +MHD_INTERNAL void +mhd_conn_close_final (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_ ; #endif /* ! MHD_DATA_PROCESS_H */ diff --git a/src/mhd2/conn_data_recv.c b/src/mhd2/conn_data_recv.c new file mode 100644 index 00000000..bf0bc3ec --- /dev/null +++ b/src/mhd2/conn_data_recv.c @@ -0,0 +1,157 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2015-2024 Evgeny Grin (Karlson2k) + Copyright (C) 2007-2020 Daniel Pittman and Christian Grothoff + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/conn_data_recv.c + * @brief The implementation of data receiving functions for connection + * @author Karlson2k (Evgeny Grin) + * + * Based on the MHD v0.x code by Daniel Pittman, Christian Grothoff, Evgeny Grin + * and other contributors. + */ + +#include "conn_data_recv.h" + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include "mhd_assert.h" + +#include "mhd_connection.h" + +#include "mhd_recv.h" + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_conn_data_recv (struct MHD_Connection restrict *c, + bool has_err) +{ + ssize_t bytes_read; + uint_fast64_t dummy_buf; + void *buf; + size_t buf_size; + size_t received; + enum mhd_SocketError res; + + mhd_assert (MHD_CONNECTION_CLOSED != c->state); + mhd_assert (NULL != c->read_buffer); + mhd_assert (c->read_buffer_size > c->read_buffer_offset); + mhd_assert (! has_err || \ + (0 != (c->sk_ready & mhd_SOCKET_NET_STATE_ERROR_READY))); + mhd_assert ((0 == (c->sk_ready & mhd_SOCKET_NET_STATE_ERROR_READY)) || \ + has_err); + + // TODO: TLS support: handshake/transport layer + + buf = c->read_buffer + c->read_buffer_offset; + buf_size = c->read_buffer_size - c->read_buffer_offset; + + res = mhd_recv (c,buf_size, buf, &received); + + if ((mhd_SOCKET_ERR_NO_ERROR != res) || has_err) + { + /* Handle errors */ + if (has_err && ! mhd_SOCKET_ERR_IS_HARD (res) && c->sk_nonblck) + { + /* Re-try last time to detect the error */ + uint_fast64_t dummy_buf; + res = mhd_recv (c, &dummy_buf, sizeof(dummy_buf), &received); + } + if (mhd_SOCKET_ERR_IS_HARD (res)) + { + c->sk_discnt_err = res; + c->sk_ready = + (enum mhd_SocketNetState) (((unsigned int) c->sk_ready) + | mhd_SOCKET_NET_STATE_ERROR_READY); + } + return; + } + + if (0 == bytes_read) + c->sk_rmt_shut_wr = true; + + c->read_buffer_offset += (size_t) bytes_read; + MHD_update_last_activity_ (c); // TODO: centralise activity update + return; +} + + +#if 0 // TODO: report disconnect +if ((bytes_read < 0) || socket_error) +{ + if (MHD_ERR_CONNRESET_ == bytes_read) + { + if ( (MHD_CONNECTION_INIT < c->state) && + (MHD_CONNECTION_FULL_REQ_RECEIVED > c->state) ) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (c->daemon, + _ ("Socket has been disconnected when reading request.\n")); +#endif + c->discard_request = true; + } + MHD_connection_close_ (c, + MHD_REQUEST_TERMINATED_READ_ERROR); + return; + } + +#ifdef HAVE_MESSAGES + if (MHD_CONNECTION_INIT != c->state) + MHD_DLOG (c->daemon, + _ ("Connection socket is closed when reading " \ + "request due to the error: %s\n"), + (bytes_read < 0) ? str_conn_error_ (bytes_read) : + "detected c closure"); +#endif + CONNECTION_CLOSE_ERROR (c, + NULL); + return; +} + +#if 0 // TODO: handle remote shut WR +if (0 == bytes_read) +{ /* Remote side closed c. */ // FIXME: Actually NOT! + c->sk_rmt_shut_wr = true; + if ( (MHD_CONNECTION_INIT < c->state) && + (MHD_CONNECTION_FULL_REQ_RECEIVED > c->state) ) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (c->daemon, + _ ("Connection was closed by remote side with incomplete " + "request.\n")); +#endif + c->discard_request = true; + MHD_connection_close_ (c, + MHD_REQUEST_TERMINATED_CLIENT_ABORT); + } + else if (MHD_CONNECTION_INIT == c->state) + /* This termination code cannot be reported to the application + * because application has not been informed yet about this request */ + MHD_connection_close_ (c, + MHD_REQUEST_TERMINATED_COMPLETED_OK); + else + MHD_connection_close_ (c, + MHD_REQUEST_TERMINATED_WITH_ERROR); + return; +} +#endif +#endif diff --git a/src/mhd2/conn_data_recv.h b/src/mhd2/conn_data_recv.h new file mode 100644 index 00000000..927f103a --- /dev/null +++ b/src/mhd2/conn_data_recv.h @@ -0,0 +1,50 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/conn_data_recv.h + * @brief The definition of data receiving functions for connection + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_CONN_DATA_RECV_H +#define MHD_CONN_DATA_RECV_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" + +struct MHD_Connection; /* forward declarations */ + +/** + * Perform data receiving for the connection and try to detect the socket error + * type. + * + * @param c the connection to use + * @param has_err if 'true' then just check for the network error + * type is performed + */ +MHD_INTERNAL void +mhd_conn_data_recv (struct MHD_Connection restrict *c, + bool has_err) +MHD_FN_PAR_NONNULL_ALL_; + + +#endif /* ! MHD_CONN_DATA_RECV_H */ diff --git a/src/mhd2/conn_data_send.c b/src/mhd2/conn_data_send.c new file mode 100644 index 00000000..2f4cd6e0 --- /dev/null +++ b/src/mhd2/conn_data_send.c @@ -0,0 +1,279 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2015-2024 Evgeny Grin (Karlson2k) + Copyright (C) 2007-2020 Daniel Pittman and Christian Grothoff + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/conn_data_send.c + * @brief The implementation of data sending functions for connection + * @author Karlson2k (Evgeny Grin) + * + * Based on the MHD v0.x code by Daniel Pittman, Christian Grothoff, Evgeny Grin + * and other contributors. + */ + +#include "mhd_sys_options.h" + +#include "conn_data_send.h" +#include "sys_bool_type.h" +#include "sys_base_types.h" +#include "mhd_str_macros.h" +#include "mhd_socket_error.h" + +#include "mhd_assert.h" + +#include "mhd_connection.h" + +#include "mhd_send.h" + + +/** + * Check if we are done sending the write-buffer. + * If so, transition into "next_state". + * + * @param connection connection to check write status for + * @param next_state the next state to transition to + * @return #MHD_NO if we are not done, #MHD_YES if we are + */ +static MHD_FN_PAR_NONNULL_ALL_ bool +check_write_done (struct MHD_Connection *restrict connection, + enum MHD_CONNECTION_STATE next_state) +{ + // TODO: integrate into states processing + if ( (connection->write_buffer_append_offset != + connection->write_buffer_send_offset) + /* || data_in_tls_buffers == true */ + ) + return false; + connection->write_buffer_append_offset = 0; + connection->write_buffer_send_offset = 0; + connection->state = next_state; + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_conn_data_send (struct MHD_Connection restrict *c, + bool has_err) +{ + static const char http_100_continue_msg[] = + mdh_HTTP_1_1_100_CONTINUE_REPLY; + static const size_t http_100_continue_msg_len = + mhd_SSTR_LEN(mdh_HTTP_1_1_100_CONTINUE_REPLY); + enum mhd_SocketError res; + size_t sent; + + // TODO: assert check suspended + +#ifdef HTTPS_SUPPORT + // TODO: TLS support, handshake +#endif /* HTTPS_SUPPORT */ + + res = mhd_SOCKET_ERR_INTERNAL; + + if (MHD_CONNECTION_CONTINUE_SENDING == c->state) + { + res = mhd_send_data (c, + http_100_continue_msg + [c->continue_message_write_offset], + http_100_continue_msg_len + - c->continue_message_write_offset, + true, + &sent); + if (mhd_SOCKET_ERR_NO_ERROR != res) + c->continue_message_write_offset += sent; + } + else if (MHD_CONNECTION_HEADERS_SENDING == c->state) + { + struct MHD_Response *const restrict resp = c->rp.response; + const size_t wb_ready = c->write_buffer_append_offset + - c->write_buffer_send_offset; + mhd_assert (c->write_buffer_append_offset >= \ + c->write_buffer_send_offset); + mhd_assert (NULL != resp); + mhd_assert ((MHD_CONN_MUST_UPGRADE != c->keepalive) || \ + (! c->rp.props.send_reply_body)); + + // TODO: support body generating alongside with header sending + + if ((c->rp.props.send_reply_body) && + (mhd_REPLY_CNTN_LOC_RESP_BUF == c->rp.cntn_loc) && + (! c->rp.props.chunked) ) + { + /* Send response headers alongside the response body, if the body + * data is available. */ + mhd_assert (mhd_RESPONSE_CONTENT_DATA_BUFFER == resp->cntn_dtype); + + res = mhd_send_hdr_and_body (c, + c->write_buffer + + c->write_buffer_send_offset, + wb_ready, + false, + (const char *) resp->cntn.buf, + resp->cntn_size, + true, + &sent); + } + else + { + /* This is response for HEAD request or reply body is not allowed + * for any other reason or reply body is dynamically generated. */ + /* Do not send the body data even if it's available. */ + res = mhd_send_hdr_and_body (c, + c->write_buffer + + c->write_buffer_send_offset, + wb_ready, + false, + NULL, + 0, + ((0 == resp->cntn_size) || + (! c->rp.props.send_reply_body)), + &sent); + } + if (mhd_SOCKET_ERR_NO_ERROR != res) + { + if (sent > wb_ready) + { + /* The complete header and some response data have been sent, + * update both offsets. */ + mhd_assert (0 == c->rp.rsp_cntn_read_pos); + mhd_assert (! c->rp.props.chunked); + mhd_assert (c->rp.props.send_reply_body); + c->write_buffer_send_offset += wb_ready; + c->rp.rsp_cntn_read_pos = sent - wb_ready; + } + else + c->write_buffer_send_offset += sent; + + mhd_assert (MHD_CONNECTION_HEADERS_SENDING == c->state); + + // TODO: move it to data processing + check_write_done (c, + MHD_CONNECTION_HEADERS_SENT); + + } + + } + else if ((MHD_CONNECTION_NORMAL_BODY_READY == c->state) || + (MHD_CONNECTION_CHUNKED_BODY_READY == c->state)) + { + struct MHD_Response *const restrict resp = c->rp.response; + mhd_assert (c->rp.props.send_reply_body); + mhd_assert (c->rp.rsp_cntn_read_pos < resp->cntn_size); + mhd_assert ((MHD_CONNECTION_CHUNKED_BODY_READY != c->state) || \ + (mhd_REPLY_CNTN_LOC_CONN_BUF == c->rp.cntn_loc)); + if (mhd_REPLY_CNTN_LOC_RESP_BUF == c->rp.cntn_loc) + { + mhd_assert (mhd_RESPONSE_CONTENT_DATA_BUFFER == resp->cntn_dtype); + + res = mhd_send_data (c, + (const char *) resp->cntn.buf + + c->rp.rsp_cntn_read_pos, + c->rp.rsp_cntn_read_pos - resp->cntn_size, + true, + &sent); + } + else if (mhd_REPLY_CNTN_LOC_CONN_BUF == c->rp.cntn_loc) + { + mhd_assert (c->write_buffer_append_offset > \ + c->write_buffer_send_offset); + + res = mhd_send_data (c, + c->write_buffer + c->write_buffer_send_offset, + c->write_buffer_append_offset + - c->write_buffer_send_offset, + true, + &sent); + } + else if (mhd_REPLY_CNTN_LOC_IOV == c->rp.cntn_loc) + { + mhd_assert (mhd_RESPONSE_CONTENT_DATA_IOVEC == resp->cntn_dtype); + + res = mhd_send_iovec (c, + &c->rp.resp_iov, + true, + &sent); + } +#if defined(MHD_USE_SENDFILE) + else if (mhd_REPLY_CNTN_LOC_FILE == c->rp.cntn_loc) + { + mhd_assert (mhd_RESPONSE_CONTENT_DATA_FILE == resp->cntn_dtype); + + res = mhd_send_sendfile (c, &sent); + } +#endif /* MHD_USE_SENDFILE */ + else + { + mhd_assert (0 && "Should be unreachable"); + res = mhd_SOCKET_ERR_INTERNAL; + } + + if (mhd_SOCKET_ERR_NO_ERROR != res) + { + if (mhd_REPLY_CNTN_LOC_CONN_BUF != c->rp.cntn_loc) + c->rp.rsp_cntn_read_pos += sent; + else + c->write_buffer_send_offset += sent; + } + + if (MHD_CONNECTION_CHUNKED_BODY_READY == c->state) + { + // TODO: move it to data processing + check_write_done (c, + (c->rp.response->cntn_size == + c->rp.rsp_cntn_read_pos) ? + MHD_CONNECTION_CHUNKED_BODY_SENT : + MHD_CONNECTION_CHUNKED_BODY_UNREADY); + } + } + else if (MHD_CONNECTION_FOOTERS_SENDING == c->state) + { + res = mhd_send_data (c, + c->write_buffer + + c->write_buffer_send_offset, + c->write_buffer_append_offset + - c->write_buffer_send_offset, + true, + &sent); + if (mhd_SOCKET_ERR_NO_ERROR != res) + { + c->write_buffer_send_offset += sent; + // TODO: move it to data processing + check_write_done (c, + MHD_CONNECTION_FULL_REPLY_SENT); + } + } + else + { + mhd_assert (0 && "Should be unreachable"); + res = mhd_SOCKET_ERR_INTERNAL; + } + + if (mhd_SOCKET_ERR_NO_ERROR != res) + { + MHD_update_last_activity_ (c); // TODO: centralise activity mark updates + } + else if (mhd_SOCKET_ERR_IS_HARD(res)) + { + c->sk_discnt_err = res; + c->sk_ready = + (enum mhd_SocketNetState) (((unsigned int) c->sk_ready) + | mhd_SOCKET_NET_STATE_ERROR_READY); + } +} diff --git a/src/mhd2/conn_data_send.h b/src/mhd2/conn_data_send.h new file mode 100644 index 00000000..d3d88190 --- /dev/null +++ b/src/mhd2/conn_data_send.h @@ -0,0 +1,50 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/conn_data_send.h + * @brief The definition of data sending functions for connection + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_CONN_DATA_SEND_H +#define MHD_CONN_DATA_SEND_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" + +struct MHD_Connection; /* forward declarations */ + +/** + * Perform data sending for the connection and try to detect the socket error + * type. + * + * @param c the connection to use + * @param has_err if 'true' then just check for the network error + * type is performed + */ +MHD_INTERNAL void +mhd_conn_data_send (struct MHD_Connection restrict *c, + bool has_err) +MHD_FN_PAR_NONNULL_ALL_; + + +#endif /* ! MHD_CONN_DATA_SEND_H */ diff --git a/src/mhd2/daemon_add_conn.c b/src/mhd2/daemon_add_conn.c index 8a4890b8..22a0a592 100644 --- a/src/mhd2/daemon_add_conn.c +++ b/src/mhd2/daemon_add_conn.c @@ -42,6 +42,8 @@ #include "sys_sockets_types.h" #include "sys_sockets_headers.h" +#include + #include "compat_calloc.h" #include "mhd_panic.h" @@ -59,6 +61,7 @@ #include "events_process.h" #include "response_from.h" +#include "response_destroy.h" /** @@ -736,7 +739,7 @@ MHD_daemon_add_connection (struct MHD_Daemon *daemon, for (i = 0; i < daemon->threading.hier.pool.num; ++i) { - struct MHD_Daemon *const volatile worker = + struct MHD_Daemon *const restrict worker = daemon->threading.hier.pool.workers + (i + offset) % daemon->threading.hier.pool.num; if (worker->conns.block_new) diff --git a/src/mhd2/daemon_create.c b/src/mhd2/daemon_create.c index 25d5a8f8..8ae5d77d 100644 --- a/src/mhd2/daemon_create.c +++ b/src/mhd2/daemon_create.c @@ -93,8 +93,8 @@ MHD_daemon_create (MHD_RequestCallback req_cb, s->fd_number_limit = MHD_INVALID_SOCKET; d->log_params.v_log_cb = mhd_logger_default; - d->req_cb = req_cb; - d->req_cb_cls = req_cb_cls; + d->req_cfg.cb = req_cb; + d->req_cfg.cb_cls = req_cb_cls; d->settings = s; return d; diff --git a/src/mhd2/daemon_funcs.h b/src/mhd2/daemon_funcs.h index cec3eb11..e5acd8aa 100644 --- a/src/mhd2/daemon_funcs.h +++ b/src/mhd2/daemon_funcs.h @@ -46,6 +46,16 @@ struct MHD_Daemon; /* forward declaration */ MHD_INTERNAL bool mhd_daemon_trigger_itc (struct MHD_Daemon *restrict d); + + +/** + * Check whether any resuming connections are pending and resume them + * @param d the daemon to use + */ +MHD_INTERNAL void +mhd_daemon_resume_conns (struct MHD_Daemon *restrict d) +MHD_FN_PAR_NONNULL_ALL_ ; + #endif /* MHD_USE_THREADS */ #endif /* ! MHD_DAEMON_FUNCS_H */ diff --git a/src/mhd2/daemon_start.c b/src/mhd2/daemon_start.c index a7f4986a..52aa11e3 100644 --- a/src/mhd2/daemon_start.c +++ b/src/mhd2/daemon_start.c @@ -74,6 +74,8 @@ # include "daemon_funcs.h" #endif +#include "mhd_limits.h" + /** * The default value for fastopen queue length (currently GNU/Linux only) @@ -614,8 +616,8 @@ create_bind_listen_stream_socket (struct MHD_Daemon *restrict d, if (MHD_INVALID_SOCKET == sk) #endif /* MHD_WINSOCK_SOCKETS && WSA_FLAG_NO_HANDLE_INHERIT */ sk = socket (p_use_sa->sa_family, - SOCK_STREAM | MHD_SOCK_NONBLOCK - | MHD_SOCK_CLOEXEC | MHD_SOCK_NOSIGPIPE, 0); + SOCK_STREAM | mhd_SOCK_NONBLOCK + | mhd_SOCK_CLOEXEC | mhd_SOCK_NOSIGPIPE, 0); if (MHD_INVALID_SOCKET == sk) { @@ -639,8 +641,8 @@ create_bind_listen_stream_socket (struct MHD_Daemon *restrict d, return MHD_SC_FAILED_TO_OPEN_LISTEN_SOCKET; } - is_non_block = (0 != MHD_SOCK_NONBLOCK); - is_non_inhr = (0 == MHD_SOCK_CLOEXEC); + is_non_block = (0 != mhd_SOCK_NONBLOCK); + is_non_inhr = (0 == mhd_SOCK_CLOEXEC); } else { @@ -1958,8 +1960,8 @@ set_connections_total_limits (struct MHD_Daemon *restrict d, #endif /* MHD_USE_THREADS */ limit_by_conf = s->global_connection_limit; - limit_by_num = (unsigned int) ~((unsigned int) 0); /* UINT_MAX */ - limit_by_select = (unsigned int) ~((unsigned int) 0); /* UINT_MAX */ + limit_by_num = UINT_MAX; + limit_by_select = UINT_MAX; error_by_fd_setsize = false; #ifdef MHD_POSIX_SOCKETS @@ -1996,7 +1998,7 @@ set_connections_total_limits (struct MHD_Daemon *restrict d, } } else - limit_by_num = (unsigned int) ~((unsigned int) 0); /* UINT_MAX */ + limit_by_num = UINT_MAX; } #elif defined(MHD_WINSOCK_SOCKETS) if (1) @@ -2020,7 +2022,7 @@ set_connections_total_limits (struct MHD_Daemon *restrict d, { limit_by_select = limit_per_worker * num_worker_daemons; if (limit_by_select / limit_per_worker != num_worker_daemons) - limit_by_select = (unsigned int) ~((unsigned int) 0); /* UINT_MAX */ + limit_by_select = UINT_MAX; } } #endif /* MHD_USE_SELECT */ diff --git a/src/mhd2/data_process.c b/src/mhd2/data_process.c deleted file mode 100644 index 63002e6a..00000000 --- a/src/mhd2/data_process.c +++ /dev/null @@ -1,303 +0,0 @@ -/* - This file is part of GNU libmicrohttpd - Copyright (C) 2015-2024 Evgeny Grin (Karlson2k) - Copyright (C) 2007-2020 Daniel Pittman and Christian Grothoff - - GNU libmicrohttpd is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - GNU libmicrohttpd is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -*/ - -/** - * @file src/mhd2/data_process.c - * @brief The implementation of data receiving, sending and processing - * functions for connection - * @author Karlson2k (Evgeny Grin) - * - * Based on the MHD v0.x code by Daniel Pittman, Christian Grothoff and other - * contributors. - */ - -#include "mhd_sys_options.h" - -#include "data_process.h" - -#include "sys_bool_type.h" -#include "sys_base_types.h" - -#include "mhd_assert.h" - -#include "mhd_daemon.h" -#include "mhd_connection.h" - -#include "mhd_recv.h" - -#include "daemon_logger.h" - -/** - * Perform data receiving for the connection and try to detect the socket error - * type. - * - * @param c the connection to use - * @param has_err if 'true' then just check for the network error - * type is performed - */ -void -conn_process_recv (struct MHD_Connection restrict *c, - bool has_err) -{ - ssize_t bytes_read; - uint_fast64_t dummy_buf; - void *buf; - size_t buf_size; - size_t received; - enum mhd_SocketError res; - - mhd_assert (MHD_CONNECTION_CLOSED != c->state); - mhd_assert ((NULL != c->read_buffer) || has_err); - mhd_assert ((c->read_buffer_size > c->read_buffer_offset) || has_err); - mhd_assert (! has_err || \ - (0 != (c->sk_ready & mhd_SOCKET_NET_STATE_ERROR_READY))); - mhd_assert ((0 == (c->sk_ready & mhd_SOCKET_NET_STATE_ERROR_READY)) || \ - has_err); - - // TODO: TLS support: handshake/transport layer - - if (! has_err || - (NULL != c->read_buffer) || (c->read_buffer_size > c->read_buffer_offset)) - { - buf = c->read_buffer + c->read_buffer_offset; - buf_size = c->read_buffer_size - c->read_buffer_offset; - } - else - { - buf = &dummy_buf; - buf_size = sizeof(buf); - } - - res = mhd_recv (c,buf_size, buf, &received); - - if ((mhd_SOCKET_ERR_NO_ERROR != res) || has_err) - { - /* Handle errors */ - if (! mhd_SOCKET_ERR_IS_HARD (res) && c->sk_nonblck) - { - /* Re-try last time to detect the error */ - res = mhd_recv (c, &dummy_buf, sizeof(dummy_buf), &received); - } - if (mhd_SOCKET_ERR_IS_HARD (res)) - { - c->sk_discnt_err = res; - c->sk_ready = - (enum mhd_SocketNetState) (((unsigned int) c->sk_ready) - | mhd_SOCKET_NET_STATE_ERROR_READY); - } - return; - } - - if (0 == bytes_read) - c->sk_rmt_shut_wr = true; - - c->read_buffer_offset += (size_t) bytes_read; - MHD_update_last_activity_ (c); // TODO: centralise activity update - return; -} - - -#if 0 // TODO: report disconnect -if ((bytes_read < 0) || socket_error) -{ - if (MHD_ERR_CONNRESET_ == bytes_read) - { - if ( (MHD_CONNECTION_INIT < c->state) && - (MHD_CONNECTION_FULL_REQ_RECEIVED > c->state) ) - { -#ifdef HAVE_MESSAGES - MHD_DLOG (c->daemon, - _ ("Socket has been disconnected when reading request.\n")); -#endif - c->discard_request = true; - } - MHD_connection_close_ (c, - MHD_REQUEST_TERMINATED_READ_ERROR); - return; - } - -#ifdef HAVE_MESSAGES - if (MHD_CONNECTION_INIT != c->state) - MHD_DLOG (c->daemon, - _ ("Connection socket is closed when reading " \ - "request due to the error: %s\n"), - (bytes_read < 0) ? str_conn_error_ (bytes_read) : - "detected c closure"); -#endif - CONNECTION_CLOSE_ERROR (c, - NULL); - return; -} - -#if 0 // TODO: handle remote shut WR -if (0 == bytes_read) -{ /* Remote side closed c. */ // FIXME: Actually NOT! - c->sk_rmt_shut_wr = true; - if ( (MHD_CONNECTION_INIT < c->state) && - (MHD_CONNECTION_FULL_REQ_RECEIVED > c->state) ) - { -#ifdef HAVE_MESSAGES - MHD_DLOG (c->daemon, - _ ("Connection was closed by remote side with incomplete " - "request.\n")); -#endif - c->discard_request = true; - MHD_connection_close_ (c, - MHD_REQUEST_TERMINATED_CLIENT_ABORT); - } - else if (MHD_CONNECTION_INIT == c->state) - /* This termination code cannot be reported to the application - * because application has not been informed yet about this request */ - MHD_connection_close_ (c, - MHD_REQUEST_TERMINATED_COMPLETED_OK); - else - MHD_connection_close_ (c, - MHD_REQUEST_TERMINATED_WITH_ERROR); - return; -} -#endif -#endif - -MHD_INTERNAL bool -mhd_connection_process_recv_send_data (struct MHD_Connection restrict *c) -{ - const bool send_ready_state_known = - ((0 != (MHD_EVENT_LOOP_INFO_WRITE & c->event_loop_info)) || - mhd_D_IS_USING_EDGE_TRIG (c->daemon)); - const bool has_sock_err = - (0 != (mhd_SOCKET_NET_STATE_ERROR_READY & c->sk_ready)); - bool data_processed; - bool need_send; - - data_processed = false; - - if ( ((0 != (mhd_SOCKET_NET_STATE_RECV_READY & c->sk_ready)) && - ((0 != (MHD_EVENT_LOOP_INFO_READ & c->event_loop_info)) - || has_sock_err)) - || (has_sock_err && c->sk_nonblck)) - { - conn_process_recv (c, has_sock_err); - if (! conn_process_data (c)) - return false; - data_processed = true; - } - need_send = ((0 != (mhd_SOCKET_NET_STATE_SEND_READY & c->sk_ready)) && - (0 != (MHD_EVENT_LOOP_INFO_WRITE & c->event_loop_info))); - - if (0 != (MHD_EVENT_LOOP_INFO_WRITE & c->event_loop_info)) - { - /* Perform sending if: - * + connection is ready for sending or - * + just formed send data, connection send ready status is not known and - * connection socket is non-blocking */ - /* Assuming that after finishing receiving phase, connection send buffers - should have some space as sending was performed before receiving or has - not been performed yet. */ - if ((0 != (mhd_SOCKET_NET_STATE_SEND_READY & c->sk_ready)) || - ((data_processed && ! send_ready_state_known && c->sk_nonblck) || - (has_sock_err && c->sk_nonblck))) - { - conn_process_send (c, has_sock_err); - if (! conn_process_data (c)) - return false; - data_processed = true; - } - } - if (! data_processed) - return conn_process_data (c); - return true; - -#if 0 // TODO: re-implement fasttrack as a single unified buffer sending - if (! force_close) - { - /* No need to check value of 'ret' here as closed connection - * cannot be in MHD_EVENT_LOOP_INFO_WRITE state. */ - if ( (MHD_EVENT_LOOP_INFO_WRITE == c->event_loop_info) && - write_ready) - { - MHD_connection_handle_write (c); - ret = MHD_connection_handle_idle (c); - states_info_processed = true; - } - } - else - { - MHD_connection_close_ (c, - MHD_REQUEST_TERMINATED_WITH_ERROR); - return MHD_connection_handle_idle (c); - } - - if (! states_info_processed) - { /* Connection is not read or write ready, but external conditions - * may be changed and need to be processed. */ - ret = MHD_connection_handle_idle (c); - } - /* Fast track for fast connections. */ - /* If full request was read by single read_handler() invocation - and headers were completely prepared by single MHD_connection_handle_idle() - then try not to wait for next sockets polling and send response - immediately. - As writeability of socket was not checked and it may have - some data pending in system buffers, use this optimization - only for non-blocking sockets. */ - /* No need to check 'ret' as connection is always in - * MHD_CONNECTION_CLOSED state if 'ret' is equal 'MHD_NO'. */ - else if (on_fasttrack && c->sk_nonblck) - { - if (MHD_CONNECTION_HEADERS_SENDING == c->state) - { - MHD_connection_handle_write (c); - /* Always call 'MHD_connection_handle_idle()' after each read/write. */ - ret = MHD_connection_handle_idle (c); - } - /* If all headers were sent by single write_handler() and - * response body is prepared by single MHD_connection_handle_idle() - * call - continue. */ - if ((MHD_CONNECTION_NORMAL_BODY_READY == c->state) || - (MHD_CONNECTION_CHUNKED_BODY_READY == c->state)) - { - MHD_connection_handle_write (c); - ret = MHD_connection_handle_idle (c); - } - } - - /* All connection's data and states are processed for this turn. - * If connection already has more data to be processed - use - * zero timeout for next select()/poll(). */ - /* Thread-per-connection do not need global zero timeout as - * connections are processed individually. */ - /* Note: no need to check for read buffer availability for - * TLS read-ready connection in 'read info' state as connection - * without space in read buffer will be marked as 'info block'. */ - if ( (! c->daemon->data_already_pending) && - (! MHD_D_IS_USING_THREAD_PER_CONN_ (c->daemon)) ) - { - if (0 != (MHD_EVENT_LOOP_INFO_PROCESS & c->event_loop_info)) - c->daemon->data_already_pending = true; -#ifdef HTTPS_SUPPORT - else if ( (c->tls_read_ready) && - (0 != (MHD_EVENT_LOOP_INFO_READ & c->event_loop_info)) ) - c->daemon->data_already_pending = true; -#endif /* HTTPS_SUPPORT */ - } - return ret; -#endif -} diff --git a/src/mhd2/events_process.c b/src/mhd2/events_process.c index 213622ac..33d6da25 100644 --- a/src/mhd2/events_process.c +++ b/src/mhd2/events_process.c @@ -40,6 +40,9 @@ #include "daemon_logger.h" #include "mhd_connection.h" #include "daemon_add_conn.h" +#include "daemon_funcs.h" +#include "conn_data_process.h" + MHD_FN_PAR_NONNULL_ (1) static void @@ -209,15 +212,20 @@ daemon_accept_new_conns (struct MHD_Daemon *restrict d) static bool daemon_process_all_act_coons (struct MHD_Daemon *restrict d) { - struct MHD_Connection *restrict c; + struct MHD_Connection *c; mhd_assert (! mhd_D_HAS_WORKERS (d)); - for (c = mhd_DLINKEDL_GET_FIRST (&(d->events),proc_ready); c != NULL; - c = mhd_DLINKEDL_GET_NEXT (c, proc_ready)) + c = mhd_DLINKEDL_GET_FIRST (&(d->events),proc_ready); + while (NULL != c) { + struct MHD_Connection *next; + next = mhd_DLINKEDL_GET_NEXT (c, proc_ready); /* The current connection can be closed */ + if (! mhd_conn_process_recv_send_data(c)) + mhd_conn_close_final(c); + c = next; } - return false; + return true; } @@ -524,6 +532,9 @@ mhd_worker_all_events (void *cls) while (! d->threading.stop_requested) { + if (d->threading.resume_requested) + mhd_daemon_resume_conns (d); + if (! process_all_events_and_data (d)) break; } diff --git a/src/mhd2/http_prot_ver.h b/src/mhd2/http_prot_ver.h index 41cb708a..70cac271 100644 --- a/src/mhd2/http_prot_ver.h +++ b/src/mhd2/http_prot_ver.h @@ -44,9 +44,9 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_HTTP_ProtocolVersion , MHD_HTTP_VERSION_1_1 = 2 , - MHD_HTTP_VERSION_2_0 = 3 + MHD_HTTP_VERSION_2 = 3 , - MHD_HTTP_VERSION_3_0 = 4 + MHD_HTTP_VERSION_3 = 4 , MHD_HTTP_VERSION_FUTURE = 255 }; diff --git a/src/mhd2/http_status_str.c b/src/mhd2/http_status_str.c index cce274c8..d946ff03 100644 --- a/src/mhd2/http_status_str.c +++ b/src/mhd2/http_status_str.c @@ -175,7 +175,7 @@ static const struct mhd_HttpStatusesBlock statuses[] = { }; MHD_EXTERN_ MHD_FN_CONST_ const struct MHD_String * -MHD_HTTP_status_code_to_string (enum MHD_StatusCode code) +MHD_HTTP_status_code_to_string (enum MHD_HTTP_StatusCode code) { struct MHD_String *res; const unsigned int code_i = (unsigned int) code; @@ -193,7 +193,7 @@ MHD_HTTP_status_code_to_string (enum MHD_StatusCode code) MHD_INTERNAL MHD_FN_CONST_ const struct MHD_String * -mhd_HTTP_status_code_to_string_int (enum MHD_StatusCode code) +mhd_HTTP_status_code_to_string_int (enum MHD_HTTP_StatusCode code) { static const struct MHD_String no_status = mhd_MSTR_INIT ("Nonstandard Status"); diff --git a/src/mhd2/http_status_str.h b/src/mhd2/http_status_str.h index 4c700fe7..4906943b 100644 --- a/src/mhd2/http_status_str.h +++ b/src/mhd2/http_status_str.h @@ -39,7 +39,7 @@ * @return pointer to MHD_String, never NULL. */ MHD_INTERNAL const struct MHD_String * -mhd_HTTP_status_code_to_string_int (enum MHD_StatusCode code) +mhd_HTTP_status_code_to_string_int (enum MHD_HTTP_StatusCode code) MHD_FN_CONST_; diff --git a/src/mhd2/mhd_atomic_counter.c b/src/mhd2/mhd_atomic_counter.c new file mode 100644 index 00000000..c6edc896 --- /dev/null +++ b/src/mhd2/mhd_atomic_counter.c @@ -0,0 +1,73 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_atomic_counter.c + * @brief The definition of the atomic counter functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include "mhd_atomic_counter.h" + +#if defined(mhd_ATOMIC_BY_LOCKS) + +MHD_INTERNAL mhd_ATOMIC_COUNTER_TYPE +mhd_atomic_counter_inc_get (struct mhd_AtomicCounter *pcnt) +{ + mhd_ATOMIC_COUNTER_TYPE ret; + + mhd_mutex_lock_chk(&(pcnt->lock)); + ret = ++(pcnt->count); + mhd_mutex_unlock_chk(&(pcnt->lock)); + + mhd_assert (0 != ret); /* check for overflow */ + + return ret; +} + +MHD_INTERNAL mhd_ATOMIC_COUNTER_TYPE +mhd_atomic_counter_dec_get (struct mhd_AtomicCounter *pcnt) +{ + mhd_ATOMIC_COUNTER_TYPE ret; + + mhd_mutex_lock_chk(&(pcnt->lock)); + ret = --(pcnt->count); + mhd_mutex_unlock_chk(&(pcnt->lock)); + + mhd_assert (mhd_ATOMIC_COUNTER_MAX != ret); /* check for underflow */ + + return ret; +} + +MHD_INTERNAL mhd_ATOMIC_COUNTER_TYPE +mhd_atomic_counter_get (struct mhd_AtomicCounter *pcnt) +{ + mhd_ATOMIC_COUNTER_TYPE ret; + + mhd_mutex_lock_chk(&(pcnt->lock)); + ret = pcnt->count; + mhd_mutex_unlock_chk(&(pcnt->lock)); + + return ret; +} + +#endif /* mhd_ATOMIC_BY_LOCKS */ diff --git a/src/mhd2/mhd_atomic_counter.h b/src/mhd2/mhd_atomic_counter.h new file mode 100644 index 00000000..2410a3e5 --- /dev/null +++ b/src/mhd2/mhd_atomic_counter.h @@ -0,0 +1,203 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_atomic_counter.h + * @brief The definition of the atomic counter type and related functions + * declarations + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_ATOMIC_COUNTER_H +#define MHD_ATOMIC_COUNTER_H 1 + +#include "mhd_sys_options.h" + +#include "sys_base_types.h" /* for size_t */ + +/* Use 'size_t' to make sure it would never overflow when used for + * MHD needs. */ + +/** + * The type used to contain the counter value. + * Always unsigned. + */ +#define mhd_ATOMIC_COUNTER_TYPE size_t +/** + * The maximum counter value + */ +#define mhd_ATOMIC_COUNTER_MAX \ + ((mhd_ATOMIC_COUNTER_TYPE)(~((mhd_ATOMIC_COUNTER_TYPE)0))) + +#ifdef MHD_USE_THREADS + +/** + * Atomic operations are based on locks + */ +# define mhd_ATOMIC_BY_LOCKS 1 + +#else /* ! MHD_USE_THREADS */ + +/** + * Atomic because single thread environment is used + */ +# define mhd_ATOMIC_SINGLE_THREAD 1 +#endif /* ! MHD_USE_THREADS */ + + +#if defined(mhd_ATOMIC_BY_LOCKS) +# include "mhd_locks.h" +# include "sys_bool_type.h" + +/** + * The atomic counter + */ +struct mhd_AtomicCounter +{ + /** + * Counter value. + * Must be read or written only with @a lock held. + */ + volatile mhd_ATOMIC_COUNTER_TYPE count; + /** + * The mutex. + */ + mhd_mutex lock; +}; + +#elif defined(mhd_ATOMIC_SINGLE_THREAD) + +/** + * The atomic counter + */ +struct mhd_AtomicCounter +{ + /** + * Counter value. + */ + volatile mhd_ATOMIC_COUNTER_TYPE count; +}; + +#endif /* mhd_ATOMIC_SINGLE_THREAD */ + + +#if defined(mhd_ATOMIC_BY_LOCKS) + +/** + * Initialise the counter to specified value. + * @param pcnt the pointer to the counter to initialise + * @param initial_value the initial value for the counter + * @return 'true' if succeed, "false' if failed + * @warning Must not be called for the counters that has been initialised + * already. + */ +# define mhd_atomic_counter_init(pcnt,initial_value) \ + ((pcnt)->count=(initial_value), mhd_mutex_init_short(&(pcnt->lock))) + +/** + * Deinitialise the counter. + * @param pcnt the pointer to the counter to deinitialise + * @warning Must be called only for the counters that has been initialised. + */ +# define mhd_atomic_counter_deinit(pcnt) mhd_mutex_destroy_chk(&((pcnt)->lock)) + +/** + * Atomically increment the value of the counter + * @param pcnt the pointer to the counter to increment + */ +# define mhd_atomic_counter_inc(pcnt) do { \ + mhd_mutex_lock_chk(&(pcnt->lock)); \ + ++(pcnt->count); \ + mhd_mutex_unlock_chk(&(pcnt->lock)); } while (0) + +/** + * Atomically increment the value of the counter and return the result + * @param pcnt the pointer to the counter to increment + * @return the final/resulting counter value + */ +MHD_INTERNAL mhd_ATOMIC_COUNTER_TYPE +mhd_atomic_counter_inc_get (struct mhd_AtomicCounter *pcnt); + +/** + * Atomically decrement the value of the counter and return the result + * @param pcnt the pointer to the counter to decrement + * @return the final/resulting counter value + */ +MHD_INTERNAL mhd_ATOMIC_COUNTER_TYPE +mhd_atomic_counter_dec_get (struct mhd_AtomicCounter *pcnt); + +/** + * Atomically get the value of the counter + * @param pcnt the pointer to the counter to get + * @return the counter value + */ +MHD_INTERNAL mhd_ATOMIC_COUNTER_TYPE +mhd_atomic_counter_get (struct mhd_AtomicCounter *pcnt); + +#elif defined(mhd_ATOMIC_SINGLE_THREAD) + +/** + * Initialise the counter to specified value. + * @param pcnt the pointer to the counter to initialise + * @param initial_value the initial value for the counter + * @return 'true' if succeed, "false' if failed + * @warning Must not be called for the counters that has been initialised + * already. + */ +# define mhd_atomic_counter_init(pcnt,initial_value) \ + ((pcnt)->count=(initial_value), (! 0)) + +/** + * Deinitialise the counter. + * @param pcnt the pointer to the counter to deinitialise + * @warning Must be called only for the counters that has been initialised. + */ +# define mhd_atomic_counter_deinit(pcnt) ((void)0) + +/** + * Atomically increment the value of the counter + * @param pcnt the pointer to the counter to increment + */ +# define mhd_atomic_counter_inc(pcnt) do { ++(pcnt->count); } while (0) + +/** + * Atomically increment the value of the counter and return the result + * @param pcnt the pointer to the counter to increment + * @return the final/resulting counter value + */ +# define mhd_atomic_counter_inc_get(pcnt) (++((pcnt)->count)) + +/** + * Atomically decrement the value of the counter and return the result + * @param pcnt the pointer to the counter to decrement + * @return the final/resulting counter value + */ +# define mhd_atomic_counter_dec_get(pcnt) (--((pcnt)->count)) + +/** + * Atomically get the value of the counter + * @param pcnt the pointer to the counter to get + * @return the counter value + */ +# define mhd_atomic_counter_get(pcnt) ((pcnt)->count) + +#endif /* mhd_ATOMIC_SINGLE_THREAD */ + +#endif /* ! MHD_ATOMIC_COUNTER_H */ diff --git a/src/mhd2/mhd_connection.h b/src/mhd2/mhd_connection.h index 527b9eee..19978980 100644 --- a/src/mhd2/mhd_connection.h +++ b/src/mhd2/mhd_connection.h @@ -37,6 +37,7 @@ #include "sys_bool_type.h" #include "sys_base_types.h" +#include "mhd_socket_type.h" #include "mhd_threads.h" @@ -50,6 +51,22 @@ #include "mhd_public_api.h" +/** + * Minimum reasonable size by which MHD tries to increment read/write buffers. + * We usually begin with half the available pool space for the + * IO-buffer, but if absolutely needed we additively grow by the + * number of bytes given here (up to -- theoretically -- the full pool + * space). + * + * Currently set to reasonable maximum MSS size. + */ +#define mhd_BUF_INC_SIZE 1500 + +/** + * Message to transmit when http 1.1 request is received + */ +#define mdh_HTTP_1_1_100_CONTINUE_REPLY "HTTP/1.1 100 Continue\r\n\r\n" + struct MHD_Connection; /* forward declaration */ @@ -113,6 +130,56 @@ enum MHD_FIXED_FLAGS_ENUM_ mhd_SocketNetState }; +/** + * The reason for the socket closure + */ +enum mhd_SocketClosureReason +{ + /** + * The socket is not closed / closing. + */ + mhd_SCOKET_CLOSURE_REASON_NO_CLOSURE = 0 + , + /** + * Socket has to be closed because HTTP protocol successfully finished data + * exchange. + */ + mhd_SCOKET_CLOSURE_REASON_PROTOCOL_SUCCESS + , + /** + * Socket has to be closed because remote side violated some HTTP + * specification requirements or request processed with an error. + * The HTTP error response should be sent. + */ + mhd_SCOKET_CLOSURE_REASON_PROTOCOL_FAILURE_SOFT + , + /** + * Timeout expired + */ + mhd_SCOKET_CLOSURE_REASON_TIMEOUT + , + /** + * Socket has to be closed because received data cannot be interpreted as + * valid HTTP data. + */ + mhd_SCOKET_CLOSURE_REASON_PROTOCOL_FAILURE_HARD + , + /** + * Unrecoverable TLS error + */ + mhd_SCOKET_CLOSURE_REASON_TLS_ERROR + , + /** + * The remote side closed connection in abortive way + */ + mhd_SCOKET_CLOSURE_REASON_REMOTE_HARD_DISCONN + , + /** + * The connection has been broken for some reason + */ + mhd_SCOKET_CLOSURE_REASON_CONN_BROKEN +}; + /** * States in a state machine for a connection. * @@ -317,7 +384,7 @@ struct MHD_Connection /** * Set to 'true' when the client shut down write/send and - * the last byte from remote has been read. + * __the last byte from the remote has been read__. */ bool sk_rmt_shut_wr; @@ -331,6 +398,16 @@ struct MHD_Connection */ mhd_DLNKDL_LINKS (MHD_Connection,by_timeout); + /** + * True if connection is suspended + */ + bool suspended; + + /** + * True if connection is resuming + */ + bool resuming; + /** * Reference to the MHD_Daemon struct. */ @@ -465,7 +542,7 @@ struct MHD_Connection enum mhd_Tristate is_nonip; /** - * true if #socket_fd is non-blocking, false otherwise. + * true if @a socket_fd is non-blocking, false otherwise. */ bool sk_nonblck; @@ -474,10 +551,12 @@ struct MHD_Connection */ bool sk_spipe_suppress; +//#ifndef MHD_WINSOCK_SOCKETS // TODO: conditionally use in the code /** * Tracks TCP_CORK / TCP_NOPUSH of the connection socket. */ enum mhd_Tristate sk_corked; +//#endif /** * Tracks TCP_NODELAY state of the connection socket. diff --git a/src/mhd2/mhd_daemon.h b/src/mhd2/mhd_daemon.h index 5aecc65f..23f2e062 100644 --- a/src/mhd2/mhd_daemon.h +++ b/src/mhd2/mhd_daemon.h @@ -681,6 +681,11 @@ struct mhd_DaemonThreadingData */ volatile bool stop_requested; + /** + * 'True' if resuming of any connection has been requested. + */ + volatile bool resume_requested; + /** * The handle of the daemon's thread (if managed by the daemon) */ @@ -765,6 +770,29 @@ struct mhd_DaemonConnections }; +/** + * Settings for requests processing + */ +struct mhd_DaemonRequestProcessingSettings +{ + /** + * Request callback. + * The main request processing callback. + */ + MHD_RequestCallback cb; + + /** + * The closure for @a req_cb + */ + void *cb_cls; + + /** + * Protocol strictness enforced by MHD on clients. + */ + enum MHD_ProtocolStrictLevel strictnees; +}; + + #ifndef NDEBUG /** * Various debugging data @@ -830,15 +858,9 @@ struct MHD_Daemon /* Request processing data */ /** - * Request callback. - * The main request processing callback. + * Settings for requests processing */ - MHD_RequestCallback req_cb; - - /** - * The closure for @a req_cb - */ - void *req_cb_cls; + struct mhd_DaemonRequestProcessingSettings req_cfg; /* Other data */ diff --git a/src/mhd2/mhd_iovec.h b/src/mhd2/mhd_iovec.h index 53614a51..1f18e3ce 100644 --- a/src/mhd2/mhd_iovec.h +++ b/src/mhd2/mhd_iovec.h @@ -29,6 +29,7 @@ #include "mhd_sys_options.h" #include "mhd_socket_type.h" +#include "sys_base_types.h" #if defined(HAVE_WRITEV) || defined(HAVE_SENDMSG) # ifdef HAVE_SYS_TYPES_H @@ -47,6 +48,8 @@ # endif #endif +#include "mhd_limits.h" + #if defined(MHD_WINSOCK_SOCKETS) /** * Internally used I/O vector type for use with winsock. @@ -58,24 +61,30 @@ struct mhd_w32_iovec char *iov_base; }; typedef struct mhd_w32_iovec mhd_iovec; -#define MHD_IOV_ELMN_MAX_SIZE ULONG_MAX -typedef unsigned long mhd_iov_size; +#define mhd_IOV_ELMN_MAX_SIZE ULONG_MAX +typedef unsigned long mhd_iov_elmn_size; +#define mhd_IOV_RET_MAX_SIZE LONG_MAX +typedef long mhd_iov_ret_type; #elif defined(HAVE_SENDMSG) || defined(HAVE_WRITEV) /** * Internally used I/O vector type for use when writev or sendmsg * is available. Matches system "struct iovec". */ typedef struct iovec mhd_iovec; -#define MHD_IOV_ELMN_MAX_SIZE SIZE_MAX -typedef size_t mhd_iov_size; +#define mhd_IOV_ELMN_MAX_SIZE SIZE_MAX +typedef size_t mhd_iov_elmn_size; +#define mhd_IOV_RET_MAX_SIZE SSIZE_MAX +typedef ssize_t mhd_iov_ret_type; #else /** * Internally used I/O vector type for use when writev or sendmsg * is not available. */ typedef struct MHD_IoVec mhd_iovec; -#define MHD_IOV_ELMN_MAX_SIZE SIZE_MAX -typedef size_t mhd_iov_size; +#define mhd_IOV_ELMN_MAX_SIZE SIZE_MAX +typedef size_t mhd_iov_elmn_size; +#define mhd_IOV_RET_MAX_SIZE SSIZE_MAX +typedef ssize_t mhd_iov_ret_type; #endif diff --git a/src/mhd2/mhd_itc.h b/src/mhd2/mhd_itc.h index 669c02a5..2f0348c7 100644 --- a/src/mhd2/mhd_itc.h +++ b/src/mhd2/mhd_itc.h @@ -289,7 +289,7 @@ mhd_itc_nonblocking (struct mhd_itc *pitc); * @return non-zero if succeeded, zero otherwise */ # define mhd_itc_activate(itc) \ - ((0 < mhd_send ((itc).sk[1], "", 1)) || mhd_SCKT_LERR_IS_EAGAIN ()) + ((0 < mhd_sys_send ((itc).sk[1], "", 1)) || mhd_SCKT_LERR_IS_EAGAIN ()) /** * Return read FD of @a itc which can be used for poll(), select() etc. diff --git a/src/mhd2/mhd_lib_init.c b/src/mhd2/mhd_lib_init.c index 17a5ae06..4100adc8 100644 --- a/src/mhd2/mhd_lib_init.c +++ b/src/mhd2/mhd_lib_init.c @@ -28,6 +28,7 @@ #include "mhd_panic.h" #include "mhd_mono_clock.h" #include "mhd_socket_type.h" +#include "mhd_send.h" #ifdef MHD_WINSOCK_SOCKETS # include #endif @@ -46,12 +47,15 @@ mhd_lib_global_init (void) MHD_PANIC ("Failed to initialise WinSock."); } #endif /* MHD_WINSOCK_SOCKETS */ + MHD_monotonic_msec_counter_init(); + mhd_send_init_static_vars(); } void mhd_lib_global_deinit (void) { + MHD_monotonic_msec_counter_finish(); #if defined(MHD_WINSOCK_SOCKETS) (void) WSACleanup (); #endif /* MHD_WINSOCK_SOCKETS */ diff --git a/src/mhd2/mhd_limits.h b/src/mhd2/mhd_limits.h new file mode 100644 index 00000000..4cb5dba5 --- /dev/null +++ b/src/mhd2/mhd_limits.h @@ -0,0 +1,177 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2015-2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/** + * @file src/mhd2/mhd_limits.h + * @brief limits values definitions + * @author Karlson2k (Evgeny Grin) + * + * This file provides maximum types values as macros. Macros may not work + * in preprocessor expressions, while macros always work in compiler + * expressions. + * This file does not include and other type-definitions files. + * For best portability, make sure that this file is included after required + * type-definitions files. + */ + +#ifndef MHD_LIMITS_H +#define MHD_LIMITS_H + +#include "mhd_sys_options.h" + +#ifdef HAVE_LIMITS_H +# include +#endif /* HAVE_LIMITS_H */ + +#define mhd_UNSIGNED_TYPE_MAX(type) ((type)(~((type) 0))) + +/* Assume 8 bits per byte, no padding bits. */ +#define mhd_SIGNED_TYPE_MAX(type) \ + ( (type) ((( ((type) 1) << (sizeof(type) * 8 - 2)) - 1) * 2 + 1) ) + +/* The maximum value for signed type, based on knowledge of unsigned counterpart + type */ +#define mhd_SIGNED_TYPE_MAX2(type,utype) ((type)(((utype)(~((utype)0))) >> 1)) + +#define mhd_IS_TYPE_SIGNED(type) (((type) 0) > ((type) - 1)) + +#if defined(__GNUC__) || defined(__clang__) +# define mhd_USE_PREDEF_LIMITS +#endif + +#ifndef INT_MAX +# if defined(mhd_USE_PREDEF_LIMITS) && defined(__INT_MAX__) +# define INT_MAX __INT_MAX__ +# else /* ! __INT_MAX__ */ +# define INT_MAX mhd_SIGNED_TYPE_MAX2 (int, unsigned int) +# endif /* ! __INT_MAX__ */ +#endif /* ! INT_MAX */ + +#ifndef UINT_MAX +# if defined(mhd_USE_PREDEF_LIMITS) && defined(__UINT_MAX__) +# define UINT_MAX __UINT_MAX__ +# else /* ! __UINT_MAX__ */ +# define UINT_MAX mhd_UNSIGNED_TYPE_MAX (unsigned int) +# endif /* ! __UINT_MAX__ */ +#endif /* !UINT_MAX */ + +#ifndef LONG_MAX +# if defined(mhd_USE_PREDEF_LIMITS) && defined(__LONG_MAX__) +# define LONG_MAX __LONG_MAX__ +# else /* ! __LONG_MAX__ */ +# define LONG_MAX mhd_SIGNED_TYPE_MAX2 (long, unsigned long) +# endif /* ! __LONG_MAX__ */ +#endif /* !LONG_MAX */ + +#ifndef ULONG_MAX +# if defined(mhd_USE_PREDEF_LIMITS) && defined(__ULONG_MAX__) +# define ULONG_MAX ULONG_MAX +# else /* ! __ULONG_MAX__ */ +# define ULONG_MAX mhd_UNSIGNED_TYPE_MAX (unsigned long) +# endif /* ! __ULONG_MAX__ */ +#endif /* !ULONG_MAX */ + +#ifndef ULLONG_MAX +# ifdef ULONGLONG_MAX +# define ULLONG_MAX ULONGLONG_MAX +# else /* ! ULONGLONG_MAX */ +# define ULLONG_MAX mhd_UNSIGNED_TYPE_MAX (unsigned long long) +# endif /* ! ULONGLONG_MAX */ +#endif /* !ULLONG_MAX */ + +#ifndef INT32_MAX +# if defined(mhd_USE_PREDEF_LIMITS) && defined(__INT32_MAX__) +# define INT32_MAX __INT32_MAX__ +# else /* ! __INT32_MAX__ */ +# define INT32_MAX ((int32_t) 0x7FFFFFFF) +# endif /* ! __INT32_MAX__ */ +#endif /* !INT32_MAX */ + +#ifndef UINT32_MAX +# if defined(mhd_USE_PREDEF_LIMITS) && defined(__UINT32_MAX__) +# define UINT32_MAX __UINT32_MAX__ +# else /* ! __UINT32_MAX__ */ +# define UINT32_MAX ((uint32_t) 0xFFFFFFFFU) +# endif /* ! __UINT32_MAX__ */ +#endif /* !UINT32_MAX */ + +#ifndef INT64_MAX +# if defined(mhd_USE_PREDEF_LIMITS) && defined(__INT64_MAX__) +# define INT64_MAX __INT64_MAX__ +# else /* ! __INT64_MAX__ */ +# define INT64_MAX ((int64_t) 0x7FFFFFFFFFFFFFFF) +# endif /* ! __UINT64_MAX__ */ +#endif /* !INT64_MAX */ + +#ifndef UINT64_MAX +# if defined(mhd_USE_PREDEF_LIMITS) && defined(__UINT64_MAX__) +# define UINT64_MAX __UINT64_MAX__ +# else /* ! __UINT64_MAX__ */ +# define UINT64_MAX ((uint64_t) 0xFFFFFFFFFFFFFFFFU) +# endif /* ! __UINT64_MAX__ */ +#endif /* !UINT64_MAX */ + +#ifndef SIZE_MAX +# if defined(mhd_USE_PREDEF_LIMITS) && defined(__SIZE_MAX__) +# define SIZE_MAX __SIZE_MAX__ +# else /* ! __SIZE_MAX__ */ +# define SIZE_MAX mhd_UNSIGNED_TYPE_MAX (size_t) +# endif /* ! __SIZE_MAX__ */ +#endif /* !SIZE_MAX */ + +#ifndef SSIZE_MAX +# if defined(mhd_USE_PREDEF_LIMITS) && defined(__SSIZE_MAX__) +# define SSIZE_MAX __SSIZE_MAX__ +# else +# define SSIZE_MAX mhd_SIGNED_TYPE_MAX2 (ssize_t, size_t) +# endif +#endif /* ! SSIZE_MAX */ + +#ifndef OFF_T_MAX +# ifdef OFF_MAX +# define OFF_T_MAX OFF_MAX +# elif defined(OFFT_MAX) +# define OFF_T_MAX OFFT_MAX +# elif defined(__APPLE__) && defined(__MACH__) +# define OFF_T_MAX INT64_MAX +# else +# define OFF_T_MAX mhd_SIGNED_TYPE_MAX (off_t) +# endif +#endif /* !OFF_T_MAX */ + +#if defined(_LARGEFILE64_SOURCE) && ! defined(OFF64_T_MAX) +# define OFF64_T_MAX mhd_SIGNED_TYPE_MAX (off64_t) +#endif /* _LARGEFILE64_SOURCE && !OFF64_T_MAX */ + +#ifndef TIME_T_MAX +# define TIME_T_MAX ((time_t) \ + (mhd_IS_TYPE_SIGNED (time_t) ? \ + mhd_SIGNED_TYPE_MAX (time_t) : \ + mhd_UNSIGNED_TYPE_MAX (time_t))) +#endif /* !TIME_T_MAX */ + +#ifndef TIMEVAL_TV_SEC_MAX +# ifndef _WIN32 +# define TIMEVAL_TV_SEC_MAX TIME_T_MAX +# else /* _WIN32 */ +# define TIMEVAL_TV_SEC_MAX LONG_MAX +# endif /* _WIN32 */ +#endif /* !TIMEVAL_TV_SEC_MAX */ + +#endif /* MHD_LIMITS_H */ diff --git a/src/mhd2/mhd_mempool.c b/src/mhd2/mhd_mempool.c index 4fad335b..85dc36c3 100644 --- a/src/mhd2/mhd_mempool.c +++ b/src/mhd2/mhd_mempool.c @@ -60,10 +60,7 @@ # endif /* HAVE_SYS_PARAM_H */ #endif /* MHD_USE_PAGESIZE_MACRO || MHD_USE_PAGE_SIZE_MACRO */ -#ifndef SIZE_MAX -/* This file does not use SIZE_MAX macro with preprocessor */ -# define SIZE_MAX ((size_t) ~((size_t) 0)) -#endif +#include "mhd_limits.h" /** * Fallback value of page size diff --git a/src/mhd2/mhd_recv.c b/src/mhd2/mhd_recv.c index 4a5fdaf7..b909d510 100644 --- a/src/mhd2/mhd_recv.c +++ b/src/mhd2/mhd_recv.c @@ -34,73 +34,56 @@ #include "sys_sockets_headers.h" #include "mhd_sockets_macros.h" -#include +#include "mhd_limits.h" +#include "mhd_socket_error.h" -#ifndef SSIZE_MAX -# define SSIZE_MAX ((ssize_t) ((~((size_t) 0)) >> 1)) -#endif +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_OUT_SIZE_ (3,2) MHD_FN_PAR_OUT_ (4) enum mhd_SocketError +mhd_plain_recv (struct MHD_Connection *restrict c, + size_t buf_size, + char buf[MHD_FN_PAR_DYN_ARR_SIZE_(buf_size)], + size_t *restrict received) +{ + /* Plain TCP connection */ + ssize_t res; + enum mhd_SocketError err; + + if (MHD_SCKT_SEND_MAX_SIZE_ < buf_size) + buf_size = MHD_SCKT_SEND_MAX_SIZE_; + + res = mhd_sys_recv (c->socket_fd, buf, buf_size); + if (0 <= res) + { + *received = (size_t) res; + if (buf_size > (size_t) res) + c->sk_ready = (enum mhd_SocketNetState) /* Clear 'recv-ready' */ + (((unsigned int) c->sk_ready) + & (~mhd_SOCKET_NET_STATE_RECV_READY)); + return mhd_SOCKET_ERR_NO_ERROR; /* Success exit point */ + } + + err = mhd_socket_error_get_from_sys_err(mhd_SCKT_GET_LERR ()); + + if (mhd_SOCKET_ERR_AGAIN == err) + c->sk_ready = (enum mhd_SocketNetState) /* Clear 'recv-ready' */ + (((unsigned int) c->sk_ready) + & (~mhd_SOCKET_NET_STATE_RECV_READY)); + + return err; /* Failure exit point */ +} MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ -MHD_FN_PAR_OUT_SIZE_ (3,2) \ - MHD_FN_PAR_OUT_ (4) enum mhd_SocketError +MHD_FN_PAR_OUT_SIZE_ (3,2) MHD_FN_PAR_OUT_ (4) enum mhd_SocketError mhd_recv (struct MHD_Connection *restrict c, size_t buf_size, - void *restrict buf, + char buf[MHD_FN_PAR_DYN_ARR_SIZE_(buf_size)], size_t *restrict received) { mhd_assert (MHD_INVALID_SOCKET != c->socket_fd); mhd_assert (MHD_CONNECTION_CLOSED != c->state); // TODO: implement TLS support - if (1) /* Plain TCP connection */ - { - ssize_t res; - int err; - if (MHD_SCKT_SEND_MAX_SIZE_ < buf_size) - buf_size = MHD_SCKT_SEND_MAX_SIZE_; - - res = mhd_sys_recv (c->socket_fd, buf, buf_size); - if (0 <= res) - { - *received = (size_t) res; - if (buf_size > (size_t) res) - c->sk_ready = - (enum mhd_SocketNetState) (((unsigned int) c->sk_ready) - & (~mhd_SOCKET_NET_STATE_RECV_READY)); - return mhd_SOCKET_ERR_NO_ERROR; /* Success exit point */ - } - - err = mhd_SCKT_GET_LERR (); - - if (mhd_SCKT_ERR_IS_EAGAIN (err)) - { - c->sk_ready = /* Clear 'recv-ready' */ - (enum mhd_SocketNetState) - (((unsigned int) c->sk_ready) - & (~mhd_SOCKET_NET_STATE_RECV_READY)); - return mhd_SOCKET_ERR_AGAIN; - } - else if (mhd_SCKT_ERR_IS_CONNRESET (err)) - return mhd_SOCKET_ERR_CONNRESET; - else if (mhd_SCKT_ERR_IS_EINTR (err)) - return mhd_SOCKET_ERR_INTR; - else if (mhd_SCKT_ERR_IS_CONN_BROKEN (err)) - return mhd_SOCKET_ERR_CONN_BROKEN; - else if (mhd_SCKT_ERR_IS_NOTCONN (err)) - return mhd_SOCKET_ERR_NOTCONN; - else if (mhd_SCKT_ERR_IS_LOW_MEM (err)) - return mhd_SOCKET_ERR_NOMEM; - else if (mhd_SCKT_ERR_IS_BADF (err)) - return mhd_SOCKET_ERR_BADF; - else if (mhd_SCKT_ERR_IS_EINVAL (err)) - return mhd_SOCKET_ERR_INVAL; - else if (mhd_SCKT_ERR_IS_OPNOTSUPP (err)) - return mhd_SOCKET_ERR_OPNOTSUPP; - else if (mhd_SCKT_ERR_IS_NOTSOCK (err)) - return mhd_SOCKET_ERR_NOTSOCK; - } - - return mhd_SOCKET_ERR_OTHER; + return mhd_plain_recv(c, buf_size, buf, received); } diff --git a/src/mhd2/mhd_recv.h b/src/mhd2/mhd_recv.h index 8f539141..c6a79c8d 100644 --- a/src/mhd2/mhd_recv.h +++ b/src/mhd2/mhd_recv.h @@ -49,8 +49,9 @@ struct MHD_Connection; /* forward declaration */ MHD_INTERNAL enum mhd_SocketError mhd_recv (struct MHD_Connection *restrict c, size_t buf_size, - void *restrict buf, + char buffer[MHD_FN_PAR_DYN_ARR_SIZE_(buf_size)], size_t *restrict received) MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_SIZE_(3,2) MHD_FN_PAR_OUT_ (4); + #endif /* ! MHD_RECV_H */ diff --git a/src/mhd2/mhd_reply.h b/src/mhd2/mhd_reply.h index 4b223e69..3caf6384 100644 --- a/src/mhd2/mhd_reply.h +++ b/src/mhd2/mhd_reply.h @@ -55,13 +55,37 @@ struct MHD_Reply_Properties bool chunked; /**< Use chunked encoding for reply */ }; -#if defined(MHD_USE_SENDFILE) -enum MHD_resp_sender_ +/** + * The location of the reply content + */ +enum MHD_FIXED_ENUM_ mhd_ReplyContentLocation { - MHD_resp_sender_std = 0, - MHD_resp_sender_sendfile + /** + * Reply content is absent + */ + mhd_REPLY_CNTN_LOC_NOWHERE = 0 + , + /** + * Reply content is in the response buffer + */ + mhd_REPLY_CNTN_LOC_RESP_BUF + , + /** + * Reply content is in the connection buffer + */ + mhd_REPLY_CNTN_LOC_CONN_BUF + , + /** + * Reply content is in the vector data + */ + mhd_REPLY_CNTN_LOC_IOV + , + /** + * Reply content is in the file, to be used with sendfile() function + */ + mhd_REPLY_CNTN_LOC_FILE }; -#endif /* MHD_USE_SENDFILE */ + /** * Reply-specific values. @@ -88,11 +112,13 @@ struct MHD_Reply bool responseIcy; /** - * Current write position in the actual response - * (excluding headers, content only; should be 0 - * while sending headers). + * Current rest position in the actual content (should be 0 while + * sending headers). + * When sending buffers located in the connection buffers, it is updated + * when the data copied to the buffers. In other cases it is updated when + * data is actually sent. */ - uint_fast64_t rsp_write_position; + uint_fast64_t rsp_cntn_read_pos; /** * The copy of iov response. @@ -102,9 +128,10 @@ struct MHD_Reply */ struct mhd_iovec_track resp_iov; -#if defined(MHD_USE_SENDFILE) - enum MHD_resp_sender_ resp_sender; -#endif /* MHD_USE_SENDFILE */ + /** + * The location of the reply content + */ + enum mhd_ReplyContentLocation cntn_loc; /** * Reply-specific properties diff --git a/src/mhd2/mhd_request.h b/src/mhd2/mhd_request.h index e7277149..a9e0f341 100644 --- a/src/mhd2/mhd_request.h +++ b/src/mhd2/mhd_request.h @@ -32,6 +32,8 @@ #include "mhd_sys_options.h" #include "sys_base_types.h" #include "sys_bool_type.h" +#include "mhd_public_api.h" + #include "mhd_dlinked_list.h" #include "http_prot_ver.h" @@ -175,48 +177,27 @@ union MHD_StartOrSize size_t size; }; -struct MHD_HTTP_Req_Header; /* forward declarations */ +struct mhd_RequestField; /* forward declarations */ -mhd_DLINKEDL_LINKS_DEF (MHD_HTTP_Req_Header); +mhd_DLINKEDL_LINKS_DEF (mhd_RequestField); /** * Header, footer, or cookie for HTTP request. */ -struct MHD_HTTP_Req_Header +struct mhd_RequestField { + /** + * The field data + */ + struct MHD_NameValueKind field; + /** * Headers are kept in a double-linked list. */ - mhd_DLNKDL_LINKS (MHD_HTTP_Req_Header,list); - - /** - * The name of the header (key), without the colon. - */ - const char *header; - - /** - * The length of the @a header, not including the final zero termination. - */ - size_t header_size; - - /** - * The value of the header. - */ - const char *value; - - /** - * The length of the @a value, not including the final zero termination. - */ - size_t value_size; - - /** - * Type of the value. - */ - enum MHD_ValueKind kind; - + mhd_DLNKDL_LINKS (mhd_RequestField,fields); }; -mhd_DLINKEDL_LIST_DEF (MHD_HTTP_Req_Header); +mhd_DLINKEDL_LIST_DEF (mhd_RequestField); /** * Request-specific values. @@ -228,7 +209,7 @@ struct MHD_Request /** * Linked list of parsed headers. */ - mhd_DLNKDL_LIST (MHD_HTTP_Req_Header,list); + mhd_DLNKDL_LIST (mhd_RequestField,fields); /** * The action set by the application diff --git a/src/mhd2/mhd_response.h b/src/mhd2/mhd_response.h index 94e8425c..0dd8c9b8 100644 --- a/src/mhd2/mhd_response.h +++ b/src/mhd2/mhd_response.h @@ -39,7 +39,11 @@ #include "mhd_iovec.h" -#include "mhd_locks.h" +#ifdef MHD_USE_THREADS +# include "mhd_locks.h" +#endif + +#include "mhd_atomic_counter.h" struct ResponseOptions; /* forward declaration */ @@ -72,17 +76,17 @@ struct mhd_ResponseHeader /** * The type of content */ -enum mhd_ResponseContentType +enum mhd_ResponseContentDataType { - mhd_RESPONSE_CONTENT_INVALID = 0 + mhd_RESPONSE_CONTENT_DATA_INVALID = 0 , - mhd_RESPONSE_CONTENT_BUFFER + mhd_RESPONSE_CONTENT_DATA_BUFFER , - mhd_RESPONSE_CONTENT_IOVEC + mhd_RESPONSE_CONTENT_DATA_IOVEC , - mhd_RESPONSE_CONTENT_FILE + mhd_RESPONSE_CONTENT_DATA_FILE , - mhd_RESPONSE_CONTENT_CALLBACK + mhd_RESPONSE_CONTENT_DATA_CALLBACK }; /** @@ -159,7 +163,7 @@ union mhd_ResponseContent const unsigned char *restrict buf; /** - * The + * The I/O vector data */ struct mhd_ResponseIoVec iovec; @@ -202,17 +206,10 @@ struct mhd_ResponseReuseData * The number of active uses of the response. * Used only when @a reusable is 'true'. * When number reached zero, the response is destroyed. - * Must be checked only under the @a cnt_lock */ - volatile uint_fast64_t counter; + struct mhd_AtomicCounter counter; #ifdef MHD_USE_THREADS - /** - * The mutex for @a counter access. - * Used only when @a reusable is 'true'. - */ - mhd_mutex cnt_lock; - /** * The mutex for @a settings access. * Used only when @a reusable is 'true'. @@ -261,6 +258,8 @@ struct mhd_ResponseDebug mhd_DLINKEDL_LIST_DEF (mhd_ResponseHeader); +// TODO: Group members in structs + struct MHD_Response { /** @@ -272,12 +271,12 @@ struct MHD_Response * The size of the response. * #MHD_SIZE_UNKNOWN if size is undefined */ - uint_fast64_t size; + uint_fast64_t cntn_size; /** * The type of the content data */ - enum mhd_ResponseContentType cntn_type; + enum mhd_ResponseContentDataType cntn_dtype; /** * The data of the content of the response diff --git a/src/microhttpd/mhd_send.c b/src/mhd2/mhd_send.c similarity index 51% rename from src/microhttpd/mhd_send.c rename to src/mhd2/mhd_send.c index db9e30fd..ce1c5591 100644 --- a/src/microhttpd/mhd_send.c +++ b/src/mhd2/mhd_send.c @@ -1,15 +1,16 @@ /* This file is part of libmicrohttpd - Copyright (C) 2017-2023 Karlson2k (Evgeny Grin), Full re-write of buffering and - pushing, many bugs fixes, optimisations, sendfile() porting + Copyright (C) 2017-2024 Karlson2k (Evgeny Grin), Full re-write of buffering + and pushing, many bugs fixes, optimisations, + sendfile() porting Copyright (C) 2019 ng0 , Initial version of send() wrappers - This library is free software; you can redistribute it and/or + GNU libmicrohttpd is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. - This library is distributed in the hope that it will be useful, + GNU libmicrohttpd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. @@ -21,7 +22,7 @@ */ /** - * @file microhttpd/mhd_send.c + * @file src/mhd2/mhd_send.c * @brief Implementation of send() wrappers and helper functions. * @author Karlson2k (Evgeny Grin) * @author ng0 (N. Gillmann) @@ -34,47 +35,63 @@ * large a chunk as possible to the socket. Alternatively, * use madvise(..., MADV_SEQUENTIAL). */ +#include "mhd_sys_options.h" + #include "mhd_send.h" -#ifdef MHD_LINUX_SOLARIS_SENDFILE -#include -#endif /* MHD_LINUX_SOLARIS_SENDFILE */ +#include "sys_sockets_headers.h" +#include "sys_ip_headers.h" +#include "mhd_sockets_macros.h" +#include "daemon_logger.h" + +#include + +#include "mhd_iovec.h" +#ifdef HAVE_LINUX_SENDFILE +# include +#endif /* HAVE_LINUX_SENDFILE */ + #if defined(HAVE_FREEBSD_SENDFILE) || defined(HAVE_DARWIN_SENDFILE) -#include -#include -#include +# include +# include +# include #endif /* HAVE_FREEBSD_SENDFILE || HAVE_DARWIN_SENDFILE */ #ifdef HAVE_SYS_PARAM_H /* For FreeBSD version identification */ -#include +# include #endif /* HAVE_SYS_PARAM_H */ #ifdef HAVE_SYSCONF -#include +# include #endif /* HAVE_SYSCONF */ #include "mhd_assert.h" #include "mhd_limits.h" -#ifdef MHD_VECT_SEND -#if (! defined(HAVE_SENDMSG) || ! defined(MSG_NOSIGNAL)) && \ - defined(MHD_SEND_SPIPE_SUPPRESS_POSSIBLE) && \ - defined(MHD_SEND_SPIPE_SUPPRESS_NEEDED) -#define _MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED 1 -#endif /* (!HAVE_SENDMSG || !MSG_NOSIGNAL) && - MHD_SEND_SPIPE_SUPPRESS_POSSIBLE && MHD_SEND_SPIPE_SUPPRESS_NEEDED */ -#endif /* MHD_VECT_SEND */ +#if defined(HAVE_SENDMSG) || defined(HAVE_WRITEV) || \ + defined(MHD_WINSOCK_SOCKETS) +# define mhd_USE_VECT_SEND 1 +#endif /* HAVE_SENDMSG || HAVE_WRITEV || MHD_WINSOCK_SOCKETS */ + + +#ifdef mhd_USE_VECT_SEND +# if (! defined(HAVE_SENDMSG) || ! defined(MSG_NOSIGNAL)) && \ + defined(mhd_SEND_SPIPE_SUPPRESS_POSSIBLE) && \ + defined(mhd_SEND_SPIPE_SUPPRESS_NEEDED) +# define mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED 1 +# endif /* (!HAVE_SENDMSG || !MSG_NOSIGNAL) && + mhd_SEND_SPIPE_SUPPRESS_POSSIBLE && mhd_SEND_SPIPE_SUPPRESS_NEEDED */ +#endif /* mhd_USE_VECT_SEND */ /** * sendfile() chuck size */ -#define MHD_SENFILE_CHUNK_ (0x20000) +#define mhd_SENFILE_CHUNK_SIZE (0x20000) /** * sendfile() chuck size for thread-per-connection */ -#define MHD_SENFILE_CHUNK_THR_P_C_ (0x200000) +#define mhd_SENFILE_CHUNK_SIZE_FOR_THR_P_C (0x200000) -#ifdef HAVE_FREEBSD_SENDFILE -#ifdef SF_FLAGS +#if defined(HAVE_FREEBSD_SENDFILE) && defined(SF_FLAGS) /** * FreeBSD sendfile() flags */ @@ -101,16 +118,19 @@ freebsd_sendfile_init_ (void) else { freebsd_sendfile_flags_ = - SF_FLAGS ((uint16_t) ((MHD_SENFILE_CHUNK_ + sys_page_size - 1) - / sys_page_size), SF_NODISKIO); + SF_FLAGS ((uint_fast16_t) \ + ((mhd_SENFILE_CHUNK_SIZE + sys_page_size - 1) / sys_page_size) \ + & 0xFFFFU, SF_NODISKIO); freebsd_sendfile_flags_thd_p_c_ = - SF_FLAGS ((uint16_t) ((MHD_SENFILE_CHUNK_THR_P_C_ + sys_page_size - 1) - / sys_page_size), SF_NODISKIO); + SF_FLAGS ((uint_fast16_t) \ + ((mhd_SENFILE_CHUNK_SIZE_FOR_THR_P_C + sys_page_size - 1) \ + / sys_page_size) & 0xFFFFU, SF_NODISKIO); } } -#endif /* SF_FLAGS */ +#else /* ! HAVE_FREEBSD_SENDFILE || ! SF_FLAGS */ +# define freebsd_sendfile_init_() (void)0 #endif /* HAVE_FREEBSD_SENDFILE */ @@ -128,11 +148,11 @@ iov_max_init_ (void) mhd_iov_max_ = (unsigned long) res; else { -#if defined(IOV_MAX) +# if defined(IOV_MAX) mhd_iov_max_ = IOV_MAX; -#else /* ! IOV_MAX */ +# else /* ! IOV_MAX */ mhd_iov_max_ = 8; /* Should be the safe limit */ -#endif /* ! IOV_MAX */ +# endif /* ! IOV_MAX */ } } @@ -140,46 +160,44 @@ iov_max_init_ (void) /** * IOV_MAX (run-time) value */ -#define _MHD_IOV_MAX mhd_iov_max_ -#elif defined(IOV_MAX) +# define mhd_IOV_MAX mhd_iov_max_ +#else /* ! HAVE_SYSCONF || ! _SC_IOV_MAX */ +# define iov_max_init_() (void)0 +# if defined(IOV_MAX) /** * IOV_MAX (static) value */ -#define _MHD_IOV_MAX IOV_MAX -#endif /* HAVE_SYSCONF && _SC_IOV_MAX */ +# define mhd_IOV_MAX IOV_MAX +# endif /* IOV_MAX */ +#endif /* ! HAVE_SYSCONF || ! _SC_IOV_MAX */ /** * Initialises static variables */ void -MHD_send_init_static_vars_ (void) +mhd_send_init_static_vars (void) { -#ifdef HAVE_FREEBSD_SENDFILE /* FreeBSD 11 and later allow to specify read-ahead size * and handles SF_NODISKIO differently. * SF_FLAGS defined only on FreeBSD 11 and later. */ -#ifdef SF_FLAGS freebsd_sendfile_init_ (); -#endif /* SF_FLAGS */ -#endif /* HAVE_FREEBSD_SENDFILE */ -#if defined(HAVE_SYSCONF) && defined(_SC_IOV_MAX) + iov_max_init_ (); -#endif /* HAVE_SYSCONF && _SC_IOV_MAX */ } -bool -MHD_connection_set_nodelay_state_ (struct MHD_Connection *connection, - bool nodelay_state) +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_connection_set_nodelay_state (struct MHD_Connection *connection, + bool nodelay_state) { #ifdef TCP_NODELAY - const MHD_SCKT_OPT_BOOL_ off_val = 0; - const MHD_SCKT_OPT_BOOL_ on_val = 1; + static const mhd_SCKT_OPT_BOOL off_val = 0; + static const mhd_SCKT_OPT_BOOL on_val = 1; int err_code; - if (_MHD_YES == connection->is_nonip) + if (mhd_T_IS_YES(connection->is_nonip)) return false; if (0 == setsockopt (connection->socket_fd, @@ -188,121 +206,76 @@ MHD_connection_set_nodelay_state_ (struct MHD_Connection *connection, (const void *) (nodelay_state ? &on_val : &off_val), sizeof (off_val))) { - connection->sk_nodelay = nodelay_state; + connection->sk_nodelay = nodelay_state ? mhd_T_YES : mhd_T_NO; return true; } - err_code = MHD_socket_get_error_ (); - if (MHD_SCKT_ERR_IS_ (err_code, MHD_SCKT_EINVAL_) || - MHD_SCKT_ERR_IS_ (err_code, MHD_SCKT_ENOPROTOOPT_) || - MHD_SCKT_ERR_IS_ (err_code, MHD_SCKT_ENOTSOCK_)) + err_code = mhd_SCKT_GET_LERR (); + if ((mhd_T_IS_NOT_YES(connection->is_nonip)) && + (mhd_SCKT_ERR_IS_EINVAL (err_code) || + mhd_SCKT_ERR_IS_NOPROTOOPT (err_code) || + mhd_SCKT_ERR_IS_NOTSOCK (err_code))) { - if (_MHD_UNKNOWN == connection->is_nonip) - connection->is_nonip = _MHD_YES; -#ifdef HAVE_MESSAGES - else - { - MHD_DLOG (connection->daemon, - _ ("Setting %s option to %s state failed " - "for TCP/IP socket %d: %s\n"), - "TCP_NODELAY", - nodelay_state ? _ ("ON") : _ ("OFF"), - (int) connection->socket_fd, - MHD_socket_strerr_ (err_code)); - } -#endif /* HAVE_MESSAGES */ + connection->is_nonip = mhd_T_YES; } -#ifdef HAVE_MESSAGES else { - MHD_DLOG (connection->daemon, - _ ("Setting %s option to %s state failed: %s\n"), - "TCP_NODELAY", - nodelay_state ? _ ("ON") : _ ("OFF"), - MHD_socket_strerr_ (err_code)); + MHD_LOG_MSG (connection->daemon, MHD_SC_SOCKET_TCP_NODELAY_FAILED, \ + "Failed to set required TCP_NODELAY option for the socket."); } -#endif /* HAVE_MESSAGES */ - #else /* ! TCP_NODELAY */ - (void) connection; (void) nodelay_state; /* Mute compiler warnings */ + (void) nodelay_state; /* Mute compiler warnings */ + connection->sk_nodelay = mhd_T_NO; #endif /* ! TCP_NODELAY */ return false; } -/** - * Set required cork state for connection socket - * - * The function automatically updates sk_corked state. - * - * @param connection the connection to manipulate - * @param cork_state the requested new state of socket - * @return true if succeed, false if failed or not supported - * by the current platform / kernel. - */ -bool -MHD_connection_set_cork_state_ (struct MHD_Connection *connection, - bool cork_state) +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_connection_set_cork_state (struct MHD_Connection *connection, + bool cork_state) { -#if defined(MHD_TCP_CORK_NOPUSH) - const MHD_SCKT_OPT_BOOL_ off_val = 0; - const MHD_SCKT_OPT_BOOL_ on_val = 1; +#if defined(mhd_TCP_CORK_NOPUSH) + static const mhd_SCKT_OPT_BOOL off_val = 0; + static const mhd_SCKT_OPT_BOOL on_val = 1; int err_code; - if (_MHD_YES == connection->is_nonip) + if (mhd_T_IS_YES(connection->is_nonip)) return false; + if (0 == setsockopt (connection->socket_fd, IPPROTO_TCP, - MHD_TCP_CORK_NOPUSH, + mhd_TCP_CORK_NOPUSH, (const void *) (cork_state ? &on_val : &off_val), sizeof (off_val))) { - connection->sk_corked = cork_state; + connection->sk_corked = cork_state ? mhd_T_YES : mhd_T_NO; return true; } - err_code = MHD_socket_get_error_ (); - if (MHD_SCKT_ERR_IS_ (err_code, MHD_SCKT_EINVAL_) || - MHD_SCKT_ERR_IS_ (err_code, MHD_SCKT_ENOPROTOOPT_) || - MHD_SCKT_ERR_IS_ (err_code, MHD_SCKT_ENOTSOCK_)) + err_code = mhd_SCKT_GET_LERR (); + if ((mhd_T_IS_NOT_YES(connection->is_nonip)) && + (mhd_SCKT_ERR_IS_EINVAL (err_code) || + mhd_SCKT_ERR_IS_NOPROTOOPT (err_code) || + mhd_SCKT_ERR_IS_NOTSOCK (err_code))) { - if (_MHD_UNKNOWN == connection->is_nonip) - connection->is_nonip = _MHD_YES; -#ifdef HAVE_MESSAGES - else - { - MHD_DLOG (connection->daemon, - _ ("Setting %s option to %s state failed " - "for TCP/IP socket %d: %s\n"), -#ifdef TCP_CORK - "TCP_CORK", -#else /* ! TCP_CORK */ - "TCP_NOPUSH", -#endif /* ! TCP_CORK */ - cork_state ? _ ("ON") : _ ("OFF"), - (int) connection->socket_fd, - MHD_socket_strerr_ (err_code)); - } -#endif /* HAVE_MESSAGES */ + connection->is_nonip = mhd_T_YES; } -#ifdef HAVE_MESSAGES else { - MHD_DLOG (connection->daemon, - _ ("Setting %s option to %s state failed: %s\n"), -#ifdef TCP_CORK - "TCP_CORK", -#else /* ! TCP_CORK */ - "TCP_NOPUSH", -#endif /* ! TCP_CORK */ - cork_state ? _ ("ON") : _ ("OFF"), - MHD_socket_strerr_ (err_code)); +# ifdef TCP_CORK + MHD_LOG_MSG (connection->daemon, MHD_SC_SOCKET_TCP_CORK_NOPUSH_FAILED, \ + "Failed to set required TCP_CORK option for the socket."); +# else + MHD_LOG_MSG (connection->daemon, MHD_SC_SOCKET_TCP_CORK_NOPUSH_FAILED, \ + "Failed to set required TCP_NOPUSH option for the socket."); +# endif } -#endif /* HAVE_MESSAGES */ -#else /* ! MHD_TCP_CORK_NOPUSH */ - (void) connection; (void) cork_state; /* Mute compiler warnings. */ -#endif /* ! MHD_TCP_CORK_NOPUSH */ +#else /* ! mhd_TCP_CORK_NOPUSH */ + (void) cork_state; /* Mute compiler warnings. */ + connection->sk_corked = mhd_T_NO; +#endif /* ! mhd_TCP_CORK_NOPUSH */ return false; } @@ -326,8 +299,11 @@ pre_send_setopt (struct MHD_Connection *connection, * Final piece is indicated by push_data == true. */ const bool buffer_data = (! push_data); - if (_MHD_YES == connection->is_nonip) + if (mhd_T_IS_YES(connection->is_nonip)) return; + + // TODO: support inheriting of TCP_NODELAY and TCP_NOPUSH + /* The goal is to minimise the total number of additional sys-calls * before and after send(). * The following tricky (over-)complicated algorithm typically use zero, @@ -336,67 +312,72 @@ pre_send_setopt (struct MHD_Connection *connection, if (buffer_data) { /* Need to buffer data if possible. */ -#ifdef MHD_USE_MSG_MORE +#ifdef mhd_USE_MSG_MORE if (plain_send) return; /* Data is buffered by send() with MSG_MORE flag. * No need to check or change anything. */ -#else /* ! MHD_USE_MSG_MORE */ +#else /* ! mhd_USE_MSG_MORE */ (void) plain_send; /* Mute compiler warning. */ -#endif /* ! MHD_USE_MSG_MORE */ +#endif /* ! mhd_USE_MSG_MORE */ -#ifdef MHD_TCP_CORK_NOPUSH - if (_MHD_ON == connection->sk_corked) +#ifdef mhd_TCP_CORK_NOPUSH + if (mhd_T_IS_YES(connection->sk_corked)) return; /* The connection was already corked. */ - if (MHD_connection_set_cork_state_ (connection, true)) + /* Prefer 'cork' over 'no delay' as the 'cork' buffers better, regardless + * of the number of received ACKs. */ + if (mhd_connection_set_cork_state (connection, true)) return; /* The connection has been corked. */ /* Failed to cork the connection. * Really unlikely to happen on TCP connections. */ -#endif /* MHD_TCP_CORK_NOPUSH */ - if (_MHD_OFF == connection->sk_nodelay) +#endif /* mhd_TCP_CORK_NOPUSH */ + if (mhd_T_IS_NO(connection->sk_nodelay)) return; /* TCP_NODELAY was not set for the socket. * Nagle's algorithm will buffer some data. */ /* Try to reset TCP_NODELAY state for the socket. * Ignore possible error as no other options exist to * buffer data. */ - MHD_connection_set_nodelay_state_ (connection, false); + mhd_connection_set_nodelay_state (connection, false); /* TCP_NODELAY has been (hopefully) reset for the socket. * Nagle's algorithm will buffer some data. */ return; } - /* Need to push data after send() */ - /* If additional sys-call is required prefer to make it after the send() - * as the next send() may consume only part of the prepared data and + /* Need to push data after the next send() */ + /* If additional sys-call is required, prefer to make it only after the send() + * (if possible) as this send() may consume only part of the prepared data and * more send() calls will be used. */ -#ifdef MHD_TCP_CORK_NOPUSH -#ifdef _MHD_CORK_RESET_PUSH_DATA -#ifdef _MHD_CORK_RESET_PUSH_DATA_ALWAYS +#ifdef mhd_TCP_CORK_NOPUSH +# ifdef mhd_CORK_RESET_PUSH_DATA +# ifdef mhd_CORK_RESET_PUSH_DATA_ALWAYS /* Data can be pushed immediately by uncorking socket regardless of * cork state before. */ /* This is typical for Linux, no other kernel with - * such behavior are known so far. */ + * such behaviour are known so far. */ /* No need to check the current state of TCP_CORK / TCP_NOPUSH * as reset of cork will push the data anyway. */ return; /* Data may be pushed by resetting of * TCP_CORK / TCP_NOPUSH after send() */ -#else /* ! _MHD_CORK_RESET_PUSH_DATA_ALWAYS */ +# else /* ! mhd_CORK_RESET_PUSH_DATA_ALWAYS */ /* Reset of TCP_CORK / TCP_NOPUSH will push the data * only if socket is corked. */ -#ifdef _MHD_NODELAY_SET_PUSH_DATA_ALWAYS +# ifdef mhd_NODELAY_SET_PUSH_DATA_ALWAYS /* Data can be pushed immediately by setting TCP_NODELAY regardless * of TCP_NODDELAY or corking state before. */ - /* Dead code currently, no known kernels with such behavior. */ + /* Dead code currently, no known kernels with such behaviour and without + * pushing by uncorking. */ return; /* Data may be pushed by setting of TCP_NODELAY after send(). No need to make extra sys-calls before send().*/ -#else /* ! _MHD_NODELAY_SET_PUSH_DATA_ALWAYS */ +# else /* ! mhd_NODELAY_SET_PUSH_DATA_ALWAYS */ -#ifdef _MHD_NODELAY_SET_PUSH_DATA +/* These next comment blocks are just generic description for the possible + * choices for the code below. */ +# ifdef mhd_NODELAY_SET_PUSH_DATA /* Setting of TCP_NODELAY will push the data only if * both TCP_NODELAY and TCP_CORK / TCP_NOPUSH were not set. */ @@ -404,31 +385,32 @@ pre_send_setopt (struct MHD_Connection *connection, * socket was corked before or by setting TCP_NODELAY if * socket was not corked and TCP_NODELAY was not set before. */ - /* Dead code currently as Linux is the only kernel that push - * data by setting of TCP_NODELAY and Linux push data always. */ -#else /* ! _MHD_NODELAY_SET_PUSH_DATA */ + /* This combination not possible currently as Linux is the only kernel that + * pushes data by setting of TCP_NODELAY and Linux pushes data always + * by TCP_NODELAY, regardless previous TCP_NODELAY state. */ +# else /* ! mhd_NODELAY_SET_PUSH_DATA */ /* Data can be pushed immediately by uncorking socket or * can be pushed by send() on uncorked socket if * TCP_NODELAY was set *before*. */ - /* This is typical FreeBSD behavior. */ -#endif /* ! _MHD_NODELAY_SET_PUSH_DATA */ + /* This is typical modern FreeBSD and OpenBSD behaviour. */ +# endif /* ! mhd_NODELAY_SET_PUSH_DATA */ - if (_MHD_ON == connection->sk_corked) + if (mhd_T_IS_YES(connection->sk_corked)) return; /* Socket is corked. Data can be pushed by resetting of * TCP_CORK / TCP_NOPUSH after send() */ - else if (_MHD_OFF == connection->sk_corked) + else if (mhd_T_IS_NO(connection->sk_corked)) { /* The socket is not corked. */ - if (_MHD_ON == connection->sk_nodelay) + if (mhd_T_IS_YES(connection->sk_nodelay)) return; /* TCP_NODELAY was already set, * data will be pushed automatically by the next send() */ -#ifdef _MHD_NODELAY_SET_PUSH_DATA - else if (_MHD_UNKNOWN == connection->sk_nodelay) +# ifdef mhd_NODELAY_SET_PUSH_DATA + else if (mhd_T_IS_MAYBE(connection->sk_nodelay)) { - /* Setting TCP_NODELAY may push data. + /* Setting TCP_NODELAY may push data NOW. * Cork socket here and uncork after send(). */ - if (MHD_connection_set_cork_state_ (connection, true)) + if (mhd_connection_set_cork_state (connection, true)) return; /* The connection has been corked. * Data can be pushed by resetting of * TCP_CORK / TCP_NOPUSH after send() */ @@ -438,41 +420,40 @@ pre_send_setopt (struct MHD_Connection *connection, * Really unlikely to happen on TCP connections */ /* Have to set TCP_NODELAY. * If TCP_NODELAY real system state was OFF then - * already buffered data may be pushed here, but this is unlikely - * to happen as it is only a backup solution when corking has failed. + * already buffered data may be pushed NOW, but it is unlikely + * to happen as this is only a backup solution when corking has failed. * Ignore possible error here as no other options exist to * push data. */ - MHD_connection_set_nodelay_state_ (connection, true); + mhd_connection_set_nodelay_state (connection, true); /* TCP_NODELAY has been (hopefully) set for the socket. * The data will be pushed by the next send(). */ return; } } -#endif /* _MHD_NODELAY_SET_PUSH_DATA */ +# endif /* mhd_NODELAY_SET_PUSH_DATA */ else { -#ifdef _MHD_NODELAY_SET_PUSH_DATA - /* TCP_NODELAY was switched off and - * the socket is not corked. */ -#else /* ! _MHD_NODELAY_SET_PUSH_DATA */ - /* Socket is not corked and TCP_NODELAY was not set or unknown. */ -#endif /* ! _MHD_NODELAY_SET_PUSH_DATA */ +# ifdef mhd_NODELAY_SET_PUSH_DATA + /* The socket is not corked and TCP_NODELAY is switched off. */ +# else /* ! mhd_NODELAY_SET_PUSH_DATA */ + /* The socket is not corked and TCP_NODELAY is not set or unknown. */ +# endif /* ! mhd_NODELAY_SET_PUSH_DATA */ - /* At least one additional sys-call is required. */ + /* At least one additional sys-call before send() is required. */ /* Setting TCP_NODELAY is optimal here as data will be pushed * automatically by the next send() and no additional * sys-call are needed after the send(). */ - if (MHD_connection_set_nodelay_state_ (connection, true)) + if (mhd_connection_set_nodelay_state (connection, true)) return; else { /* Failed to set TCP_NODELAY for the socket. * Really unlikely to happen on TCP connections. */ /* Cork the socket here and make additional sys-call - * to uncork the socket after send(). */ + * to uncork the socket after send(). This will push the data. */ /* Ignore possible error here as no other options exist to * push data. */ - MHD_connection_set_cork_state_ (connection, true); + mhd_connection_set_cork_state (connection, true); /* The connection has been (hopefully) corked. * Data can be pushed by resetting of TCP_CORK / TCP_NOPUSH * after send() */ @@ -480,60 +461,76 @@ pre_send_setopt (struct MHD_Connection *connection, } } } - /* Corked state is unknown. Need to make sys-call here otherwise + /* Corked state is unknown. Need to make a sys-call here otherwise * data may not be pushed. */ - if (MHD_connection_set_cork_state_ (connection, true)) + if (mhd_connection_set_cork_state (connection, true)) return; /* The connection has been corked. * Data can be pushed by resetting of * TCP_CORK / TCP_NOPUSH after send() */ /* The socket cannot be corked. * Really unlikely to happen on TCP connections */ - if (_MHD_ON == connection->sk_nodelay) + if (mhd_T_IS_YES(connection->sk_nodelay)) return; /* TCP_NODELAY was already set, * data will be pushed by the next send() */ + /* Have to set TCP_NODELAY. */ -#ifdef _MHD_NODELAY_SET_PUSH_DATA +# ifdef mhd_NODELAY_SET_PUSH_DATA /* If TCP_NODELAY state was unknown (external connection) then * already buffered data may be pushed here, but this is unlikely * to happen as it is only a backup solution when corking has failed. */ -#endif /* _MHD_NODELAY_SET_PUSH_DATA */ +# endif /* mhd_NODELAY_SET_PUSH_DATA */ /* Ignore possible error here as no other options exist to * push data. */ - MHD_connection_set_nodelay_state_ (connection, true); + mhd_connection_set_nodelay_state (connection, true); /* TCP_NODELAY has been (hopefully) set for the socket. * The data will be pushed by the next send(). */ return; -#endif /* ! _MHD_NODELAY_SET_PUSH_DATA_ALWAYS */ -#endif /* ! _MHD_CORK_RESET_PUSH_DATA_ALWAYS */ -#else /* ! _MHD_CORK_RESET_PUSH_DATA */ +# endif /* ! mhd_NODELAY_SET_PUSH_DATA_ALWAYS */ +# endif /* ! mhd_CORK_RESET_PUSH_DATA_ALWAYS */ +# else /* ! mhd_CORK_RESET_PUSH_DATA */ + +# ifndef mhd_NODELAY_SET_PUSH_DATA /* Neither uncorking the socket or setting TCP_NODELAY * push the data immediately. */ /* The only way to push the data is to use send() on uncorked * socket with TCP_NODELAY switched on . */ - /* This is a typical *BSD (except FreeBSD) and Darwin behavior. */ + /* This is old FreeBSD and Darwin behaviour. */ /* Uncork socket if socket wasn't uncorked. */ - if (_MHD_OFF != connection->sk_corked) - MHD_connection_set_cork_state_ (connection, false); + if (mhd_T_IS_NOT_NO(connection->sk_corked)) + mhd_connection_set_cork_state (connection, false); /* Set TCP_NODELAY if it wasn't set. */ - if (_MHD_ON != connection->sk_nodelay) - MHD_connection_set_nodelay_state_ (connection, true); + if (mhd_T_IS_NOT_YES(connection->sk_nodelay)) + mhd_connection_set_nodelay_state (connection, true); return; -#endif /* ! _MHD_CORK_RESET_PUSH_DATA */ -#else /* ! MHD_TCP_CORK_NOPUSH */ +# else /* mhd_NODELAY_SET_PUSH_DATA */ + /* Setting TCP_NODELAY push the data immediately. */ + + /* Dead code currently as Linux kernel is only kernel which push by + * setting TCP_NODELAY. The same kernel push data by resetting TCP_CORK. */ +# ifdef mhd_NODELAY_SET_PUSH_DATA_ALWAYS + return; /* Data may be pushed by setting of TCP_NODELAY after send(). + No need to make extra sys-calls before send().*/ +# else /* ! mhd_NODELAY_SET_PUSH_DATA_ALWAYS */ + /* Cannot set TCP_NODELAY here as it would push data NOW. + * Set TCP_NODELAY after the send(), together if uncorking if necessary. */ +# endif /* ! mhd_NODELAY_SET_PUSH_DATA_ALWAYS */ +# endif /* mhd_NODELAY_SET_PUSH_DATA */ +# endif /* ! mhd_CORK_RESET_PUSH_DATA */ +#else /* ! mhd_TCP_CORK_NOPUSH */ /* Buffering of data is controlled only by * Nagel's algorithm. */ /* Set TCP_NODELAY if it wasn't set. */ - if (_MHD_ON != connection->sk_nodelay) - MHD_connection_set_nodelay_state_ (connection, true); -#endif /* ! MHD_TCP_CORK_NOPUSH */ + if (mhd_T_IS_NOT_YES(connection->sk_nodelay)) + mhd_connection_set_nodelay_state (connection, true); +#endif /* ! mhd_TCP_CORK_NOPUSH */ } -#ifndef _MHD_CORK_RESET_PUSH_DATA_ALWAYS +#ifndef mhd_CORK_RESET_PUSH_DATA_ALWAYS /** * Send zero-sized data * @@ -546,27 +543,24 @@ pre_send_setopt (struct MHD_Connection *connection, * @return true if succeed, false if failed */ static bool -zero_send_ (struct MHD_Connection *connection) +zero_send (struct MHD_Connection *connection) { - int dummy; + static const int dummy; - if (_MHD_YES == connection->is_nonip) + if (mhd_T_IS_YES(connection->is_nonip)) return false; - mhd_assert (_MHD_OFF == connection->sk_corked); - mhd_assert (_MHD_ON == connection->sk_nodelay); + mhd_assert (mhd_T_IS_NO(connection->sk_corked)); + mhd_assert (mhd_T_IS_YES(connection->sk_nodelay)); dummy = 0; /* Mute compiler and analyzer warnings */ - if (0 == MHD_send_ (connection->socket_fd, &dummy, 0)) + if (0 == mhd_sys_send (connection->socket_fd, &dummy, 0)) return true; -#ifdef HAVE_MESSAGES - MHD_DLOG (connection->daemon, - _ ("Zero-send failed: %s\n"), - MHD_socket_last_strerr_ () ); -#endif /* HAVE_MESSAGES */ + MHD_LOG_MSG (connection->daemon, MHD_SC_SOCKET_ZERO_SEND_FAILED, \ + "Failed to push the data by zero-sized send."); return false; } -#endif /* ! _MHD_CORK_RESET_PUSH_DATA_ALWAYS */ +#endif /* ! mhd_CORK_RESET_PUSH_DATA_ALWAYS */ /** * Handle post-send setsockopt calls. @@ -587,48 +581,53 @@ post_send_setopt (struct MHD_Connection *connection, * Final piece is indicated by push_data == true. */ const bool buffer_data = (! push_data); - if (_MHD_YES == connection->is_nonip) + if (mhd_T_IS_YES(connection->is_nonip)) return; if (buffer_data) - return; /* Nothing to do after send(). */ + return; /* Nothing to do after the send(). */ -#ifndef MHD_USE_MSG_MORE +#ifndef mhd_USE_MSG_MORE (void) plain_send_next; /* Mute compiler warning */ -#endif /* ! MHD_USE_MSG_MORE */ +#endif /* ! mhd_USE_MSG_MORE */ /* Need to push data. */ -#ifdef MHD_TCP_CORK_NOPUSH -#ifdef _MHD_CORK_RESET_PUSH_DATA_ALWAYS -#ifdef _MHD_NODELAY_SET_PUSH_DATA_ALWAYS -#ifdef MHD_USE_MSG_MORE - if (_MHD_OFF == connection->sk_corked) - { - if (_MHD_ON == connection->sk_nodelay) - return; /* Data was already pushed by send(). */ - } - /* This is Linux kernel. There are options: +#ifdef mhd_TCP_CORK_NOPUSH + if (mhd_T_IS_YES(connection->sk_nodelay) && \ + mhd_T_IS_NO(connection->sk_corked)) + return; /* Data has been already pushed by last send(). */ + +# ifdef mhd_CORK_RESET_PUSH_DATA_ALWAYS +# ifdef mhd_NODELAY_SET_PUSH_DATA_ALWAYS +# ifdef mhd_USE_MSG_MORE + /* This is Linux kernel. + * The socket is corked (or unknown) or 'no delay' is not set (or unknown). + * There are options: * * Push the data by setting of TCP_NODELAY (without change * of the cork on the socket), * * Push the data by resetting of TCP_CORK. * The optimal choice depends on the next final send functions - * used on the same socket. If TCP_NODELAY wasn't set then push - * data by setting TCP_NODELAY (TCP_NODELAY will not be removed - * and is needed to push the data by send() without MSG_MORE). + * used on the same socket. + * + * In general on Linux kernel TCP_NODELAY always enabled is preferred, + * as buffering is controlled by MSG_MORE or cork/uncork. + * + * If next send function will not support MSG_MORE (like sendfile() + * or TLS-connection) than push data by setting TCP_NODELAY + * so the socket may remain corked (no additional sys-call before + * next send()). + * * If send()/sendmsg() will be used next than push data by - * resetting of TCP_CORK so next send without MSG_MORE will push - * data to the network (without additional sys-call to push data). - * If next final send function will not support MSG_MORE (like - * sendfile() or TLS-connection) than push data by setting - * TCP_NODELAY so socket will remain corked (no additional - * sys-call before next send()). */ - if ((_MHD_ON != connection->sk_nodelay) || + * resetting of TCP_CORK so next final send without MSG_MORE will push + * data to the network (without additional sys-call to push data). */ + + if (mhd_T_IS_NOT_YES(connection->sk_nodelay) || (! plain_send_next)) { - if (MHD_connection_set_nodelay_state_ (connection, true)) + if (mhd_connection_set_nodelay_state (connection, true)) return; /* Data has been pushed by TCP_NODELAY. */ /* Failed to set TCP_NODELAY for the socket. * Really unlikely to happen on TCP connections. */ - if (MHD_connection_set_cork_state_ (connection, false)) + if (mhd_connection_set_cork_state (connection, false)) return; /* Data has been pushed by uncorking the socket. */ /* Failed to uncork the socket. * Really unlikely to happen on TCP connections. */ @@ -637,53 +636,53 @@ post_send_setopt (struct MHD_Connection *connection, } else { - if (MHD_connection_set_cork_state_ (connection, false)) + if (mhd_connection_set_cork_state (connection, false)) return; /* Data has been pushed by uncorking the socket. */ /* Failed to uncork the socket. * Really unlikely to happen on TCP connections. */ - if (MHD_connection_set_nodelay_state_ (connection, true)) + if (mhd_connection_set_nodelay_state (connection, true)) return; /* Data has been pushed by TCP_NODELAY. */ /* Failed to set TCP_NODELAY for the socket. * Really unlikely to happen on TCP connections. */ /* The socket cannot be uncorked, no way to push data */ } -#else /* ! MHD_USE_MSG_MORE */ - /* Use setting of TCP_NODELAY here to avoid sys-call - * for corking the socket during sending of the next response. */ - if (MHD_connection_set_nodelay_state_ (connection, true)) +# else /* ! mhd_USE_MSG_MORE */ + /* Push data by setting TCP_NODELAY here as uncorking here + * would require corking the socket before sending the next response. */ + if (mhd_connection_set_nodelay_state (connection, true)) return; /* Data was pushed by TCP_NODELAY. */ /* Failed to set TCP_NODELAY for the socket. * Really unlikely to happen on TCP connections. */ - if (MHD_connection_set_cork_state_ (connection, false)) + if (mhd_connection_set_cork_state (connection, false)) return; /* Data was pushed by uncorking the socket. */ /* Failed to uncork the socket. * Really unlikely to happen on TCP connections. */ /* The socket remains corked, no way to push data */ -#endif /* ! MHD_USE_MSG_MORE */ -#else /* ! _MHD_NODELAY_SET_PUSH_DATA_ALWAYS */ - if (MHD_connection_set_cork_state_ (connection, false)) +# endif /* ! mhd_USE_MSG_MORE */ +# else /* ! mhd_NODELAY_SET_PUSH_DATA_ALWAYS */ + if (mhd_connection_set_cork_state (connection, false)) return; /* Data was pushed by uncorking the socket. */ /* Failed to uncork the socket. * Really unlikely to happen on TCP connections. */ - return; /* Socket remains corked, no way to push data */ -#endif /* ! _MHD_NODELAY_SET_PUSH_DATA_ALWAYS */ -#else /* ! _MHD_CORK_RESET_PUSH_DATA_ALWAYS */ - /* This is a typical *BSD or Darwin kernel. */ - if (_MHD_OFF == connection->sk_corked) + /* Socket remains corked, no way to push data */ +# endif /* ! mhd_NODELAY_SET_PUSH_DATA_ALWAYS */ +# else /* ! mhd_CORK_RESET_PUSH_DATA_ALWAYS */ + /* This is old FreeBSD or Darwin kernel. */ + + if (mhd_T_IS_NO(connection->sk_corked)) { - if (_MHD_ON == connection->sk_nodelay) - return; /* Data was already pushed by send(). */ + mhd_assert(mhd_T_IS_NOT_YES(connection->sk_nodelay)); /* Unlikely to reach this code. * TCP_NODELAY should be turned on before send(). */ - if (MHD_connection_set_nodelay_state_ (connection, true)) + if (mhd_connection_set_nodelay_state (connection, true)) { /* TCP_NODELAY has been set on uncorked socket. * Use zero-send to push the data. */ - if (zero_send_ (connection)) + if (zero_send (connection)) return; /* The data has been pushed by zero-send. */ } @@ -692,234 +691,173 @@ post_send_setopt (struct MHD_Connection *connection, } else { -#ifdef _MHD_CORK_RESET_PUSH_DATA - enum MHD_tristate old_cork_state = connection->sk_corked; -#endif /* _MHD_CORK_RESET_PUSH_DATA */ +#ifdef mhd_CORK_RESET_PUSH_DATA + enum mhd_Tristate old_cork_state = connection->sk_corked; +#endif /* mhd_CORK_RESET_PUSH_DATA */ /* The socket is corked or cork state is unknown. */ - if (MHD_connection_set_cork_state_ (connection, false)) + if (mhd_connection_set_cork_state (connection, false)) { -#ifdef _MHD_CORK_RESET_PUSH_DATA - /* FreeBSD kernel */ - if (_MHD_OFF == old_cork_state) +#ifdef mhd_CORK_RESET_PUSH_DATA + /* Modern FreeBSD or OpenBSD kernel */ + if (mhd_T_IS_YES(old_cork_state)) return; /* Data has been pushed by uncorking the socket. */ -#endif /* _MHD_CORK_RESET_PUSH_DATA */ +#endif /* mhd_CORK_RESET_PUSH_DATA */ /* Unlikely to reach this code. * The data should be pushed by uncorking (FreeBSD) or * the socket should be uncorked before send(). */ - if ((_MHD_ON == connection->sk_nodelay) || - (MHD_connection_set_nodelay_state_ (connection, true))) + if (mhd_T_IS_YES(connection->sk_nodelay) || + (mhd_connection_set_nodelay_state (connection, true))) { /* TCP_NODELAY is turned ON on uncorked socket. * Use zero-send to push the data. */ - if (zero_send_ (connection)) + if (zero_send (connection)) return; /* The data has been pushed by zero-send. */ } } - /* The socket remains corked. Data cannot be pushed. */ + /* Data cannot be pushed. */ } -#endif /* ! _MHD_CORK_RESET_PUSH_DATA_ALWAYS */ -#else /* ! MHD_TCP_CORK_NOPUSH */ +#endif /* ! mhd_CORK_RESET_PUSH_DATA_ALWAYS */ +#else /* ! mhd_TCP_CORK_NOPUSH */ /* Corking is not supported. Buffering is controlled * by TCP_NODELAY only. */ - mhd_assert (_MHD_ON != connection->sk_corked); - if (_MHD_ON == connection->sk_nodelay) + mhd_assert (mhd_T_IS_NOT_YES(connection->sk_corked)); + if (mhd_T_IS_YES(connection->sk_nodelay)) return; /* Data was already pushed by send(). */ /* Unlikely to reach this code. * TCP_NODELAY should be turned on before send(). */ - if (MHD_connection_set_nodelay_state_ (connection, true)) + if (mhd_connection_set_nodelay_state (connection, true)) { /* TCP_NODELAY has been set. - * Use zero-send to push the data. */ - if (zero_send_ (connection)) + * Use zero-send to try to push the data. */ + if (zero_send (connection)) return; /* The data has been pushed by zero-send. */ } /* Failed to push the data. */ -#endif /* ! MHD_TCP_CORK_NOPUSH */ -#ifdef HAVE_MESSAGES - MHD_DLOG (connection->daemon, - _ ("Failed to push the data from buffers to the network. " - "Client may experience some delay " - "(usually in range 200ms - 5 sec).\n")); -#endif /* HAVE_MESSAGES */ +#endif /* ! mhd_TCP_CORK_NOPUSH */ + MHD_LOG_MSG (connection->daemon, MHD_SC_SOCKET_FLUSH_LAST_PART_FAILED, \ + "Failed to force flush the last part of the response header " \ + "or the response content that might have been buffered by " \ + "the kernel. The client may experience some delay (usually " \ + "in range 200ms - 5 sec)."); return; } - -ssize_t -MHD_send_data_ (struct MHD_Connection *connection, - const char *buffer, - size_t buffer_size, - bool push_data) +static MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_ (3,2) +MHD_FN_PAR_OUT_ (5) enum mhd_SocketError +mhd_plain_send (struct MHD_Connection *restrict c, + size_t buf_size, + const char buf[MHD_FN_PAR_DYN_ARR_SIZE_(buf_size)], + bool push_data, + size_t *restrict sent) { - MHD_socket s = connection->socket_fd; - ssize_t ret; -#ifdef HTTPS_SUPPORT - const bool tls_conn = (connection->daemon->options & MHD_USE_TLS); -#else /* ! HTTPS_SUPPORT */ - const bool tls_conn = false; -#endif /* ! HTTPS_SUPPORT */ + /* plaintext transmission */ + ssize_t res; + bool full_buf_sent; - if ( (MHD_INVALID_SOCKET == s) || - (MHD_CONNECTION_CLOSED == connection->state) ) + if (buf_size > MHD_SCKT_SEND_MAX_SIZE_) { - return MHD_ERR_NOTCONN_; - } - - if (buffer_size > SSIZE_MAX) - { - buffer_size = SSIZE_MAX; /* Max return value */ + buf_size = MHD_SCKT_SEND_MAX_SIZE_; /* send() return value limit */ push_data = false; /* Incomplete send */ } + pre_send_setopt (c, true, push_data); +#ifdef mhd_USE_MSG_MORE + res = mhd_sys_send4 (s, + buffer, + buffer_size, + push_data ? 0 : MSG_MORE); +#else + res = mhd_sys_send4 (c, + buf, + buf_size, + 0); +#endif + + if (0 >= res) + { + enum mhd_SocketError err; + + err = mhd_socket_error_get_from_sys_err(mhd_SCKT_GET_LERR ()); + + if (mhd_SOCKET_ERR_AGAIN == err) + c->sk_ready = (enum mhd_SocketNetState) /* Clear 'send-ready' */ + (((unsigned int) c->sk_ready) + & (~mhd_SOCKET_NET_STATE_SEND_READY)); + + return err; + } + *sent = (size_t) res; + + full_buf_sent = (buf_size == (size_t) res); + + if (! full_buf_sent) + c->sk_ready = (enum mhd_SocketNetState) /* Clear 'send-ready' */ + (((unsigned int) c->sk_ready) + & (~mhd_SOCKET_NET_STATE_SEND_READY)); + + /* If there is a need to push the data from network buffers + * call post_send_setopt(). */ + /* It's unknown whether sendfile() (or other send function without + * MSG_MORE support) will be used for the next reply so assume + * that next sending will be the same, like this call. */ + if ( push_data && full_buf_sent) + post_send_setopt (c, false, push_data); + + return mhd_SOCKET_ERR_NO_ERROR; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_ (3,2) +MHD_FN_PAR_OUT_ (5) enum mhd_SocketError +mhd_send_data (struct MHD_Connection *restrict connection, + size_t buf_size, + const char buf[MHD_FN_PAR_DYN_ARR_SIZE_(buf_size)], + bool push_data, + size_t *restrict sent) +{ + MHD_Socket s = connection->socket_fd; + + enum mhd_SocketError ret; + const bool tls_conn = false; // TODO: TLS support + + mhd_assert (MHD_INVALID_SOCKET != s); + mhd_assert (MHD_CONNECTION_CLOSED != connection->state); + if (tls_conn) { #ifdef HTTPS_SUPPORT pre_send_setopt (connection, (! tls_conn), push_data); - ret = gnutls_record_send (connection->tls_session, - buffer, - buffer_size); - if (GNUTLS_E_AGAIN == ret) - { -#ifdef EPOLL_SUPPORT - connection->epoll_state &= - ~((enum MHD_EpollState) MHD_EPOLL_STATE_WRITE_READY); -#endif - return MHD_ERR_AGAIN_; - } - if (GNUTLS_E_INTERRUPTED == ret) - return MHD_ERR_AGAIN_; - if ( (GNUTLS_E_ENCRYPTION_FAILED == ret) || - (GNUTLS_E_INVALID_SESSION == ret) || - (GNUTLS_E_COMPRESSION_FAILED == ret) || - (GNUTLS_E_EXPIRED == ret) || - (GNUTLS_E_HASH_FAILED == ret) ) - return MHD_ERR_TLS_; - if ( (GNUTLS_E_PUSH_ERROR == ret) || - (GNUTLS_E_INTERNAL_ERROR == ret) || - (GNUTLS_E_CRYPTODEV_IOCTL_ERROR == ret) || - (GNUTLS_E_CRYPTODEV_DEVICE_ERROR == ret) ) - return MHD_ERR_PIPE_; -#if defined(GNUTLS_E_PREMATURE_TERMINATION) - if (GNUTLS_E_PREMATURE_TERMINATION == ret) - return MHD_ERR_CONNRESET_; -#elif defined(GNUTLS_E_UNEXPECTED_PACKET_LENGTH) - if (GNUTLS_E_UNEXPECTED_PACKET_LENGTH == ret) - return MHD_ERR_CONNRESET_; -#endif /* GNUTLS_E_UNEXPECTED_PACKET_LENGTH */ - if (GNUTLS_E_MEMORY_ERROR == ret) - return MHD_ERR_NOMEM_; - if (ret < 0) - { - /* Treat any other error as hard error. */ - return MHD_ERR_NOTCONN_; - } -#ifdef EPOLL_SUPPORT - /* Unlike non-TLS connections, do not reset "write-ready" if - * sent amount smaller than provided amount, as TLS - * connections may break data into smaller parts for sending. */ -#endif /* EPOLL_SUPPORT */ #else /* ! HTTPS_SUPPORT */ ret = MHD_ERR_NOTCONN_; #endif /* ! HTTPS_SUPPORT */ - } - else - { - /* plaintext transmission */ - if (buffer_size > MHD_SCKT_SEND_MAX_SIZE_) - { - buffer_size = MHD_SCKT_SEND_MAX_SIZE_; /* send() return value limit */ - push_data = false; /* Incomplete send */ - } - - pre_send_setopt (connection, (! tls_conn), push_data); -#ifdef MHD_USE_MSG_MORE - ret = MHD_send4_ (s, - buffer, - buffer_size, - push_data ? 0 : MSG_MORE); -#else - ret = MHD_send4_ (s, - buffer, - buffer_size, - 0); -#endif - - if (0 > ret) - { - const int err = MHD_socket_get_error_ (); - - if (MHD_SCKT_ERR_IS_EAGAIN_ (err)) - { -#ifdef EPOLL_SUPPORT - /* EAGAIN, no longer write-ready */ - connection->epoll_state &= - ~((enum MHD_EpollState) MHD_EPOLL_STATE_WRITE_READY); -#endif /* EPOLL_SUPPORT */ - return MHD_ERR_AGAIN_; - } - if (MHD_SCKT_ERR_IS_EINTR_ (err)) - return MHD_ERR_AGAIN_; - if (MHD_SCKT_ERR_IS_REMOTE_DISCNN_ (err)) - return MHD_ERR_CONNRESET_; - if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_EPIPE_)) - return MHD_ERR_PIPE_; - if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_EOPNOTSUPP_)) - return MHD_ERR_OPNOTSUPP_; - if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_ENOTCONN_)) - return MHD_ERR_NOTCONN_; - if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_EINVAL_)) - return MHD_ERR_INVAL_; - if (MHD_SCKT_ERR_IS_LOW_RESOURCES_ (err)) - return MHD_ERR_NOMEM_; - if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_EBADF_)) - return MHD_ERR_BADF_; - /* Treat any other error as a hard error. */ - return MHD_ERR_NOTCONN_; - } -#ifdef EPOLL_SUPPORT - else if (buffer_size > (size_t) ret) - connection->epoll_state &= - ~((enum MHD_EpollState) MHD_EPOLL_STATE_WRITE_READY); -#endif /* EPOLL_SUPPORT */ + mhd_assert (0 && "Not implemented yet"); + return mhd_SOCKET_ERR_OTHER; } - /* If there is a need to push the data from network buffers - * call post_send_setopt(). */ - /* If TLS connection is used then next final send() will be - * without MSG_MORE support. If non-TLS connection is used - * it's unknown whether sendfile() will be used or not so - * assume that next call will be the same, like this call. */ - if ( (push_data) && - (buffer_size == (size_t) ret) ) - post_send_setopt (connection, (! tls_conn), push_data); - - return ret; + return mhd_plain_send(connection, buf_size, buf, push_data, sent); } - -ssize_t -MHD_send_hdr_and_body_ (struct MHD_Connection *connection, - const char *header, - size_t header_size, - bool never_push_hdr, - const char *body, - size_t body_size, - bool complete_response) +MHD_INTERNAL MHD_FN_PAR_NONNULL_(1) MHD_FN_PAR_NONNULL_(3) +MHD_FN_PAR_IN_SIZE_ (3,2) MHD_FN_PAR_IN_SIZE_ (6,5) enum mhd_SocketError +mhd_send_hdr_and_body (struct MHD_Connection *restrict connection, + size_t header_size, + const char *restrict header, + bool never_push_hdr, + size_t body_size, + const char *restrict body, + bool complete_response, + size_t *restrict sent) { - ssize_t ret; + mhd_iov_ret_type res; + bool send_error; bool push_hdr; bool push_body; - MHD_socket s = connection->socket_fd; -#ifndef _WIN32 -#define _MHD_SEND_VEC_MAX MHD_SCKT_SEND_MAX_SIZE_ -#else /* ! _WIN32 */ -#define _MHD_SEND_VEC_MAX UINT32_MAX -#endif /* ! _WIN32 */ -#ifdef MHD_VECT_SEND + MHD_Socket s = connection->socket_fd; +#ifdef mhd_USE_VECT_SEND #if defined(HAVE_SENDMSG) || defined(HAVE_WRITEV) struct iovec vector[2]; #ifdef HAVE_SENDMSG @@ -934,25 +872,22 @@ MHD_send_hdr_and_body_ (struct MHD_Connection *connection, no_vec = false; #ifdef HTTPS_SUPPORT - no_vec = no_vec || (connection->daemon->options & MHD_USE_TLS); + no_vec = no_vec || (false); // TODO: TLS support #endif /* HTTPS_SUPPORT */ #if (! defined(HAVE_SENDMSG) || ! defined(MSG_NOSIGNAL) ) && \ - defined(MHD_SEND_SPIPE_SEND_SUPPRESS_POSSIBLE) && \ - defined(MHD_SEND_SPIPE_SUPPRESS_NEEDED) + defined(mhd_SEND_SPIPE_SUPPRESS_POSSIBLE) && \ + defined(mhd_SEND_SPIPE_SUPPRESS_NEEDED) no_vec = no_vec || (! connection->daemon->sigpipe_blocked && ! connection->sk_spipe_suppress); #endif /* (!HAVE_SENDMSG || ! MSG_NOSIGNAL) && - MHD_SEND_SPIPE_SEND_SUPPRESS_POSSIBLE && - MHD_SEND_SPIPE_SUPPRESS_NEEDED */ -#endif /* MHD_VECT_SEND */ + mhd_SEND_SPIPE_SUPPRESS_POSSIBLE && + mhd_SEND_SPIPE_SUPPRESS_NEEDED */ +#endif /* mhd_USE_VECT_SEND */ mhd_assert ( (NULL != body) || (0 == body_size) ); - if ( (MHD_INVALID_SOCKET == s) || - (MHD_CONNECTION_CLOSED == connection->state) ) - { - return MHD_ERR_NOTCONN_; - } + mhd_assert (MHD_INVALID_SOCKET != s); + mhd_assert (MHD_CONNECTION_CLOSED != connection->state); push_body = complete_response; @@ -965,7 +900,7 @@ MHD_send_hdr_and_body_ (struct MHD_Connection *connection, else { if (1400 > (header_size + body_size)) - push_hdr = false; /* Do not push the header as complete + push_hdr = false; /* Do not push the header as complete * reply is already ready and the whole * reply most probably will fit into * the single IP packet. */ @@ -980,76 +915,70 @@ MHD_send_hdr_and_body_ (struct MHD_Connection *connection, if (complete_response && (0 == body_size)) push_hdr = true; /* The header alone is equal to the whole response. */ - if ( -#ifdef MHD_VECT_SEND - (no_vec) || - (0 == body_size) || - ((size_t) SSIZE_MAX <= header_size) || - ((size_t) _MHD_SEND_VEC_MAX < header_size) -#ifdef _WIN32 - || ((size_t) UINT_MAX < header_size) -#endif /* _WIN32 */ -#else /* ! MHD_VECT_SEND */ - true -#endif /* ! MHD_VECT_SEND */ - ) - { - ret = MHD_send_data_ (connection, - header, - header_size, - push_hdr); +#ifndef mhd_USE_VECT_SEND + no_vec = (no_vec || true); +#else /* mhd_USE_VECT_SEND */ + no_vec = (no_vec || (0 == body_size)); + no_vec = (no_vec || ((sizeof(mhd_iov_elmn_size) <= sizeof(size_t)) && + (((size_t) mhd_IOV_ELMN_MAX_SIZE) < header_size))); +#endif /* mhd_USE_VECT_SEND */ - if ( (header_size == (size_t) ret) && - ((size_t) SSIZE_MAX > header_size) && - (0 != body_size) && - (connection->sk_nonblck) ) + + + if (no_vec) + { + enum mhd_SocketError ret; + ret = mhd_send_data (connection, + header, + header_size, + push_hdr, + sent); + + // TODO: check 'send-ready' + if ((mhd_SOCKET_ERR_NO_ERROR == ret) && + (header_size == *sent) && + (0 != body_size) && + (header_size < header_size + body_size) && + (connection->sk_nonblck)) { - ssize_t ret2; + size_t sent_b; /* The header has been sent completely. * Try to send the reply body without waiting for * the next round. */ - /* Make sure that sum of ret + ret2 will not exceed SSIZE_MAX as - * function needs to return positive value if succeed. */ - if ( (((size_t) SSIZE_MAX) - ((size_t) ret)) < body_size) - { - body_size = (((size_t) SSIZE_MAX) - ((size_t) ret)); - complete_response = false; - push_body = complete_response; - } - ret2 = MHD_send_data_ (connection, - body, - body_size, - push_body); - if (0 < ret2) - return ret + ret2; /* Total data sent */ - if (MHD_ERR_AGAIN_ == ret2) - return ret; + ret = mhd_send_data (connection, + body, + body_size, + push_body, + &sent_b); - return ret2; /* Error code */ + if (mhd_SOCKET_ERR_NO_ERROR == ret) + *sent += sent_b; + else if (mhd_SOCKET_ERR_IS_HARD(ret)) + return ret; /* Unrecoverable error */ + + return mhd_SOCKET_ERR_NO_ERROR; /* The header has been sent successfully */ } return ret; } -#ifdef MHD_VECT_SEND +#ifdef mhd_USE_VECT_SEND - if ( ((size_t) SSIZE_MAX <= body_size) || - ((size_t) SSIZE_MAX < (header_size + body_size)) ) + if (header_size > (header_size + body_size)) { /* Return value limit */ - body_size = SSIZE_MAX - header_size; + body_size = SIZE_MAX - header_size; complete_response = false; push_body = complete_response; } -#if (SSIZE_MAX != _MHD_SEND_VEC_MAX) || (_MHD_SEND_VEC_MAX + 0 == 0) - if (((size_t) _MHD_SEND_VEC_MAX <= body_size) || - ((size_t) _MHD_SEND_VEC_MAX < (header_size + body_size))) + if (((mhd_iov_ret_type) (header_size + body_size)) < 0 || + ((size_t)(mhd_iov_ret_type) (header_size + body_size)) != + (header_size + body_size)) { - /* Send total amount limit */ - body_size = _MHD_SEND_VEC_MAX - header_size; + /* Send sys-call total amount limit */ + body_size = mhd_IOV_RET_MAX_SIZE - header_size; complete_response = false; push_body = complete_response; } -#endif /* SSIZE_MAX != _MHD_SEND_VEC_MAX */ pre_send_setopt (connection, #ifdef HAVE_SENDMSG @@ -1058,84 +987,75 @@ MHD_send_hdr_and_body_ (struct MHD_Connection *connection, false, #endif /* ! HAVE_SENDMSG */ push_hdr || push_body); + send_error = false; #if defined(HAVE_SENDMSG) || defined(HAVE_WRITEV) - vector[0].iov_base = _MHD_DROP_CONST (header); + vector[0].iov_base = mhd_DROP_CONST (header); vector[0].iov_len = header_size; - vector[1].iov_base = _MHD_DROP_CONST (body); + vector[1].iov_base = mhd_DROP_CONST (body); vector[1].iov_len = body_size; #if defined(HAVE_SENDMSG) memset (&msg, 0, sizeof(msg)); + msg.msg_name = NULL; + msg.msg_namelen = 0; msg.msg_iov = vector; msg.msg_iovlen = 2; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; - ret = sendmsg (s, &msg, MSG_NOSIGNAL_OR_ZERO); + res = sendmsg (s, &msg, mhd_MSG_NOSIGNAL | + ((push_hdr || push_body) ? 0 : mhd_MSG_MORE)); #elif defined(HAVE_WRITEV) - ret = writev (s, vector, 2); + res = writev (s, vector, 2); #endif /* HAVE_WRITEV */ + if (0 < res) + *sent = (size_t) res; + else + send_error = true; #endif /* HAVE_SENDMSG || HAVE_WRITEV */ #ifdef _WIN32 - if ((size_t) UINT_MAX < body_size) + if (((mhd_iov_elmn_size) body_size) != body_size) { /* Send item size limit */ - body_size = UINT_MAX; + body_size = mhd_IOV_ELMN_MAX_SIZE; complete_response = false; push_body = complete_response; } - vector[0].buf = (char *) _MHD_DROP_CONST (header); + vector[0].buf = (char *) mhd_DROP_CONST (header); vector[0].len = (unsigned long) header_size; - vector[1].buf = (char *) _MHD_DROP_CONST (body); + vector[1].buf = (char *) mhd_DROP_CONST (body); vector[1].len = (unsigned long) body_size; - ret = WSASend (s, vector, 2, &vec_sent, 0, NULL, NULL); - if (0 == ret) - ret = (ssize_t) vec_sent; + res = WSASend (s, vector, 2, &vec_sent, 0, NULL, NULL); + if (0 == res) + *sent = (size_t) vec_sent; else - ret = -1; + send_error = true; #endif /* _WIN32 */ - if (0 > ret) + if (send_error) { - const int err = MHD_socket_get_error_ (); + enum mhd_SocketError err; - if (MHD_SCKT_ERR_IS_EAGAIN_ (err)) - { -#ifdef EPOLL_SUPPORT - /* EAGAIN, no longer write-ready */ - connection->epoll_state &= - ~((enum MHD_EpollState) MHD_EPOLL_STATE_WRITE_READY); -#endif /* EPOLL_SUPPORT */ - return MHD_ERR_AGAIN_; - } - if (MHD_SCKT_ERR_IS_EINTR_ (err)) - return MHD_ERR_AGAIN_; - if (MHD_SCKT_ERR_IS_REMOTE_DISCNN_ (err)) - return MHD_ERR_CONNRESET_; - if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_EPIPE_)) - return MHD_ERR_PIPE_; - if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_EOPNOTSUPP_)) - return MHD_ERR_OPNOTSUPP_; - if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_ENOTCONN_)) - return MHD_ERR_NOTCONN_; - if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_EINVAL_)) - return MHD_ERR_INVAL_; - if (MHD_SCKT_ERR_IS_LOW_RESOURCES_ (err)) - return MHD_ERR_NOMEM_; - if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_EBADF_)) - return MHD_ERR_BADF_; - /* Treat any other error as a hard error. */ - return MHD_ERR_NOTCONN_; + err = mhd_socket_error_get_from_sys_err(mhd_SCKT_GET_LERR ()); + + if (mhd_SOCKET_ERR_AGAIN == err) + connection->sk_ready = (enum mhd_SocketNetState) /* Clear 'send-ready' */ + (((unsigned int) connection->sk_ready) + & (~mhd_SOCKET_NET_STATE_SEND_READY)); + + return err; } -#ifdef EPOLL_SUPPORT - else if ((header_size + body_size) > (size_t) ret) - connection->epoll_state &= - ~((enum MHD_EpollState) MHD_EPOLL_STATE_WRITE_READY); -#endif /* EPOLL_SUPPORT */ + if ((header_size + body_size) > *sent) + connection->sk_ready = (enum mhd_SocketNetState) /* Clear 'send-ready' */ + (((unsigned int) connection->sk_ready) + & (~mhd_SOCKET_NET_STATE_SEND_READY)); /* If there is a need to push the data from network buffers * call post_send_setopt(). */ if ( (push_body) && - ((header_size + body_size) == (size_t) ret) ) + ((header_size + body_size) == *sent) ) { /* Complete reply has been sent. */ /* If TLS connection is used then next final send() will be @@ -1145,39 +1065,37 @@ MHD_send_hdr_and_body_ (struct MHD_Connection *connection, * the same, like for this response. */ post_send_setopt (connection, #ifdef HAVE_SENDMSG - true, + true, /* Assume the same type of the send function */ #else /* ! HAVE_SENDMSG */ - false, + false, /* Assume the same type of the send function */ #endif /* ! HAVE_SENDMSG */ true); } else if ( (push_hdr) && - (header_size <= (size_t) ret)) + (header_size <= *sent)) { /* The header has been sent completely and there is a * need to push the header data. */ /* Luckily the type of send function will be used next is known. */ post_send_setopt (connection, -#if defined(_MHD_HAVE_SENDFILE) - MHD_resp_sender_std == connection->rp.resp_sender, -#else /* ! _MHD_HAVE_SENDFILE */ true, -#endif /* ! _MHD_HAVE_SENDFILE */ true); } - return ret; -#else /* ! MHD_VECT_SEND */ - mhd_assert (false); - return MHD_ERR_CONNRESET_; /* Unreachable. Mute warnings. */ -#endif /* ! MHD_VECT_SEND */ + return mhd_SOCKET_ERR_NO_ERROR; +#else /* ! mhd_USE_VECT_SEND */ + mhd_assert (0 && "Should be unreachable"); + return mhd_SOCKET_ERR_INTERNAL; /* Unreachable. Mute warnings. */ +#endif /* ! mhd_USE_VECT_SEND */ } -#if defined(_MHD_HAVE_SENDFILE) -ssize_t -MHD_send_sendfile_ (struct MHD_Connection *connection) +#if defined(MHD_USE_SENDFILE) // TODO: adapt, update +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_ (2) enum mhd_SocketError +mhd_send_sendfile (struct MHD_Connection *restrict connection, + size_t *restrict sent) { +#ifdef 0 ssize_t ret; const int file_fd = connection->rp.response->fd; uint64_t left; @@ -1203,23 +1121,23 @@ MHD_send_sendfile_ (struct MHD_Connection *connection) #endif /* HAVE_DARWIN_SENDFILE */ const bool used_thr_p_c = MHD_D_IS_USING_THREAD_PER_CONN_ (connection->daemon); - const size_t chunk_size = used_thr_p_c ? MHD_SENFILE_CHUNK_THR_P_C_ : - MHD_SENFILE_CHUNK_; + const size_t chunk_size = used_thr_p_c ? mhd_SENFILE_CHUNK_SIZE_FOR_THR_P_C : + mhd_SENFILE_CHUNK_SIZE; size_t send_size = 0; bool push_data; - mhd_assert (MHD_resp_sender_sendfile == connection->rp.resp_sender); + mhd_assert (mhd_resp_sender_sendfile == connection->rp.resp_sender); mhd_assert (0 == (connection->daemon->options & MHD_USE_TLS)); - offsetu64 = connection->rp.rsp_write_position + offsetu64 = connection->rp.rsp_cntn_read_pos + connection->rp.response->fd_off; if (max_off_t < offsetu64) { /* Retry to send with standard 'send()'. */ - connection->rp.resp_sender = MHD_resp_sender_std; + connection->rp.resp_sender = mhd_resp_sender_std; return MHD_ERR_AGAIN_; } left = connection->rp.response->total_size - - connection->rp.rsp_write_position; + - connection->rp.rsp_cntn_read_pos; if ( (uint64_t) SSIZE_MAX < left) left = SSIZE_MAX; @@ -1254,7 +1172,7 @@ MHD_send_sendfile_ (struct MHD_Connection *connection) #endif /* HAVE_SENDFILE64 */ if (0 > ret) { - const int err = MHD_socket_get_error_ (); + const int err = mhd_socket_get_error (); if (MHD_SCKT_ERR_IS_EAGAIN_ (err)) { #ifdef EPOLL_SUPPORT @@ -1275,14 +1193,14 @@ MHD_send_sendfile_ (struct MHD_Connection *connection) to fall back to 'SEND'; see also this thread for info on odd libc/Linux behavior with sendfile: http://lists.gnu.org/archive/html/libmicrohttpd/2011-02/msg00015.html */ - connection->rp.resp_sender = MHD_resp_sender_std; + connection->rp.resp_sender = mhd_resp_sender_std; return MHD_ERR_AGAIN_; #else /* HAVE_SOLARIS_SENDFILE */ if ( (EAFNOSUPPORT == err) || (EINVAL == err) || (EOPNOTSUPP == err) ) { /* Retry with standard file reader. */ - connection->rp.resp_sender = MHD_resp_sender_std; + connection->rp.resp_sender = mhd_resp_sender_std; return MHD_ERR_AGAIN_; } if ( (ENOTCONN == err) || @@ -1311,7 +1229,7 @@ MHD_send_sendfile_ (struct MHD_Connection *connection) &sent_bytes, flags)) { - const int err = MHD_socket_get_error_ (); + const int err = mhd_socket_get_error (); if (MHD_SCKT_ERR_IS_EAGAIN_ (err) || MHD_SCKT_ERR_IS_EINTR_ (err) || (EBUSY == err) ) @@ -1324,7 +1242,7 @@ MHD_send_sendfile_ (struct MHD_Connection *connection) } /* Some unrecoverable error. Possibly file FD is not suitable * for sendfile(). Retry with standard send(). */ - connection->rp.resp_sender = MHD_resp_sender_std; + connection->rp.resp_sender = mhd_resp_sender_std; return MHD_ERR_AGAIN_; } mhd_assert (0 < sent_bytes); @@ -1339,7 +1257,7 @@ MHD_send_sendfile_ (struct MHD_Connection *connection) NULL, 0)) { - const int err = MHD_socket_get_error_ (); + const int err = mhd_socket_get_error (); if (MHD_SCKT_ERR_IS_EAGAIN_ (err) || MHD_SCKT_ERR_IS_EINTR_ (err)) { @@ -1358,7 +1276,7 @@ MHD_send_sendfile_ (struct MHD_Connection *connection) (EOPNOTSUPP == err) ) { /* This file FD is not suitable for sendfile(). * Retry with standard send(). */ - connection->rp.resp_sender = MHD_resp_sender_std; + connection->rp.resp_sender = mhd_resp_sender_std; return MHD_ERR_AGAIN_; } return MHD_ERR_BADF_; /* Return hard error. */ @@ -1378,12 +1296,17 @@ MHD_send_sendfile_ (struct MHD_Connection *connection) post_send_setopt (connection, false, push_data); return ret; +#else + (void) connection; (void) sent; + mhd_assert (0 && "Not implemented yet"); + return mhd_SOCKET_ERR_INTERNAL; +#endif } -#endif /* _MHD_HAVE_SENDFILE */ +#endif /* MHD_USE_SENDFILE */ -#if defined(MHD_VECT_SEND) +#if defined(mhd_USE_VECT_SEND) /** @@ -1397,14 +1320,18 @@ MHD_send_sendfile_ (struct MHD_Connection *connection) * system buffers (usually set for the last piece of data), * set to false to prefer holding incomplete network packets * (more data will be send for the same reply). - * @return actual number of bytes sent + * @param[out] sent the pointer to get amount of actually sent bytes + * @return mhd_SOCKET_ERR_NO_ERROR if send succeed (the @a sent gets + * the sent size) or socket error */ -static ssize_t -send_iov_nontls (struct MHD_Connection *connection, - struct MHD_iovec_track_ *const r_iov, - bool push_data) +static MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_ (4) enum mhd_SocketError +send_iov_nontls (struct MHD_Connection *restrict connection, + struct mhd_iovec_track *const restrict r_iov, + bool push_data, + size_t *restrict sent) { ssize_t res; + bool send_error; size_t items_to_send; #ifdef HAVE_SENDMSG struct msghdr msg; @@ -1413,46 +1340,53 @@ send_iov_nontls (struct MHD_Connection *connection, DWORD cnt_w; #endif /* MHD_WINSOCK_SOCKETS */ - mhd_assert (0 == (connection->daemon->options & MHD_USE_TLS)); + // TODO: assert for non-TLS - if ( (MHD_INVALID_SOCKET == connection->socket_fd) || - (MHD_CONNECTION_CLOSED == connection->state) ) - { - return MHD_ERR_NOTCONN_; - } + mhd_assert (MHD_INVALID_SOCKET != connection->socket_fd); + mhd_assert (MHD_CONNECTION_CLOSED != connection->state); + send_error = false; items_to_send = r_iov->cnt - r_iov->sent; -#ifdef _MHD_IOV_MAX - if (_MHD_IOV_MAX < items_to_send) +#ifdef mhd_IOV_MAX + if (mhd_IOV_MAX < items_to_send) { - mhd_assert (0 < _MHD_IOV_MAX); - if (0 == _MHD_IOV_MAX) - return MHD_ERR_NOTCONN_; /* Should never happen */ - items_to_send = _MHD_IOV_MAX; + mhd_assert (0 < mhd_IOV_MAX); + if (0 == mhd_IOV_MAX) + return mhd_SOCKET_ERR_INTERNAL; /* Should never happen */ + items_to_send = mhd_IOV_MAX; push_data = false; /* Incomplete response */ } -#endif /* _MHD_IOV_MAX */ +#endif /* mhd_IOV_MAX */ #ifdef HAVE_SENDMSG memset (&msg, 0, sizeof(struct msghdr)); + msg.msg_name = NULL; + msg.msg_namelen = 0; msg.msg_iov = r_iov->iov + r_iov->sent; msg.msg_iovlen = items_to_send; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; pre_send_setopt (connection, true, push_data); -#ifdef MHD_USE_MSG_MORE res = sendmsg (connection->socket_fd, &msg, - MSG_NOSIGNAL_OR_ZERO | (push_data ? 0 : MSG_MORE)); -#else /* ! MHD_USE_MSG_MORE */ - res = sendmsg (connection->socket_fd, &msg, MSG_NOSIGNAL_OR_ZERO); -#endif /* ! MHD_USE_MSG_MORE */ + MSG_NOSIGNAL_OR_ZERO | (push_data ? 0 : mhd_MSG_MORE)); + if (0 < res) + *sent = (size_t) res; + else + send_error = true; #elif defined(HAVE_WRITEV) - pre_send_setopt (connection, true, push_data); + pre_send_setopt (connection, false, push_data); res = writev (connection->socket_fd, r_iov->iov + r_iov->sent, items_to_send); + if (0 < res) + *sent = (size_t) res; + else + send_error = true; #elif defined(MHD_WINSOCK_SOCKETS) #ifdef _WIN64 - if (items_to_send > UINT32_MAX) + if (items_to_send > ULONG_MAX) { - cnt_w = UINT32_MAX; + cnt_w = ULONG_MAX; push_data = false; /* Incomplete response */ } else @@ -1465,50 +1399,31 @@ send_iov_nontls (struct MHD_Connection *connection, (LPWSABUF) (r_iov->iov + r_iov->sent), cnt_w, &bytes_sent, 0, NULL, NULL)) - res = (ssize_t) bytes_sent; + *sent = (size_t) bytes_sent; else - res = -1; + send_error = true; #else /* !HAVE_SENDMSG && !HAVE_WRITEV && !MHD_WINSOCK_SOCKETS */ #error No vector-send function available #endif - if (0 > res) + if (send_error) { - const int err = MHD_socket_get_error_ (); + enum mhd_SocketError err; - if (MHD_SCKT_ERR_IS_EAGAIN_ (err)) - { -#ifdef EPOLL_SUPPORT - /* EAGAIN --- no longer write-ready */ - connection->epoll_state &= - ~((enum MHD_EpollState) MHD_EPOLL_STATE_WRITE_READY); -#endif /* EPOLL_SUPPORT */ - return MHD_ERR_AGAIN_; - } - if (MHD_SCKT_ERR_IS_EINTR_ (err)) - return MHD_ERR_AGAIN_; - if (MHD_SCKT_ERR_IS_REMOTE_DISCNN_ (err)) - return MHD_ERR_CONNRESET_; - if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_EPIPE_)) - return MHD_ERR_PIPE_; - if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_EOPNOTSUPP_)) - return MHD_ERR_OPNOTSUPP_; - if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_ENOTCONN_)) - return MHD_ERR_NOTCONN_; - if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_EINVAL_)) - return MHD_ERR_INVAL_; - if (MHD_SCKT_ERR_IS_LOW_RESOURCES_ (err)) - return MHD_ERR_NOMEM_; - if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_EBADF_)) - return MHD_ERR_BADF_; - /* Treat any other error as a hard error. */ - return MHD_ERR_NOTCONN_; + err = mhd_socket_error_get_from_sys_err(mhd_SCKT_GET_LERR ()); + + if (mhd_SOCKET_ERR_AGAIN == err) + connection->sk_ready = (enum mhd_SocketNetState) /* Clear 'send-ready' */ + (((unsigned int) connection->sk_ready) + & (~mhd_SOCKET_NET_STATE_SEND_READY)); + + return err; } /* Some data has been sent */ if (1) { - size_t track_sent = (size_t) res; + size_t track_sent = (size_t) *sent; /* Adjust the internal tracking information for the iovec to * take this last send into account. */ while ((0 != track_sent) && (r_iov->iov[r_iov->sent].iov_len <= track_sent)) @@ -1522,29 +1437,28 @@ send_iov_nontls (struct MHD_Connection *connection, post_send_setopt (connection, true, push_data); else { -#ifdef EPOLL_SUPPORT - connection->epoll_state &= - ~((enum MHD_EpollState) MHD_EPOLL_STATE_WRITE_READY); -#endif /* EPOLL_SUPPORT */ + connection->sk_ready = (enum mhd_SocketNetState) /* Clear 'send-ready' */ + (((unsigned int) connection->sk_ready) + & (~mhd_SOCKET_NET_STATE_SEND_READY)); if (0 != track_sent) { mhd_assert (r_iov->cnt > r_iov->sent); /* The last iov element has been partially sent */ r_iov->iov[r_iov->sent].iov_base = (void *) ((uint8_t *) r_iov->iov[r_iov->sent].iov_base + track_sent); - r_iov->iov[r_iov->sent].iov_len -= (MHD_iov_size_) track_sent; + r_iov->iov[r_iov->sent].iov_len -= (mhd_iov_elmn_size) track_sent; } } } - return res; + return mhd_SOCKET_ERR_NO_ERROR; } -#endif /* MHD_VECT_SEND */ +#endif /* mhd_USE_VECT_SEND */ -#if ! defined(MHD_VECT_SEND) || defined(HTTPS_SUPPORT) || \ - defined(_MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED) +#if ! defined(mhd_USE_VECT_SEND) || defined(HTTPS_SUPPORT) || \ + defined(mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED) /** @@ -1559,99 +1473,112 @@ send_iov_nontls (struct MHD_Connection *connection, * system buffers (usually set for the last piece of data), * set to false to prefer holding incomplete network packets * (more data will be send for the same reply). - * @return actual number of bytes sent + * @param[out] sent the pointer to get amount of actually sent bytes + * @return mhd_SOCKET_ERR_NO_ERROR if send succeed (the @a sent gets + * the sent size) or socket error */ -static ssize_t -send_iov_emu (struct MHD_Connection *connection, - struct MHD_iovec_track_ *const r_iov, - bool push_data) +static MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_ (4) enum mhd_SocketError +send_iov_emu (struct MHD_Connection *restrict connection, + struct mhd_iovec_track *const restrict r_iov, + bool push_data, + size_t *restrict sent) { const bool non_blk = connection->sk_nonblck; size_t total_sent; - ssize_t res; + size_t sent_elements; + size_t max_elelements_to_sent; mhd_assert (NULL != r_iov->iov); total_sent = 0; + sent_elements = 0; + max_elelements_to_sent = 8; /* Do not make too many sys-calls for just one connection */ do { - if ((size_t) SSIZE_MAX - total_sent < r_iov->iov[r_iov->sent].iov_len) - return (ssize_t) total_sent; /* return value would overflow */ + enum mhd_SocketError res; + size_t sent_el_size; - res = MHD_send_data_ (connection, + if (total_sent > (size_t)(r_iov->iov[r_iov->sent].iov_len + total_sent)) + break; /* return value would overflow */ + + res = mhd_send_data (connection, r_iov->iov[r_iov->sent].iov_base, r_iov->iov[r_iov->sent].iov_len, - push_data && (r_iov->cnt == r_iov->sent + 1)); - if (0 > res) + push_data && (r_iov->cnt == r_iov->sent + 1), + &sent_el_size); + if (mhd_SOCKET_ERR_NO_ERROR == res) { /* Result is an error */ if (0 == total_sent) - return res; /* Nothing was sent, return result as is */ + return res; /* Nothing was sent, return error as is */ - if (MHD_ERR_AGAIN_ == res) - return (ssize_t) total_sent; /* Return the amount of the sent data */ + if (mhd_SOCKET_ERR_IS_HARD(res)) + return res; /* Any kind of a hard error */ - return res; /* Any kind of a hard error */ + break; /* Return the amount of the sent data */ } - total_sent += (size_t) res; + total_sent += sent_el_size; - if (r_iov->iov[r_iov->sent].iov_len != (size_t) res) + if (r_iov->iov[r_iov->sent].iov_len != sent_el_size) { - const size_t sent = (size_t) res; /* Incomplete buffer has been sent. * Adjust buffer of the last element. */ r_iov->iov[r_iov->sent].iov_base = - (void *) ((uint8_t *) r_iov->iov[r_iov->sent].iov_base + sent); - r_iov->iov[r_iov->sent].iov_len -= (MHD_iov_size_) sent; + (void *) ((uint8_t *) r_iov->iov[r_iov->sent].iov_base + sent_el_size); + r_iov->iov[r_iov->sent].iov_len -= (mhd_iov_elmn_size) sent_el_size; - return (ssize_t) total_sent; + break; /* Return the amount of the sent data */ } /* The iov element has been completely sent */ r_iov->sent++; - } while ((r_iov->cnt > r_iov->sent) && (non_blk)); + } while ((r_iov->cnt > r_iov->sent) && 0 != (--max_elelements_to_sent) && + (non_blk)); - return (ssize_t) total_sent; + mhd_assert (0 != total_sent); + *sent = total_sent; + return mhd_SOCKET_ERR_NO_ERROR; } -#endif /* !MHD_VECT_SEND || HTTPS_SUPPORT - || _MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */ +#endif /* !mhd_USE_VECT_SEND || HTTPS_SUPPORT + || mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */ - -ssize_t -MHD_send_iovec_ (struct MHD_Connection *connection, - struct MHD_iovec_track_ *const r_iov, - bool push_data) +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_ (4) enum mhd_SocketError +mhd_send_iovec (struct MHD_Connection *restrict connection, + struct mhd_iovec_track *const restrict r_iov, + bool push_data, + size_t *restrict sent) { -#ifdef MHD_VECT_SEND +#ifdef mhd_USE_VECT_SEND #if defined(HTTPS_SUPPORT) || \ - defined(_MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED) + defined(mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED) bool use_iov_send = true; -#endif /* HTTPS_SUPPORT || _MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */ -#endif /* MHD_VECT_SEND */ +#endif /* HTTPS_SUPPORT || mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */ +#endif /* mhd_USE_VECT_SEND */ mhd_assert (NULL != connection->rp.resp_iov.iov); - mhd_assert (NULL != connection->rp.response->data_iov); + mhd_assert (mhd_RESPONSE_CONTENT_DATA_IOVEC == \ + connection->rp.response->cntn_dtype); mhd_assert (connection->rp.resp_iov.cnt > connection->rp.resp_iov.sent); -#ifdef MHD_VECT_SEND +#ifdef mhd_USE_VECT_SEND #if defined(HTTPS_SUPPORT) || \ - defined(_MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED) + defined(mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED) #ifdef HTTPS_SUPPORT - use_iov_send = use_iov_send && - (0 == (connection->daemon->options & MHD_USE_TLS)); + use_iov_send = use_iov_send & + (true); // TODO: TLS support #endif /* HTTPS_SUPPORT */ -#ifdef _MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED +#ifdef mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED use_iov_send = use_iov_send && (connection->daemon->sigpipe_blocked || connection->sk_spipe_suppress); -#endif /* _MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */ +#endif /* mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */ if (use_iov_send) -#endif /* HTTPS_SUPPORT || _MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */ +#endif /* HTTPS_SUPPORT || mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */ return send_iov_nontls (connection, r_iov, push_data); -#endif /* MHD_VECT_SEND */ +#endif /* mhd_USE_VECT_SEND */ -#if ! defined(MHD_VECT_SEND) || defined(HTTPS_SUPPORT) || \ - defined(_MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED) +#if ! defined(mhd_USE_VECT_SEND) || defined(HTTPS_SUPPORT) || \ + defined(mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED) return send_iov_emu (connection, r_iov, push_data); -#endif /* !MHD_VECT_SEND || HTTPS_SUPPORT - || _MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */ +#endif /* !mhd_USE_VECT_SEND || HTTPS_SUPPORT + || mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */ } diff --git a/src/microhttpd/mhd_send.h b/src/mhd2/mhd_send.h similarity index 54% rename from src/microhttpd/mhd_send.h rename to src/mhd2/mhd_send.h index c7a078ad..fd71fa2d 100644 --- a/src/microhttpd/mhd_send.h +++ b/src/mhd2/mhd_send.h @@ -1,14 +1,14 @@ /* This file is part of libmicrohttpd - Copyright (C) 2017-2021 Karlson2k (Evgeny Grin) + Copyright (C) 2017-2024 Evgeny Grin (Karlson2k) Copyright (C) 2019 ng0 - This library is free software; you can redistribute it and/or + GNU libmicrohttpd is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. - This library is distributed in the hope that it will be useful, + GNU libmicrohttpd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. @@ -20,37 +20,29 @@ */ /** - * @file mhd_send.h + * @file src/mhd2/mhd_send.h * @brief Declarations of send() wrappers. - * @author ng0 * @author Karlson2k (Evgeny Grin) + * @author ng0 (N. Gillmann) */ #ifndef MHD_SEND_H #define MHD_SEND_H -#include "platform.h" -#include "internal.h" -#if defined(HAVE_STDBOOL_H) -#include -#endif /* HAVE_STDBOOL_H */ -#include -#include "mhd_sockets.h" -#include "connection.h" -#ifdef HTTPS_SUPPORT -#include "connection_https.h" -#endif +#include "mhd_sys_options.h" -#if defined(HAVE_SENDMSG) || defined(HAVE_WRITEV) || \ - defined(MHD_WINSOCK_SOCKETS) -#define MHD_VECT_SEND 1 -#endif /* HAVE_SENDMSG || HAVE_WRITEV || MHD_WINSOCK_SOCKETS */ +#include "sys_base_types.h" +#include "sys_bool_type.h" +#include "mhd_socket_error.h" + +struct MHD_Connection; /* forward declaration */ +struct mhd_iovec_track; /* forward declaration */ /** * Initialises static variables */ void -MHD_send_init_static_vars_ (void); +mhd_send_init_static_vars (void); /** @@ -58,20 +50,23 @@ MHD_send_init_static_vars_ (void); * and full buffer is sent. * * @param connection the MHD_Connection structure - * @param buffer content of the buffer to send * @param buffer_size the size of the @a buffer (in bytes) + * @param buffer content of the buffer to send * @param push_data set to true to force push the data to the network from * system buffers (usually set for the last piece of data), * set to false to prefer holding incomplete network packets * (more data will be send for the same reply). - * @return sum of the number of bytes sent from both buffers or - * error code (negative) + * @param[out] sent the pointer to get amount of actually sent bytes + * @return mhd_SOCKET_ERR_NO_ERROR if send succeed (the @a sent gets + * the sent size) or socket error */ -ssize_t -MHD_send_data_ (struct MHD_Connection *connection, - const char *buffer, - size_t buffer_size, - bool push_data); +MHD_INTERNAL enum mhd_SocketError +mhd_send_data (struct MHD_Connection *restrict connection, + size_t buf_size, + const char buf[MHD_FN_PAR_DYN_ARR_SIZE_(buf_size)], + bool push_data, + size_t *restrict sent) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_ (3,2) MHD_FN_PAR_OUT_ (5); /** @@ -89,31 +84,62 @@ MHD_send_data_ (struct MHD_Connection *connection, * is provided by @a header and @a body, * set to false if additional body data * will be sent later - * @return sum of the number of bytes sent from both buffers or - * error code (negative) + * @param[out] sent the pointer to get amount of actually sent bytes + * in total (from both buffers combined) + * @return mhd_SOCKET_ERR_NO_ERROR if send succeed (the @a sent gets + * the sent size) or socket error */ -ssize_t -MHD_send_hdr_and_body_ (struct MHD_Connection *connection, - const char *header, - size_t header_size, +MHD_INTERNAL enum mhd_SocketError +mhd_send_hdr_and_body (struct MHD_Connection *restrict connection, + size_t header_size, + const char *restrict header, bool never_push_hdr, - const char *body, size_t body_size, - bool complete_response); + const char *restrict body, + bool complete_response, + size_t *restrict sent) +MHD_FN_PAR_NONNULL_(1) MHD_FN_PAR_NONNULL_(3) +MHD_FN_PAR_IN_SIZE_ (3,2) MHD_FN_PAR_IN_SIZE_ (6,5) MHD_FN_PAR_OUT_ (8); -#if defined(_MHD_HAVE_SENDFILE) +#if defined(MHD_USE_SENDFILE) /** * Function for sending responses backed by file FD. * * @param connection the MHD connection structure - * @return actual number of bytes sent + * @param[out] sent the pointer to get amount of actually sent bytes + * in total (from both buffers combined) + * @return mhd_SOCKET_ERR_NO_ERROR if send succeed (the @a sent gets + * the sent size) or socket error */ -ssize_t -MHD_send_sendfile_ (struct MHD_Connection *connection); +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_ (2) enum mhd_SocketError +mhd_send_sendfile (struct MHD_Connection *restrict connection, + size_t *restrict sent) +MHD_FN_PAR_NONNULL_ALL_ ; #endif +/** + * Function for sending responses backed by a an array of memory buffers. + * + * @param connection the MHD connection structure + * @param r_iov the pointer to iov response structure with tracking + * @param push_data set to true to force push the data to the network from + * system buffers (usually set for the last piece of data), + * set to false to prefer holding incomplete network packets + * (more data will be send for the same reply). + * @param[out] sent the pointer to get amount of actually sent bytes + * in total (from both buffers combined) + * @return mhd_SOCKET_ERR_NO_ERROR if send succeed (the @a sent gets + * the sent size) or socket error + */ +MHD_INTERNAL enum mhd_SocketError +mhd_send_iovec (struct MHD_Connection *restrict connection, + struct mhd_iovec_track *const restrict r_iov, + bool push_data, + size_t *restrict sent) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_ (4); + /** * Set required TCP_NODELAY state for connection socket * @@ -123,9 +149,10 @@ MHD_send_sendfile_ (struct MHD_Connection *connection); * @return true if succeed, false if failed or not supported * by the current platform / kernel. */ -bool -MHD_connection_set_nodelay_state_ (struct MHD_Connection *connection, - bool nodelay_state); +MHD_INTERNAL bool +mhd_connection_set_nodelay_state (struct MHD_Connection *connection, + bool nodelay_state) +MHD_FN_PAR_NONNULL_ALL_; /** @@ -138,26 +165,10 @@ MHD_connection_set_nodelay_state_ (struct MHD_Connection *connection, * @return true if succeed, false if failed or not supported * by the current platform / kernel. */ -bool -MHD_connection_set_cork_state_ (struct MHD_Connection *connection, - bool cork_state); - - -/** - * Function for sending responses backed by a an array of memory buffers. - * - * @param connection the MHD connection structure - * @param r_iov the pointer to iov response structure with tracking - * @param push_data set to true to force push the data to the network from - * system buffers (usually set for the last piece of data), - * set to false to prefer holding incomplete network packets - * (more data will be send for the same reply). - * @return actual number of bytes sent - */ -ssize_t -MHD_send_iovec_ (struct MHD_Connection *connection, - struct MHD_iovec_track_ *const r_iov, - bool push_data); +MHD_INTERNAL bool +mhd_connection_set_cork_state (struct MHD_Connection *connection, + bool cork_state) +MHD_FN_PAR_NONNULL_ALL_; #endif /* MHD_SEND_H */ diff --git a/src/mhd2/mhd_socket_error.c b/src/mhd2/mhd_socket_error.c new file mode 100644 index 00000000..449a9e1e --- /dev/null +++ b/src/mhd2/mhd_socket_error.c @@ -0,0 +1,78 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_socket_error.c + * @brief The definition of mhd_SocketError-related functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" +#include "mhd_socket_error.h" +#include "sys_sockets_headers.h" +#include "mhd_sockets_macros.h" + +MHD_INTERNAL enum mhd_SocketError +mhd_socket_error_get_from_sys_err (int socket_err) +{ + if (mhd_SCKT_ERR_IS_EAGAIN (socket_err)) + return mhd_SOCKET_ERR_AGAIN; + else if (mhd_SCKT_ERR_IS_CONNRESET (socket_err)) + return mhd_SOCKET_ERR_CONNRESET; + else if (mhd_SCKT_ERR_IS_EINTR (socket_err)) + return mhd_SOCKET_ERR_INTR; + else if (mhd_SCKT_ERR_IS_CONN_BROKEN (socket_err)) + return mhd_SOCKET_ERR_CONN_BROKEN; + else if (mhd_SCKT_ERR_IS_PIPE (socket_err)) + return mhd_SOCKET_ERR_PIPE; + else if (mhd_SCKT_ERR_IS_NOTCONN (socket_err)) + return mhd_SOCKET_ERR_NOTCONN; + else if (mhd_SCKT_ERR_IS_LOW_MEM (socket_err)) + return mhd_SOCKET_ERR_NOMEM; + else if (mhd_SCKT_ERR_IS_BADF (socket_err)) + return mhd_SOCKET_ERR_BADF; + else if (mhd_SCKT_ERR_IS_EINVAL (socket_err)) + return mhd_SOCKET_ERR_INVAL; + else if (mhd_SCKT_ERR_IS_OPNOTSUPP (socket_err)) + return mhd_SOCKET_ERR_OPNOTSUPP; + else if (mhd_SCKT_ERR_IS_NOTSOCK (socket_err)) + return mhd_SOCKET_ERR_NOTSOCK; + + return mhd_SOCKET_ERR_OTHER; +} + + +MHD_INTERNAL enum mhd_SocketError +mhd_socket_error_get_from_socket (MHD_Socket fd) +{ +#if defined(SOL_SOCKET) && defined(SO_ERROR) + enum mhd_SocketError err; + int sock_err; + if (0 == setsockopt(fd, SOL_SOCKET, SOCKET_ERROR, + (void *) &sock_err, sizeof(sock_err))) + return mhd_socket_error_get_from_sys_err (sock_err); + + err = mhd_socket_error_get_from_sys_err(mhd_SCKT_GET_LERR()); + if ((mhd_SOCKET_ERR_NOTSOCK == err) || + (mhd_SOCKET_ERR_BADF == err)) + return err; +#endif /* SOL_SOCKET && SO_ERROR */ + return mhd_SOCKET_ERR_NOT_CHECKED; +} diff --git a/src/mhd2/mhd_socket_error.h b/src/mhd2/mhd_socket_error.h index 3d1a4b76..8e55de1b 100644 --- a/src/mhd2/mhd_socket_error.h +++ b/src/mhd2/mhd_socket_error.h @@ -20,7 +20,8 @@ /** * @file src/mhd2/mhd_socket_error.h - * @brief The definition of the mhd_SocketError enum + * @brief The definition of the mhd_SocketError enum and related macros and + * declarations of related functions * @author Karlson2k (Evgeny Grin) */ @@ -28,9 +29,11 @@ #define MHD_SOCKET_ERROR_H 1 #include "mhd_sys_options.h" +#include "mhd_socket_type.h" +// TODO: better classification, when clearer local closing / network aborts /** - * Recognised socket errors + * Recognised socket errors for recv() and send() */ enum MHD_FIXED_ENUM_ mhd_SocketError { @@ -72,6 +75,23 @@ enum MHD_FIXED_ENUM_ mhd_SocketError */ mhd_SOCKET_ERR_NOTCONN , + /** + * General TLS encryption or decryption error + */ + mhd_SOCKET_ERR_TLS + , + /** + * The socket has been shut down for writing or no longer connected + * Only for 'send()'. + */ + mhd_SOCKET_ERR_PIPE + , + /** + * The error status reported, but concrete code error has not been + * checked by MHD + */ + mhd_SOCKET_ERR_NOT_CHECKED + , /** * The socket FD is invalid */ @@ -92,22 +112,16 @@ enum MHD_FIXED_ENUM_ mhd_SocketError */ mhd_SOCKET_ERR_NOTSOCK , - /** - * The remote side shut down reading, the socket has been shut down - * for writing or no longer connected - * Only for 'send()'. - */ - mhd_SOCKET_ERR_PIPE - , - /** - * General TLS encryption or decryption error - */ - mhd_SOCKET_ERR_TLS - , /** * Other socket error */ mhd_SOCKET_ERR_OTHER + , + /** + * Internal (MHD) error + * Not actually reported by the OS + */ + mhd_SOCKET_ERR_INTERNAL }; @@ -116,4 +130,27 @@ enum MHD_FIXED_ENUM_ mhd_SocketError */ #define mhd_SOCKET_ERR_IS_HARD(err) (mhd_SOCKET_ERR_CONNRESET <= (err)) +/** + * Check whether the socket error is unexpected + */ +#define mhd_SOCKET_ERR_IS_BAD(err) (mhd_SOCKET_ERR_BADF <= (err)) + +/** + * Map recv() / send() system socket error to the enum value + * @param socket_err the system socket error + * @return the enum value for the @a socket_err + */ +MHD_INTERNAL enum mhd_SocketError +mhd_socket_error_get_from_sys_err (int socket_err); + +/** + * Get the last socket error recoded for the given socket + * @param fd the socket to check for the error + * @return the recorded error @a fd, + * #mhd_SOCKET_ERR_NOT_CHECKED if not possible to check @a fd for + * the error + */ +MHD_INTERNAL enum mhd_SocketError +mhd_socket_error_get_from_socket (MHD_Socket fd); + #endif /* ! MHD_SOCKET_ERROR_H */ diff --git a/src/mhd2/mhd_sockets_macros.h b/src/mhd2/mhd_sockets_macros.h index b7703c0b..d3668234 100644 --- a/src/mhd2/mhd_sockets_macros.h +++ b/src/mhd2/mhd_sockets_macros.h @@ -55,7 +55,7 @@ #endif /** - * mhd_send4 is a wrapper for system's send() + * mhd_sys_send4 is a wrapper for system's send() * @param s the socket to use * @param b the buffer with data to send * @param l the length of data in @a b @@ -64,11 +64,11 @@ */ #define mhd_sys_send4(s,b,l,f) \ ((ssize_t) send ((s),(const void*) (b),(mhd_SCKT_SEND_SIZE) (l), \ - ((MHD_MSG_NOSIGNAL) | (f)))) + ((mhd_MSG_NOSIGNAL) | (f)))) /** - * mhd_send is a simple wrapper for system's send() + * mhd_sys_send is a simple wrapper for system's send() * @param s the socket to use * @param b the buffer with data to send * @param l the length of data in @a b @@ -218,6 +218,16 @@ # define mhd_SCKT_ERR_IS_OPNOTSUPP(err) (WSAEOPNOTSUPP == (err)) #endif +#if defined(MHD_POSIX_SOCKETS) +# ifdef ENOPROTOOPT +# define mhd_SCKT_ERR_IS_NOPROTOOPT(err) (ENOPROTOOPT == (err)) +# else +# define mhd_SCKT_ERR_IS_NOPROTOOPT(err) ((void) (err), ! ! 0) +# endif +#elif defined(MHD_WINSOCK_SOCKETS) +# define mhd_SCKT_ERR_IS_NOPROTOOPT(err) (WSAENOPROTOOPT == (err)) +#endif + #if defined(MHD_POSIX_SOCKETS) # ifdef EBADF # define mhd_SCKT_ERR_IS_BADF(err) (EBADF == (err)) @@ -238,6 +248,16 @@ # define mhd_SCKT_ERR_IS_NOTSOCK(err) (WSAENOTSOCK == (err)) #endif +#if defined(MHD_POSIX_SOCKETS) +# ifdef EPIPE +# define mhd_SCKT_ERR_IS_PIPE(err) (EPIPE == (err)) +# else +# define mhd_SCKT_ERR_IS_PIPE(err) ((void) (err), ! ! 0) +# endif +#elif defined(MHD_WINSOCK_SOCKETS) +# define mhd_SCKT_ERR_IS_PIPE(err) (WSAESHUTDOWN == (err)) +#endif + /** * Check whether is given socket error is type of "incoming connection * was disconnected before 'accept()' is called". @@ -262,8 +282,8 @@ */ #if defined(MHD_POSIX_SOCKETS) /* + EHOSTUNREACH: probably reported by intermediate - + ETIMEDOUT: probably keep-alive ping failure - + ENETUNREACH: probably cable physically disconnected or similar */ + + ETIMEDOUT: probably keep-alive ping failure + + ENETUNREACH: probably cable physically disconnected or similar */ # define mhd_SCKT_ERR_IS_CONN_BROKEN(err) \ ((0 != (err)) && \ ((MHD_EHOSTUNREACH_OR_ZERO == (err)) || \ @@ -317,8 +337,8 @@ # if defined(HAVE_SOCK_NONBLOCK) # ifdef MHD_AF_UNIX # define mhd_socket_pair_nblk(fdarr_ptr) \ - (0 != socketpair (MHD_AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, ( \ - fdarr_ptr))) + (0 != socketpair (MHD_AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, \ + (fdarr_ptr))) # else # define mhd_socket_pair_nblk(fdarr_ptr) \ (0 != socketpair (AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0, (fdarr_ptr))) /* Fallback, could be broken on many platforms */ @@ -331,4 +351,30 @@ /* mhd_socket_pair() implemented in "mhd_sockets_funcs.h" based on local function */ #endif +#if defined(SOL_SOCKET) && defined(SO_NOSIGPIPE) +/** + * Helper for mhd_socket_nosignal() + */ +# ifdef HAVE_COMPOUND_LITERALS_LVALUES +# define mhd_socket_nosig_helper_int_one ((int){1}) +# else +/** + * Internal static const helper for mhd_socket_nosignal() + */ +static const int mhd_socket_nosig_helper_int_one = 1; +# endif + + +/** + * Change socket options to no signal on remote disconnect / broken connection. + * + * @param sock socket to manipulate + * @return non-zero if succeeded, zero otherwise + */ +# define mhd_socket_nosignal(sock) \ + (! setsockopt ((sock),SOL_SOCKET,SO_NOSIGPIPE, \ + &mhd_socket_nosig_helper_int_one, sizeof(int))) +#endif /* SOL_SOCKET && SO_NOSIGPIPE */ + + #endif /* ! MHD_SOCKETS_MACROS_H */ diff --git a/src/mhd2/mhd_str.c b/src/mhd2/mhd_str.c index f953aa6a..d266383a 100644 --- a/src/mhd2/mhd_str.c +++ b/src/mhd2/mhd_str.c @@ -1,13 +1,13 @@ /* This file is part of libmicrohttpd - Copyright (C) 2015-2024 Karlson2k (Evgeny Grin) + Copyright (C) 2015-2024 Evgeny Grin (Karlson2k) - This library is free software; you can redistribute it and/or + GNU libmicrohttpd is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. - This library is distributed in the hope that it will be useful, + GNU libmicrohttpd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. @@ -77,7 +77,7 @@ isasciilower (char c) MHD_static_inline_ bool isasciiupper (char c) { - return (c >= 'A') && (c <= 'Z'); + return (c <= 'Z') && (c >= 'A'); } @@ -107,7 +107,7 @@ isasciialpha (char c) MHD_static_inline_ bool isasciidigit (char c) { - return (c >= '0') && (c <= '9'); + return (c <= '9') && (c >= '0'); } @@ -122,8 +122,8 @@ MHD_static_inline_ bool isasciixdigit (char c) { return isasciidigit (c) || - ( (c >= 'A') && (c <= 'F') ) || - ( (c >= 'a') && (c <= 'f') ); + ( (c <= 'F') && (c >= 'A') ) || + ( (c <= 'f') && (c >= 'a') ); } @@ -179,7 +179,7 @@ toasciiupper (char c) #endif /* Disable unused functions. */ -#if defined(MHD_FAVOR_SMALL_CODE) /* Used only in MHD_str_to_uvalue_n_() */ +#if defined(MHD_FAVOR_SMALL_CODE) /* Used only in mhd_str_to_uvalue_n() */ /** * Convert US-ASCII decimal digit to its value. * @@ -510,12 +510,21 @@ toxdigitvalue (char c) } return -1; #else /* MHD_FAVOR_SMALL_CODE */ - if (isasciidigit (c)) - return (unsigned char) (c - '0'); - if ( (c >= 'A') && (c <= 'F') ) - return (unsigned char) (c - 'A' + 10); - if ( (c >= 'a') && (c <= 'f') ) - return (unsigned char) (c - 'a' + 10); + if (c <= 9) + { + if (c >= 0) + return (unsigned char) (c - '0'); + } + else if (c <= 'F') + { + if (c >= 'A') + return (unsigned char) (c - 'A' + 10); + } + else if (c <= 'F') + { + if (c >= 'a') + return (unsigned char) (c - 'a' + 10); + } return -1; #endif /* MHD_FAVOR_SMALL_CODE */ @@ -549,7 +558,7 @@ charsequalcaseless (const char c1, const char c2) * @return boolean true if character is lower case letter, * boolean false otherwise */ -#define isasciilower(c) (((char) (c)) >= 'a' && ((char) (c)) <= 'z') +#define isasciilower(c) ((((char) (c)) >= 'a') && (((char) (c)) <= 'z')) /** @@ -559,7 +568,7 @@ charsequalcaseless (const char c1, const char c2) * @return boolean true if character is upper case letter, * boolean false otherwise */ -#define isasciiupper(c) (((char) (c)) >= 'A' && ((char) (c)) <= 'Z') +#define isasciiupper(c) ((((char) (c)) <= 'Z') && (((char) (c)) >= 'A')) /** @@ -579,7 +588,7 @@ charsequalcaseless (const char c1, const char c2) * @return boolean true if character is decimal digit, boolean false * otherwise */ -#define isasciidigit(c) (((char) (c)) >= '0' && ((char) (c)) <= '9') +#define isasciidigit(c) ((((char) (c)) <= '9') && (((char) (c)) >= '0')) /** @@ -590,8 +599,8 @@ charsequalcaseless (const char c1, const char c2) * boolean false otherwise */ #define isasciixdigit(c) (isasciidigit ((c)) || \ - (((char) (c)) >= 'A' && ((char) (c)) <= 'F') || \ - (((char) (c)) >= 'a' && ((char) (c)) <= 'f') ) + (((char) (c)) <= 'F' && ((char) (c)) >= 'A') || \ + (((char) (c)) <= 'f' && ((char) (c)) >= 'a')) /** @@ -669,15 +678,8 @@ charsequalcaseless (const char c1, const char c2) #ifndef MHD_FAVOR_SMALL_CODE -/** - * Check two strings for equality, ignoring case of US-ASCII letters. - * - * @param str1 first string to compare - * @param str2 second string to compare - * @return non-zero if two strings are equal, zero otherwise. - */ -int -MHD_str_equal_caseless_ (const char *str1, +MHD_INTERNAL bool +mhd_str_equal_caseless (const char *str1, const char *str2) { while (0 != (*str1)) @@ -690,7 +692,7 @@ MHD_str_equal_caseless_ (const char *str1, str2++; } else - return 0; + return false; } return 0 == (*str2); } @@ -699,19 +701,8 @@ MHD_str_equal_caseless_ (const char *str1, #endif /* ! MHD_FAVOR_SMALL_CODE */ -/** - * Check two string for equality, ignoring case of US-ASCII letters and - * checking not more than @a maxlen characters. - * Compares up to first terminating null character, but not more than - * first @a maxlen characters. - * - * @param str1 first string to compare - * @param str2 second string to compare - * @param maxlen maximum number of characters to compare - * @return non-zero if two strings are equal, zero otherwise. - */ -int -MHD_str_equal_caseless_n_ (const char *const str1, +MHD_INTERNAL bool +mhd_str_equal_caseless_n (const char *const str1, const char *const str2, size_t maxlen) { @@ -726,26 +717,16 @@ MHD_str_equal_caseless_n_ (const char *const str1, if (charsequalcaseless (c1, c2)) continue; else - return 0; + return false; } - return ! 0; + return true; } -/** - * Check two string for equality, ignoring case of US-ASCII letters and - * checking not more than @a len bytes. - * Compares not more first than @a len bytes, including binary zero characters. - * Comparison stops at first unmatched byte. - * @param str1 first string to compare - * @param str2 second string to compare - * @param len number of characters to compare - * @return non-zero if @a len bytes are equal, zero otherwise. - */ -bool -MHD_str_equal_caseless_bin_n_ (const char *const str1, - const char *const str2, - size_t len) +MHD_INTERNAL bool +mhd_str_equal_caseless_bin_n (const char *const str1, + const char *const str2, + size_t len) { size_t i; @@ -762,21 +743,8 @@ MHD_str_equal_caseless_bin_n_ (const char *const str1, } -/** - * Check whether @a str has case-insensitive @a token. - * Token could be surrounded by spaces and tabs and delimited by comma. - * Match succeed if substring between start, end (of string) or comma - * contains only case-insensitive token and optional spaces and tabs. - * @warning token must not contain null-characters except optional - * terminating null-character. - * @param str the string to check - * @param token the token to find - * @param token_len length of token, not including optional terminating - * null-character. - * @return non-zero if two strings are equal, zero otherwise. - */ -bool -MHD_str_has_token_caseless_ (const char *str, +MHD_INTERNAL bool +mhd_str_has_token_caseless (const char *str, const char *const token, size_t token_len) { @@ -822,41 +790,13 @@ MHD_str_has_token_caseless_ (const char *str, } -/** - * Remove case-insensitive @a token from the @a str and put result - * to the output @a buf. - * - * Tokens in @a str could be surrounded by spaces and tabs and delimited by - * comma. The token match succeed if substring between start, end (of string) - * or comma contains only case-insensitive token and optional spaces and tabs. - * The quoted strings and comments are not supported by this function. - * - * The output string is normalised: empty tokens and repeated whitespaces - * are removed, no whitespaces before commas, exactly one space is used after - * each comma. - * - * @param str the string to process - * @param str_len the length of the @a str, not including optional - * terminating null-character. - * @param token the token to find - * @param token_len the length of @a token, not including optional - * terminating null-character. - * @param[out] buf the output buffer, not null-terminated. - * @param[in,out] buf_size pointer to the size variable, at input it - * is the size of allocated buffer, at output - * it is the size of the resulting string (can - * be up to 50% larger than input) or negative value - * if there is not enough space for the result - * @return 'true' if token has been removed, - * 'false' otherwise. - */ -bool -MHD_str_remove_token_caseless_ (const char *str, - size_t str_len, - const char *const token, - const size_t token_len, - char *buf, - ssize_t *buf_size) +MHD_INTERNAL bool +mhd_str_remove_token_caseless (const char *restrict str, + size_t str_len, + const char *const restrict token, + const size_t token_len, + char *restrict buf, + ssize_t *restrict buf_size) { const char *s1; /**< the "input" string / character */ char *s2; /**< the "output" string / character */ @@ -1003,40 +943,16 @@ MHD_str_remove_token_caseless_ (const char *str, } -/** - * Perform in-place case-insensitive removal of @a tokens from the @a str. - * - * Token could be surrounded by spaces and tabs and delimited by comma. - * The token match succeed if substring between start, end (of the string), or - * comma contains only case-insensitive token and optional spaces and tabs. - * The quoted strings and comments are not supported by this function. - * - * The input string must be normalised: empty tokens and repeated whitespaces - * are removed, no whitespaces before commas, exactly one space is used after - * each comma. The string is updated in-place. - * - * Behavior is undefined is the input string in not normalised. - * - * @param[in,out] str the string to update - * @param[in,out] str_len the length of the @a str, not including optional - * terminating null-character, not null-terminated - * @param tokens the token to find - * @param tokens_len the length of @a tokens, not including optional - * terminating null-character. - * @return 'true' if any token has been removed, - * 'false' otherwise. - */ -bool -MHD_str_remove_tokens_caseless_ (char *str, - size_t *str_len, - const char *const tokens, - const size_t tokens_len) +MHD_INTERNAL bool +mhd_str_remove_tokens_caseless (char *restrict str, + size_t *restrict str_len, + const char *const restrict tkns, + const size_t tokens_len) { - const char *const t = tokens; /**< a short alias for @a tokens */ size_t pt; /**< position in @a tokens */ bool token_removed; - mhd_assert (NULL == memchr (tokens, 0, tokens_len)); + mhd_assert (NULL == memchr (tkns, 0, tokens_len)); token_removed = false; pt = 0; @@ -1048,36 +964,36 @@ MHD_str_remove_tokens_caseless_ (char *str, /* Skip any initial whitespaces and empty tokens in 'tokens' */ while ( (pt < tokens_len) && - ((' ' == t[pt]) || ('\t' == t[pt]) || (',' == t[pt])) ) + ((' ' == tkns[pt]) || ('\t' == tkns[pt]) || (',' == tkns[pt])) ) pt++; if (pt >= tokens_len) break; /* No more tokens, nothing to remove */ /* Found non-whitespace char which is not a comma */ - tkn = t + pt; + tkn = tkns + pt; do { do { pt++; } while (pt < tokens_len && - (' ' != t[pt] && '\t' != t[pt] && ',' != t[pt])); + (' ' != tkns[pt] && '\t' != tkns[pt] && ',' != tkns[pt])); /* Found end of the token string, space, tab, or comma */ - tkn_len = pt - (size_t) (tkn - t); + tkn_len = pt - (size_t) (tkn - tkns); /* Skip all spaces and tabs */ - while (pt < tokens_len && (' ' == t[pt] || '\t' == t[pt])) + while (pt < tokens_len && (' ' == tkns[pt] || '\t' == tkns[pt])) pt++; /* Found end of the token string or non-whitespace char */ - } while (pt < tokens_len && ',' != t[pt]); + } while (pt < tokens_len && ',' != tkns[pt]); /* 'tkn' is the input token with 'tkn_len' chars */ mhd_assert (0 != tkn_len); if (*str_len == tkn_len) { - if (MHD_str_equal_caseless_bin_n_ (str, tkn, tkn_len)) + if (mhd_str_equal_caseless_bin_n (str, tkn, tkn_len)) { *str_len = 0; token_removed = true; @@ -1104,7 +1020,7 @@ MHD_str_remove_tokens_caseless_ (char *str, mhd_assert (pr >= pw); mhd_assert ((*str_len) >= (pr + tkn_len)); if ( ( ((*str_len) == (pr + tkn_len)) || (',' == str[pr + tkn_len]) ) && - MHD_str_equal_caseless_bin_n_ (str + pr, tkn, tkn_len) ) + mhd_str_equal_caseless_bin_n (str + pr, tkn, tkn_len) ) { /* current token in the input string matches the 'tkn', skip it */ mhd_assert ((*str_len == pr + tkn_len) || \ @@ -1179,19 +1095,9 @@ MHD_str_remove_tokens_caseless_ (char *str, #ifndef MHD_FAVOR_SMALL_CODE /* Use individual function for each case */ -/** - * Convert decimal US-ASCII digits in string to number in uint_fast64_t. - * Conversion stopped at first non-digit character. - * - * @param str string to convert - * @param[out] out_val pointer to uint_fast64_t to store result of conversion - * @return non-zero number of characters processed on succeed, - * zero if no digit is found, resulting value is larger - * then possible to store in uint_fast64_t or @a out_val is NULL - */ -size_t -MHD_str_to_uint64_ (const char *str, - uint_fast64_t *out_val) +MHD_INTERNAL size_t +mhd_str_to_uint64 (const char *restrict str, + uint_fast64_t *restrict out_val) { const char *const start = str; uint_fast64_t res; @@ -1203,13 +1109,15 @@ MHD_str_to_uint64_ (const char *str, do { const int digit = (unsigned char) (*str) - '0'; - if ( (res > (UINT64_MAX / 10)) || - ( (res == (UINT64_MAX / 10)) && - ((uint_fast64_t) digit > (UINT64_MAX % 10)) ) ) - return 0; + uint_fast64_t prev_res = res; res *= 10; + if (res / 10 != prev_res) + return 0; res += (unsigned int) digit; + if (res < (unsigned int) digit) + return 0; + str++; } while (isasciidigit (*str)); @@ -1218,23 +1126,10 @@ MHD_str_to_uint64_ (const char *str, } -/** - * Convert not more then @a maxlen decimal US-ASCII digits in string to - * number in uint_fast64_t. - * Conversion stopped at first non-digit character or after @a maxlen - * digits. - * - * @param str string to convert - * @param maxlen maximum number of characters to process - * @param[out] out_val pointer to uint_fast64_t to store result of conversion - * @return non-zero number of characters processed on succeed, - * zero if no digit is found, resulting value is larger - * then possible to store in uint_fast64_t or @a out_val is NULL - */ -size_t -MHD_str_to_uint64_n_ (const char *str, +MHD_INTERNAL size_t +mhd_str_to_uint64_n (const char *restrict str, size_t maxlen, - uint_fast64_t *out_val) + uint_fast64_t *restrict out_val) { uint_fast64_t res; size_t i; @@ -1247,14 +1142,14 @@ MHD_str_to_uint64_n_ (const char *str, do { const int digit = (unsigned char) str[i] - '0'; - - if ( (res > (UINT64_MAX / 10)) || - ( (res == (UINT64_MAX / 10)) && - ((uint_fast64_t) digit > (UINT64_MAX % 10)) ) ) - return 0; + uint_fast64_t prev_res = res; res *= 10; + if (res / 10 != prev_res) + return 0; res += (unsigned int) digit; + if (res < (unsigned int) digit) + return 0; i++; } while ( (i < maxlen) && isasciidigit (str[i]) ); @@ -1264,19 +1159,9 @@ MHD_str_to_uint64_n_ (const char *str, } -/** - * Convert hexadecimal US-ASCII digits in string to number in uint_fast32_t. - * Conversion stopped at first non-digit character. - * - * @param str string to convert - * @param[out] out_val pointer to uint_fast32_t to store result of conversion - * @return non-zero number of characters processed on succeed, - * zero if no digit is found, resulting value is larger - * then possible to store in uint_fast32_t or @a out_val is NULL - */ -size_t -MHD_strx_to_uint32_ (const char *str, - uint_fast32_t *out_val) +MHD_INTERNAL size_t +mhd_strx_to_uint32 (const char *restrict str, + uint_fast32_t *restrict out_val) { const char *const start = str; uint_fast32_t res; @@ -1289,15 +1174,15 @@ MHD_strx_to_uint32_ (const char *str, digit = toxdigitvalue (*str); while (digit >= 0) { - if ( (res < (UINT32_MAX / 16)) || - ((res == (UINT32_MAX / 16)) && - ( (uint_fast32_t) digit <= (UINT32_MAX % 16)) ) ) - { - res *= 16; - res += (unsigned int) digit; - } - else + uint_fast32_t prev_res = res; + + res *= 16; + if (res / 16 != prev_res) return 0; + res += (unsigned int) digit; + if (res < (unsigned int) digit) + return 0; + str++; digit = toxdigitvalue (*str); } @@ -1308,23 +1193,10 @@ MHD_strx_to_uint32_ (const char *str, } -/** - * Convert not more then @a maxlen hexadecimal US-ASCII digits in string - * to number in uint_fast32_t. - * Conversion stopped at first non-digit character or after @a maxlen - * digits. - * - * @param str string to convert - * @param maxlen maximum number of characters to process - * @param[out] out_val pointer to uint_fast32_t to store result of conversion - * @return non-zero number of characters processed on succeed, - * zero if no digit is found, resulting value is larger - * then possible to store in uint_fast32_t or @a out_val is NULL - */ -size_t -MHD_strx_to_uint32_n_ (const char *str, +MHD_INTERNAL size_t +mhd_strx_to_uint32_n (const char *restrict str, size_t maxlen, - uint_fast32_t *out_val) + uint_fast32_t *restrict out_val) { size_t i; uint_fast32_t res; @@ -1336,9 +1208,13 @@ MHD_strx_to_uint32_n_ (const char *str, i = 0; while (i < maxlen && (digit = toxdigitvalue (str[i])) >= 0) { - if ( (res > (UINT32_MAX / 16)) || - ((res == (UINT32_MAX / 16)) && - ( (uint_fast32_t) digit > (UINT32_MAX % 16)) ) ) + uint_fast32_t prev_res = res; + + res *= 16; + if (res / 16 != prev_res) + return 0; + res += (unsigned int) digit; + if (res < (unsigned int) digit) return 0; res *= 16; @@ -1362,9 +1238,9 @@ MHD_strx_to_uint32_n_ (const char *str, * zero if no digit is found, resulting value is larger * then possible to store in uint_fast64_t or @a out_val is NULL */ -size_t -MHD_strx_to_uint64_ (const char *str, - uint_fast64_t *out_val) +MHD_INTERNAL size_t +mhd_strx_to_uint64 (const char *restrict str, + uint_fast64_t *restrict out_val) { const char *const start = str; uint_fast64_t res; @@ -1376,15 +1252,15 @@ MHD_strx_to_uint64_ (const char *str, digit = toxdigitvalue (*str); while (digit >= 0) { - if ( (res < (UINT64_MAX / 16)) || - ((res == (UINT64_MAX / 16)) && - ( (uint_fast64_t) digit <= (UINT64_MAX % 16)) ) ) - { - res *= 16; - res += (unsigned int) digit; - } - else + uint_fast64_t prev_res = res; + + res *= 16; + if (res / 16 != prev_res) return 0; + res += (unsigned int) digit; + if (res < (unsigned int) digit) + return 0; + str++; digit = toxdigitvalue (*str); } @@ -1408,10 +1284,10 @@ MHD_strx_to_uint64_ (const char *str, * zero if no digit is found, resulting value is larger * then possible to store in uint_fast64_t or @a out_val is NULL */ -size_t -MHD_strx_to_uint64_n_ (const char *str, +MHD_INTERNAL size_t +mhd_strx_to_uint64_n (const char *restrict str, size_t maxlen, - uint_fast64_t *out_val) + uint_fast64_t *restrict out_val) { size_t i; uint_fast64_t res; @@ -1423,13 +1299,14 @@ MHD_strx_to_uint64_n_ (const char *str, i = 0; while (i < maxlen && (digit = toxdigitvalue (str[i])) >= 0) { - if ( (res > (UINT64_MAX / 16)) || - ((res == (UINT64_MAX / 16)) && - ( (uint_fast64_t) digit > (UINT64_MAX % 16)) ) ) - return 0; + uint_fast64_t prev_res = res; res *= 16; + if (res / 16 != prev_res) + return 0; res += (unsigned int) digit; + if (res < (unsigned int) digit) + return 0; i++; } @@ -1458,10 +1335,10 @@ MHD_strx_to_uint64_n_ (const char *str, * zero if no digit is found, resulting value is larger * then @a max_val, @a val_size is not 4/8 or @a out_val is NULL */ -size_t -MHD_str_to_uvalue_n_ (const char *str, +MHD_INTERNAL size_t +mhd_str_to_uvalue_n (const char *restrict str, size_t maxlen, - void *out_val, + void *restrict out_val, size_t val_size, uint_fast64_t max_val, unsigned int base) @@ -1510,8 +1387,8 @@ MHD_str_to_uvalue_n_ (const char *str, #endif /* MHD_FAVOR_SMALL_CODE */ -size_t -MHD_uint32_to_strx (uint_fast32_t val, +MHD_INTERNAL size_t +mhd_uint32_to_strx (uint_fast32_t val, char *buf, size_t buf_size) { @@ -1544,8 +1421,8 @@ MHD_uint32_to_strx (uint_fast32_t val, #ifndef MHD_FAVOR_SMALL_CODE -size_t -MHD_uint16_to_str (uint_fast16_t val, +MHD_INTERNAL size_t +mhd_uint16_to_str (uint_fast16_t val, char *buf, size_t buf_size) { @@ -1585,14 +1462,14 @@ MHD_uint16_to_str (uint_fast16_t val, #endif /* !MHD_FAVOR_SMALL_CODE */ -size_t -MHD_uint64_to_str (uint_fast64_t val, +MHD_INTERNAL size_t +mhd_uint64_to_str (uint_fast64_t val, char *buf, size_t buf_size) { char *chr; /**< pointer to the current printed digit */ /* The biggest printable number is 18446744073709551615 */ - uint_fast64_t divisor = UINT64_C (10000000000000000000); + uint_fast64_t divisor = (uint_fast64_t) 10000000000000000000U; int digit; chr = buf; @@ -1623,8 +1500,8 @@ MHD_uint64_to_str (uint_fast64_t val, } -size_t -MHD_uint8_to_str_pad (uint8_t val, +MHD_INTERNAL size_t +mhd_uint8_to_str_pad (uint8_t val, uint8_t min_digits, char *buf, size_t buf_size) @@ -1670,10 +1547,10 @@ MHD_uint8_to_str_pad (uint8_t val, } -size_t -MHD_bin_to_hex (const void *bin, +MHD_INTERNAL size_t +mhd_bin_to_hex (const void *restrict bin, size_t size, - char *hex) + char *restrict hex) { size_t i; @@ -1690,26 +1567,25 @@ MHD_bin_to_hex (const void *bin, } -size_t -MHD_bin_to_hex_z (const void *bin, +MHD_INTERNAL size_t +mhd_bin_to_hex_z (const void *restrict bin, size_t size, - char *hex) + char *restrict hex) { size_t res; - res = MHD_bin_to_hex (bin, size, hex); + res = mhd_bin_to_hex (bin, size, hex); hex[res] = 0; return res; } -size_t -MHD_hex_to_bin (const char *hex, +MHD_INTERNAL size_t +mhd_hex_to_bin (const char *restrict hex, size_t len, - void *bin) + void *restrict bin) { - uint8_t *const out = (uint8_t *) bin; size_t r; size_t w; @@ -1723,7 +1599,7 @@ MHD_hex_to_bin (const char *hex, const int l = toxdigitvalue (hex[r++]); if (0 > l) return 0; - out[w++] = (uint8_t) ((unsigned int) l); + ((uint8_t *)bin)[w++] = (uint8_t) ((unsigned int) l); } while (r < len) { @@ -1731,8 +1607,9 @@ MHD_hex_to_bin (const char *hex, const int l = toxdigitvalue (hex[r++]); if ((0 > h) || (0 > l)) return 0; - out[w++] = (uint8_t) ( ((uint8_t) (((uint8_t) ((unsigned int) h)) << 4)) - | ((uint8_t) ((unsigned int) l)) ); + ((uint8_t *)bin)[w++] = (uint8_t) ( ((uint8_t) (((uint8_t) + ((unsigned int) h)) << 4)) + | ((uint8_t) ((unsigned int) l)) ); } mhd_assert (len == r); mhd_assert ((len + 1) / 2 == w); @@ -1740,8 +1617,8 @@ MHD_hex_to_bin (const char *hex, } -size_t -MHD_str_pct_decode_strict_n_ (const char *pct_encoded, +MHD_INTERNAL size_t +mhd_str_pct_decode_strict_n (const char *pct_encoded, size_t pct_encoded_len, char *decoded, size_t buf_size) @@ -1750,7 +1627,7 @@ MHD_str_pct_decode_strict_n_ (const char *pct_encoded, bool broken; size_t res; - res = MHD_str_pct_decode_lenient_n_ (pct_encoded, pct_encoded_len, decoded, + res = mhd_str_pct_decode_lenient_n (pct_encoded, pct_encoded_len, decoded, buf_size, &broken); if (broken) return 0; @@ -1823,8 +1700,8 @@ MHD_str_pct_decode_strict_n_ (const char *pct_encoded, } -size_t -MHD_str_pct_decode_lenient_n_ (const char *pct_encoded, +MHD_INTERNAL size_t +mhd_str_pct_decode_lenient_n (const char *pct_encoded, size_t pct_encoded_len, char *decoded, size_t buf_size, @@ -1922,14 +1799,14 @@ MHD_str_pct_decode_lenient_n_ (const char *pct_encoded, } -size_t -MHD_str_pct_decode_in_place_strict_ (char *str) +MHD_INTERNAL size_t +mhd_str_pct_decode_in_place_strict (char *str) { #ifdef MHD_FAVOR_SMALL_CODE size_t res; bool broken; - res = MHD_str_pct_decode_in_place_lenient_ (str, &broken); + res = mhd_str_pct_decode_in_place_lenient (str, &broken); if (broken) { res = 0; @@ -1978,8 +1855,8 @@ MHD_str_pct_decode_in_place_strict_ (char *str) } -size_t -MHD_str_pct_decode_in_place_lenient_ (char *str, +MHD_INTERNAL size_t +mhd_str_pct_decode_in_place_lenient (char *str, bool *broken_encoding) { #ifdef MHD_FAVOR_SMALL_CODE @@ -1987,7 +1864,7 @@ MHD_str_pct_decode_in_place_lenient_ (char *str, size_t res; len = strlen (str); - res = MHD_str_pct_decode_lenient_n_ (str, len, str, len, broken_encoding); + res = mhd_str_pct_decode_lenient_n (str, len, str, len, broken_encoding); str[res] = 0; return res; @@ -2055,8 +1932,8 @@ MHD_str_pct_decode_in_place_lenient_ (char *str, #ifdef DAUTH_SUPPORT -bool -MHD_str_equal_quoted_bin_n (const char *quoted, +MHD_INTERNAL bool +mhd_str_equal_quoted_bin_n (const char *quoted, size_t quoted_len, const char *unquoted, size_t unquoted_len) @@ -2085,8 +1962,8 @@ MHD_str_equal_quoted_bin_n (const char *quoted, } -bool -MHD_str_equal_caseless_quoted_bin_n (const char *quoted, +MHD_INTERNAL bool +mhd_str_equal_caseless_quoted_bin_n (const char *quoted, size_t quoted_len, const char *unquoted, size_t unquoted_len) @@ -2115,8 +1992,8 @@ MHD_str_equal_caseless_quoted_bin_n (const char *quoted, } -size_t -MHD_str_unquote (const char *quoted, +MHD_INTERNAL size_t +mhd_str_unquote (const char *quoted, size_t quoted_len, char *result) { @@ -2144,8 +2021,8 @@ MHD_str_unquote (const char *quoted, #if defined(DAUTH_SUPPORT) || defined(BAUTH_SUPPORT) -size_t -MHD_str_quote (const char *unquoted, +MHD_INTERNAL size_t +mhd_str_quote (const char *unquoted, size_t unquoted_len, char *result, size_t buf_size) @@ -2224,23 +2101,23 @@ MHD_str_quote (const char *unquoted, #endif /* MHD_BASE64_FUNC_VERSION < 1 || MHD_BASE64_FUNC_VERSION > 3 */ #if MHD_BASE64_FUNC_VERSION == 3 -#define MHD_base64_map_type_ int +#define mhd_base64_map_type int #else /* MHD_BASE64_FUNC_VERSION < 3 */ -#define MHD_base64_map_type_ int8_t +#define mhd_base64_map_type int8_t #endif /* MHD_BASE64_FUNC_VERSION < 3 */ #if MHD_BASE64_FUNC_VERSION == 1 -static MHD_base64_map_type_ +static mhd_base64_map_type base64_char_to_value_ (uint8_t c) { if ('Z' >= c) { if ('A' <= c) - return (MHD_base64_map_type_) ((c - 'A') + 0); + return (mhd_base64_map_type) ((c - 'A') + 0); if ('0' <= c) { if ('9' >= c) - return (MHD_base64_map_type_) ((c - '0') + 52); + return (mhd_base64_map_type) ((c - '0') + 52); if ('=' == c) return -2; return -1; @@ -2252,7 +2129,7 @@ base64_char_to_value_ (uint8_t c) return -1; } if (('z' >= c) && ('a' <= c)) - return (MHD_base64_map_type_) ((c - 'a') + 26); + return (mhd_base64_map_type) ((c - 'a') + 26); return -1; } @@ -2263,14 +2140,14 @@ base64_char_to_value_ (uint8_t c) MHD_DATA_TRUNCATION_RUNTIME_CHECK_DISABLE_ -size_t -MHD_base64_to_bin_n (const char *base64, +MHD_INTERNAL size_t +mhd_base64_to_bin_n (const char *base64, size_t base64_len, void *bin, size_t bin_size) { #if MHD_BASE64_FUNC_VERSION >= 2 - static const MHD_base64_map_type_ map[] = { + static const mhd_base64_map_type map[] = { /* -1 = invalid char, -2 = padding 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, NUL, SOH, STX, ETX, EOT, ENQ, ACK, BEL, */ @@ -2370,10 +2247,10 @@ MHD_base64_to_bin_n (const char *base64, #endif /* MHD_BASE64_FUNC_VERSION == 2 */ if (1) { - const MHD_base64_map_type_ v1 = base64_char_to_value_ (in[i + 0]); - const MHD_base64_map_type_ v2 = base64_char_to_value_ (in[i + 1]); - const MHD_base64_map_type_ v3 = base64_char_to_value_ (in[i + 2]); - const MHD_base64_map_type_ v4 = base64_char_to_value_ (in[i + 3]); + const mhd_base64_map_type v1 = base64_char_to_value_ (in[i + 0]); + const mhd_base64_map_type v2 = base64_char_to_value_ (in[i + 1]); + const mhd_base64_map_type v3 = base64_char_to_value_ (in[i + 2]); + const mhd_base64_map_type v4 = base64_char_to_value_ (in[i + 3]); if ((0 > v1) || (0 > v2) || (0 > v3) || (0 > v4)) return 0; out[j + 0] = (uint8_t) (((uint8_t) (((uint8_t) v1) << 2)) @@ -2391,10 +2268,10 @@ MHD_base64_to_bin_n (const char *base64, #endif /* MHD_BASE64_FUNC_VERSION == 2 */ if (1) { /* The last four chars block */ - const MHD_base64_map_type_ v1 = base64_char_to_value_ (in[i + 0]); - const MHD_base64_map_type_ v2 = base64_char_to_value_ (in[i + 1]); - const MHD_base64_map_type_ v3 = base64_char_to_value_ (in[i + 2]); - const MHD_base64_map_type_ v4 = base64_char_to_value_ (in[i + 3]); + const mhd_base64_map_type v1 = base64_char_to_value_ (in[i + 0]); + const mhd_base64_map_type v2 = base64_char_to_value_ (in[i + 1]); + const mhd_base64_map_type v3 = base64_char_to_value_ (in[i + 2]); + const mhd_base64_map_type v4 = base64_char_to_value_ (in[i + 3]); if ((0 > v1) || (0 > v2)) return 0; /* Invalid char or padding at first two positions */ mhd_assert (j < bin_size); @@ -2435,6 +2312,6 @@ MHD_base64_to_bin_n (const char *base64, MHD_DATA_TRUNCATION_RUNTIME_CHECK_RESTORE_ -#undef MHD_base64_map_type_ +#undef mhd_base64_map_type #endif /* BAUTH_SUPPORT */ diff --git a/src/mhd2/mhd_str.h b/src/mhd2/mhd_str.h index a17dc96e..0139233a 100644 --- a/src/mhd2/mhd_str.h +++ b/src/mhd2/mhd_str.h @@ -1,13 +1,13 @@ /* This file is part of libmicrohttpd - Copyright (C) 2015-2024 Karlson2k (Evgeny Grin) + Copyright (C) 2015-2024 Evgeny Grin (Karlson2k) - This library is free software; you can redistribute it and/or + GNU libmicrohttpd is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. - This library is distributed in the hope that it will be useful, + GNU libmicrohttpd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. @@ -30,7 +30,9 @@ #include "sys_base_types.h" #include "sys_bool_type.h" #include "mhd_str_macros.h" - +#ifdef MHD_FAVOR_SMALL_CODE +# include "mhd_limits.h" +#endif /* * Block of functions/macros that use US-ASCII charset as required by HTTP * standards. Not affected by current locale settings. @@ -42,9 +44,9 @@ * * @param str1 first string to compare * @param str2 second string to compare - * @return non-zero if two strings are equal, zero otherwise. + * @return 'true' if two strings are equal, 'false' otherwise. */ -int +MHD_INTERNAL bool mhd_str_equal_caseless (const char *str1, const char *str2); @@ -63,9 +65,9 @@ mhd_str_equal_caseless (const char *str1, * @param str1 first string to compare * @param str2 second string to compare * @param maxlen maximum number of characters to compare - * @return non-zero if two strings are equal, zero otherwise. + * @return 'true' if two strings are equal, 'false' otherwise. */ -int +MHD_INTERNAL bool mhd_str_equal_caseless_n (const char *const str1, const char *const str2, size_t maxlen); @@ -79,9 +81,9 @@ mhd_str_equal_caseless_n (const char *const str1, * @param str1 first string to compare * @param str2 second string to compare * @param len number of characters to compare - * @return non-zero if @a len bytes are equal, zero otherwise. + * @return 'true' if two strings are equal, 'false' otherwise. */ -bool +MHD_INTERNAL bool mhd_str_equal_caseless_bin_n (const char *const str1, const char *const str2, size_t len); @@ -99,7 +101,7 @@ mhd_str_equal_caseless_bin_n (const char *const str1, * @param a the statically allocated string to compare * @param s the string to compare * @param len number of characters to compare - * @return non-zero if @a len bytes are equal, zero otherwise. + * @return 'true' if two strings are equal, 'false' otherwise. */ #define mhd_str_equal_caseless_s_bin_n(a,s,l) \ ((mhd_SSTR_LEN (a) == (l)) \ @@ -118,8 +120,8 @@ mhd_str_equal_caseless_bin_n (const char *const str1, * null-character. * @return non-zero if two strings are equal, zero otherwise. */ -bool -mhd_str_has_token_caseless (const char *str, +MHD_INTERNAL bool +mhd_str_has_token_caseless (const char *restrict str, const char *const token, size_t token_len); @@ -165,13 +167,13 @@ mhd_str_has_token_caseless (const char *str, * @return 'true' if token has been removed, * 'false' otherwise. */ -bool -mhd_str_remove_token_caseless (const char *str, +MHD_INTERNAL bool +mhd_str_remove_token_caseless (const char *restrict str, size_t str_len, - const char *const token, + const char *const restrict token, const size_t token_len, - char *buf, - ssize_t *buf_size); + char *restrict buf, + ssize_t *restrict buf_size); /** @@ -197,10 +199,10 @@ mhd_str_remove_token_caseless (const char *str, * @return 'true' if any token has been removed, * 'false' otherwise. */ -bool -mhd_str_remove_tokens_caseless (char *str, - size_t *str_len, - const char *const tokens, +MHD_INTERNAL bool +mhd_str_remove_tokens_caseless (char *restrict str, + size_t *restrict str_len, + const char *const restrict tkns, const size_t tokens_len); @@ -217,9 +219,9 @@ mhd_str_remove_tokens_caseless (char *str, * zero if no digit is found, resulting value is larger * then possible to store in uint_fast64_t or @a out_val is NULL */ -size_t -mhd_str_to_uint64 (const char *str, - uint_fast64_t *out_val); +MHD_INTERNAL size_t +mhd_str_to_uint64 (const char *restrict str, + uint_fast64_t *restrict out_val); /** * Convert not more then @a maxlen decimal US-ASCII digits in string to @@ -234,10 +236,10 @@ mhd_str_to_uint64 (const char *str, * zero if no digit is found, resulting value is larger * then possible to store in uint_fast64_t or @a out_val is NULL */ -size_t -mhd_str_to_uint64_n (const char *str, +MHD_INTERNAL size_t +mhd_str_to_uint64_n (const char *restrict str, size_t maxlen, - uint_fast64_t *out_val); + uint_fast64_t *restrict out_val); /** @@ -250,9 +252,9 @@ mhd_str_to_uint64_n (const char *str, * zero if no digit is found, resulting value is larger * then possible to store in uint_fast32_t or @a out_val is NULL */ -size_t -mhd_strx_to_uint32 (const char *str, - uint_fast32_t *out_val); +MHD_INTERNAL size_t +mhd_strx_to_uint32 (const char *restrict str, + uint_fast32_t *restrict out_val); /** @@ -268,10 +270,10 @@ mhd_strx_to_uint32 (const char *str, * zero if no digit is found, resulting value is larger * then possible to store in uint_fast32_t or @a out_val is NULL */ -size_t -mhd_strx_to_uint32_n (const char *str, +MHD_INTERNAL size_t +mhd_strx_to_uint32_n (const char *restrict str, size_t maxlen, - uint_fast32_t *out_val); + uint_fast32_t *restrict out_val); /** @@ -284,9 +286,9 @@ mhd_strx_to_uint32_n (const char *str, * zero if no digit is found, resulting value is larger * then possible to store in uint_fast64_t or @a out_val is NULL */ -size_t -mhd_strx_to_uint64 (const char *str, - uint_fast64_t *out_val); +MHD_INTERNAL size_t +mhd_strx_to_uint64 (const char *restrict str, + uint_fast64_t *restrict out_val); /** @@ -302,10 +304,10 @@ mhd_strx_to_uint64 (const char *str, * zero if no digit is found, resulting value is larger * then possible to store in uint_fast64_t or @a out_val is NULL */ -size_t -mhd_strx_to_uint64_n (const char *str, +MHD_INTERNAL size_t +mhd_strx_to_uint64_n (const char *restrict str, size_t maxlen, - uint_fast64_t *out_val); + uint_fast64_t *restrict out_val); #else /* MHD_FAVOR_SMALL_CODE */ /* Use one universal function and macros to reduce size */ @@ -327,10 +329,10 @@ mhd_strx_to_uint64_n (const char *str, * zero if no digit is found, resulting value is larger * then @a max_val, @a val_size is not 4/8 or @a out_val is NULL */ -size_t -mhd_str_to_uvalue_n (const char *str, +MHD_INTERNAL size_t +mhd_str_to_uvalue_n (const char *restrict str, size_t maxlen, - void *out_val, + void *restrict out_val, size_t val_size, uint_fast64_t max_val, unsigned int base); @@ -387,7 +389,7 @@ mhd_str_to_uvalue_n (const char *str, * @return number of characters has been put to the @a buf, * zero if buffer is too small (buffer may be modified). */ -size_t +MHD_INTERNAL size_t mhd_uint32_to_strx (uint_fast32_t val, char *buf, size_t buf_size); @@ -403,13 +405,13 @@ mhd_uint32_to_strx (uint_fast32_t val, * @return number of characters has been put to the @a buf, * zero if buffer is too small (buffer may be modified). */ -size_t +MHD_INTERNAL size_t mhd_uint16_to_str (uint_fast16_t val, char *buf, size_t buf_size); #else /* MHD_FAVOR_SMALL_CODE */ -#define MHD_uint16_to_str(v,b,s) MHD_uint64_to_str (v,b,s) +#define mhd_uint16_to_str(v,b,s) mhd_uint64_to_str (v,b,s) #endif /* MHD_FAVOR_SMALL_CODE */ @@ -422,7 +424,7 @@ mhd_uint16_to_str (uint_fast16_t val, * @return number of characters has been put to the @a buf, * zero if buffer is too small (buffer may be modified). */ -size_t +MHD_INTERNAL size_t mhd_uint64_to_str (uint_fast64_t val, char *buf, size_t buf_size); @@ -443,7 +445,7 @@ mhd_uint64_to_str (uint_fast64_t val, * @return number of characters has been put to the @a buf, * zero if buffer is too small (buffer may be modified). */ -size_t +MHD_INTERNAL size_t mhd_uint8_to_str_pad (uint8_t val, uint8_t min_digits, char *buf, @@ -459,10 +461,10 @@ mhd_uint8_to_str_pad (uint8_t val, * @param[out] hex the output buffer, should be at least 2 * @a size * @return The number of characters written to the output buffer. */ -size_t -mhd_bin_to_hex (const void *bin, +MHD_INTERNAL size_t +mhd_bin_to_hex (const void *restrict bin, size_t size, - char *hex); + char *restrict hex); /** * Convert @a size bytes from input binary data to lower case @@ -473,10 +475,10 @@ mhd_bin_to_hex (const void *bin, * @return The number of characters written to the output buffer, * not including terminating zero. */ -size_t -mhd_bin_to_hex_z (const void *bin, +MHD_INTERNAL size_t +mhd_bin_to_hex_z (const void *restrict bin, size_t size, - char *hex); + char *restrict hex); /** * Convert hexadecimal digits to binary data. @@ -491,10 +493,10 @@ mhd_bin_to_hex_z (const void *bin, * @return the number of bytes written to the output buffer, * zero if found any character which is not hexadecimal digits */ -size_t -mhd_hex_to_bin (const char *hex, +MHD_INTERNAL size_t +mhd_hex_to_bin (const char *restrict hex, size_t len, - void *bin); + void *restrict bin); /** * Decode string with percent-encoded characters as defined by @@ -514,7 +516,7 @@ mhd_hex_to_bin (const char *hex, * by less than two hexadecimal digits) or output buffer is too * small to hold the result */ -size_t +MHD_INTERNAL size_t mhd_str_pct_decode_strict_n_ (const char *pct_encoded, size_t pct_encoded_len, char *decoded, @@ -542,7 +544,7 @@ mhd_str_pct_decode_strict_n_ (const char *pct_encoded, * @return the number of characters written to the output buffer or * zero if output buffer is too small to hold the result */ -size_t +MHD_INTERNAL size_t mhd_str_pct_decode_lenient_n_ (const char *pct_encoded, size_t pct_encoded_len, char *decoded, @@ -563,8 +565,8 @@ mhd_str_pct_decode_lenient_n_ (const char *pct_encoded, * truncated to zero length if broken encoding is found * @return the number of character in decoded string */ -size_t -MHD_str_pct_decode_in_place_strict_ (char *str); +MHD_INTERNAL size_t +mhd_str_pct_decode_in_place_strict (char *str); /** @@ -585,8 +587,8 @@ MHD_str_pct_decode_in_place_strict_ (char *str); * optional, can be NULL * @return the number of character in decoded string */ -size_t -MHD_str_pct_decode_in_place_lenient_ (char *str, +MHD_INTERNAL size_t +mhd_str_pct_decode_in_place_lenient (char *str, bool *broken_encoding); #ifdef DAUTH_SUPPORT @@ -609,7 +611,7 @@ MHD_str_pct_decode_in_place_lenient_ (char *str, * non-zero if two strings are equal after unquoting of the * first string. */ -bool +MHD_INTERNAL bool mhd_str_equal_quoted_bin_n (const char *quoted, size_t quoted_len, const char *unquoted, @@ -631,8 +633,8 @@ mhd_str_equal_quoted_bin_n (const char *quoted, * non-zero if two strings are equal after unquoting of the * first string. */ -#define MHD_str_equal_quoted_s_bin_n(q,l,u) \ - MHD_str_equal_quoted_bin_n (q,l,u,mhd_SSTR_LEN (u)) +#define mhd_str_equal_quoted_s_bin_n(q,l,u) \ + mhd_str_equal_quoted_bin_n (q,l,u,mhd_SSTR_LEN (u)) /** * Check two strings for equality, "unquoting" the first string from quoted @@ -654,7 +656,7 @@ mhd_str_equal_quoted_bin_n (const char *quoted, * non-zero if two strings are caseless equal after unquoting of the * first string. */ -bool +MHD_INTERNAL bool mhd_str_equal_caseless_quoted_bin_n (const char *quoted, size_t quoted_len, const char *unquoted, @@ -677,8 +679,8 @@ mhd_str_equal_caseless_quoted_bin_n (const char *quoted, * non-zero if two strings are caseless equal after unquoting of the * first string. */ -#define MHD_str_equal_caseless_quoted_s_bin_n(q,l,u) \ - MHD_str_equal_caseless_quoted_bin_n (q,l,u,mhd_SSTR_LEN (u)) +#define mhd_str_equal_caseless_quoted_s_bin_n(q,l,u) \ + mhd_str_equal_caseless_quoted_bin_n (q,l,u,mhd_SSTR_LEN (u)) /** * Convert string from quoted to unquoted form as specified by @@ -695,7 +697,7 @@ mhd_str_equal_caseless_quoted_bin_n (const char *quoted, * zero if last backslash is not followed by any character (or * @a quoted_len is zero). */ -size_t +MHD_INTERNAL size_t mhd_str_unquote (const char *quoted, size_t quoted_len, char *result); @@ -719,7 +721,7 @@ mhd_str_unquote (const char *quoted, * @a unquoted_len, zero if @a unquoted_len is zero or if quoted * string is larger than @a buf_size. */ -size_t +MHD_INTERNAL size_t mhd_str_quote (const char *unquoted, size_t unquoted_len, char *result, @@ -737,7 +739,7 @@ mhd_str_quote (const char *unquoted, * @a enc_size is valid (properly padded), * undefined value smaller then @a enc_size if @a enc_size is not valid */ -#define MHD_base64_max_dec_size_(enc_size) (((enc_size) / 4) * 3) +#define mhd_base64_max_dec_size(enc_size) (((enc_size) / 4) * 3) /** * Convert Base64 encoded string to binary data. @@ -756,7 +758,7 @@ mhd_str_quote (const char *unquoted, * (base64_len / 4 * 3 - 2), (base64_len / 4 * 3 - 1) or * (base64_len / 4 * 3), depending on the number of padding characters. */ -size_t +MHD_INTERNAL size_t mhd_base64_to_bin_n (const char *base64, size_t base64_len, void *bin, diff --git a/src/mhd2/request_funcs.c b/src/mhd2/request_funcs.c new file mode 100644 index 00000000..6b3519f9 --- /dev/null +++ b/src/mhd2/request_funcs.c @@ -0,0 +1,70 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2022-2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/request_funcs.c + * @brief The definition of the request internal functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" +#include "mhd_request.h" +#include "mhd_connection.h" +#include "request_funcs.h" +#include "stream_funcs.h" +#include "mhd_dlinked_list.h" + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_stream_add_field_nullable (struct MHD_Connection *restrict c, + enum MHD_ValueKind kind, + const struct MHD_String *restrict name, + const struct MHD_StringNullable *restrict value) +{ + struct mhd_RequestField *f; + + f = (struct mhd_RequestField *) + mhd_stream_alloc_memory (sizeof(struct mhd_RequestField)); + if (NULL == f) + return false; + + f->field.nv.name = name; + f->field.nv.value = value; + f->field.kind = kind; + mhd_DLINKEDL_INIT_LINKS(f, fields); + + mhd_DLINKEDL_INS_LAST(&(c->rq),f,fields); + + return true; +} + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_stream_add_field (struct MHD_Connection *restrict c, + enum MHD_ValueKind kind, + const struct MHD_String *restrict name, + const struct MHD_String *restrict value) +{ + struct MHD_StringNullable value2; + + value2.len = value->len; + value2.cstr = value->cstr; + + return mhd_stream_add_field_nullable(c, kind, name, &value2); +} diff --git a/src/mhd2/request_funcs.h b/src/mhd2/request_funcs.h new file mode 100644 index 00000000..e8836ddf --- /dev/null +++ b/src/mhd2/request_funcs.h @@ -0,0 +1,78 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2022-2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/request_funcs.h + * @brief The declaration of the request internal functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_REQUEST_FUNCS_H +#define MHD_REQUEST_FUNCS_H 1 + +#include "mhd_sys_options.h" +#include "sys_bool_type.h" +#include "mhd_str_types.h" +#include "mhd_public_api.h" + +struct MHD_Connection; /* forward declaration */ + +/** + * Add field to the request. + * The memory allocated in the request memory pool + * + * @param c the connection to use + * @param kind the kind of the field to add + * @param name the name of the field to add, the string is not copied, + * only copied the pointer value + * @param value the value of the field to add, the string is not copied, + * only copied the pointer value + * @return true if succeed, + * false if memory cannot be allocated + */ +MHD_INTERNAL bool +mhd_stream_add_field (struct MHD_Connection *restrict c, + enum MHD_ValueKind kind, + const struct MHD_String *restrict name, + const struct MHD_String *restrict value) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Add field to the request. + * The memory allocated in the request memory pool + * The value can have NULL string ("no value"). + * + * @param c the connection to use + * @param kind the kind of the field to add + * @param name the name of the field to add, the string is not copied, + * only copied the pointer value + * @param value the value of the field to add, the string is not copied, + * only copied the pointer value + * @return true if succeed, + * false if memory cannot be allocated + */ +MHD_INTERNAL bool +mhd_stream_add_field_nullable (struct MHD_Connection *restrict c, + enum MHD_ValueKind kind, + const struct MHD_String *restrict name, + const struct MHD_StringNullable *restrict value) +MHD_FN_PAR_NONNULL_ALL_; + +#endif /* ! MHD_REQUEST_FUNCS_H */ diff --git a/src/mhd2/request_get_value.c b/src/mhd2/request_get_value.c new file mode 100644 index 00000000..ca7a2b0b --- /dev/null +++ b/src/mhd2/request_get_value.c @@ -0,0 +1,68 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/request_get_value.c + * @brief The implementation of MHD_request_get_value*() functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" +#include "request_get_value.h" +#include "sys_base_types.h" +#include + +#include "mhd_request.h" + +#include "mhd_public_api.h" + +#include "mhd_dlinked_list.h" + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ (1) +MHD_FN_PAR_NONNULL_ (3) MHD_FN_PAR_CSTR_ (3) const struct MHD_String * +mhd_request_get_value_len (struct MHD_Request *MHD_RESTRICT request, + enum MHD_ValueKind kind, + size_t key_len, + const char *restrict key) +{ + struct mhd_RequestField *f; + + for (f = mhd_DLINKEDL_GET_FIRST(request, fields); NULL != f; + f = mhd_DLINKEDL_GET_NEXT(f, fields)) + { + if ((key_len == f->field.nv.name.len) && + (kind == f->field.kind) && + (0 == memcmp(key, f->field.nv.name.cstr))) + return &(f->field.nv.value); + } + return NULL; +} + +MHD_EXTERN_ MHD_FN_PAR_NONNULL_ (1) +MHD_FN_PAR_NONNULL_ (3) MHD_FN_PAR_CSTR_ (3) const struct MHD_String * +MHD_request_get_value (struct MHD_Request *MHD_RESTRICT request, + enum MHD_ValueKind kind, + const char *MHD_RESTRICT key) +{ + size_t len; + len = strlen(key); + return mhd_request_get_value_len(request, kind, len, key); +} diff --git a/src/mhd2/request_get_value.h b/src/mhd2/request_get_value.h new file mode 100644 index 00000000..950bb523 --- /dev/null +++ b/src/mhd2/request_get_value.h @@ -0,0 +1,56 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/request_get_value.h + * @brief The declaration of internal mhd_request_get_value* functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_REQUEST_GET_VALUE_H +#define MHD_REQUEST_GET_VALUE_H 1 + +#include "mhd_sys_options.h" +#include "sys_base_types.h" +#include "mhd_public_api.h" + +/** + * Get a particular header (or other kind of request data) value. + * If multiple values match the kind, return any one of them. + * + * The returned pointer is valid until the response is queued. + * If the data is needed beyond this point, it should be copied. + * + * @param request request to get values from + * @param kind what kind of value are we looking for + * @param key the header to look for, empty to lookup 'trailing' value + * without a key + * @return NULL if no such item was found + * @ingroup request + */ +MHD_INTERNAL const struct MHD_String * +mhd_request_get_value_len (struct MHD_Request *restrict request, + enum MHD_ValueKind kind, + size_t key_len, + const char *restrict key) +MHD_FN_PAR_NONNULL_ (1) +MHD_FN_PAR_NONNULL_ (4) MHD_FN_PAR_CSTR_ (4); + +#endif /* ! MHD_REQUEST_GET_VALUE_H */ diff --git a/src/mhd2/response_add_header.c b/src/mhd2/response_add_header.c index 94c4372b..e563239c 100644 --- a/src/mhd2/response_add_header.c +++ b/src/mhd2/response_add_header.c @@ -34,6 +34,54 @@ #include "mhd_public_api.h" +MHD_INTERNAL +MHD_FN_PAR_NONNULL_ (1) +MHD_FN_PAR_NONNULL_ (3) MHD_FN_PAR_CSTR_ (3) MHD_FN_PAR_IN_SIZE_(3,2) +MHD_FN_PAR_NONNULL_ (5) MHD_FN_PAR_CSTR_ (5) MHD_FN_PAR_IN_SIZE_(5,4) bool +response_add_header_no_check ( + struct MHD_Response *response, + size_t name_len, + const char name[MHD_FN_PAR_DYN_ARR_SIZE_(name_len)], + size_t value_len, + const char value[MHD_FN_PAR_DYN_ARR_SIZE_(value_len)]) +{ + char *buf; + struct mhd_ResponseHeader *new_hdr; + + buf = malloc (sizeof(struct mhd_ResponseHeader) + name_len + value_len + 2); + if (NULL == buf) + return false; + + new_hdr = (struct mhd_ResponseHeader *) buf; + buf += sizeof(struct mhd_ResponseHeader); + memcpy (buf, name, name_len); + buf[name_len] = 0; + new_hdr->name.cstr = buf; + new_hdr->name.len = name_len; + buf += name_len + 1; + memcpy (buf, value, value_len); + buf[value_len] = 0; + new_hdr->value.cstr = buf; + new_hdr->value.len = value_len; + + mhd_DLINKEDL_INS_LAST (response, new_hdr, headers); + return true; +} + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ (1) void +mhd_response_remove_all_headers (struct MHD_Response *restrict r) +{ + struct mhd_ResponseHeader *hdr; + + for (hdr = mhd_DLINKEDL_GET_LAST(r, headers); NULL != hdr; + hdr = mhd_DLINKEDL_GET_LAST(r, headers)) + { + mhd_DLINKEDL_DEL(r, hdr, headers); + free (hdr); + } +} + + static enum MHD_StatusCode response_add_header_int (struct MHD_Response *response, const char *name, @@ -41,8 +89,6 @@ response_add_header_int (struct MHD_Response *response, { const size_t name_len = strlen (name); const size_t value_len = strlen (name); - char *buf; - struct mhd_ResponseHeader *new_hdr; if (response->frozen) /* Re-check with the lock held */ return MHD_SC_TOO_LATE; @@ -57,22 +103,9 @@ response_add_header_int (struct MHD_Response *response, (NULL != memchr (value, '\r', value_len))) return MHD_SC_RESP_HEADER_VALUE_INVALID; - buf = malloc (sizeof(struct mhd_ResponseHeader) + name_len + value_len + 2); - if (NULL == buf) + if (!response_add_header_no_check(response, name_len, name, value_len, value)) return MHD_SC_RESPONSE_HEADER_MALLOC_FAILED; - new_hdr = (struct mhd_ResponseHeader *) buf; - buf += sizeof(struct mhd_ResponseHeader); - memcpy (buf, name, name_len + 1); - new_hdr->name.cstr = buf; - new_hdr->name.len = name_len; - buf += name_len + 1; - memcpy (buf, value, value_len + 1); - new_hdr->value.cstr = buf; - new_hdr->value.len = value_len; - - mhd_DLINKEDL_INS_LAST (response, new_hdr, headers); - return MHD_SC_OK; } @@ -85,7 +118,7 @@ MHD_response_add_header (struct MHD_Response *response, const char *name, const char *value) { - bool need_lock; + bool need_unlock; enum MHD_StatusCode res; if (response->frozen) @@ -93,19 +126,19 @@ MHD_response_add_header (struct MHD_Response *response, if (response->reuse.reusable) { - need_lock = true; + need_unlock = true; if (! mhd_mutex_lock (&(response->reuse.settings_lock))) return MHD_SC_RESPONSE_MUTEX_LOCK_FAILED; - mhd_assert (1 == response->reuse.counter); + mhd_assert (1 == mhd_atomic_counter_get(&(response->reuse.counter))); } else - need_lock = false; + need_unlock = false; // TODO: add special processing for "Date", "Connection", "Content-Length" res = response_add_header_int (response, name, value); - if (need_lock) + if (need_unlock) mhd_mutex_unlock_chk (&(response->reuse.settings_lock)); return res; diff --git a/src/mhd2/response_add_header.h b/src/mhd2/response_add_header.h new file mode 100644 index 00000000..bcaf6be2 --- /dev/null +++ b/src/mhd2/response_add_header.h @@ -0,0 +1,44 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/response_add_header.h + * @brief The declarations of the response header manipulation internal + * functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_RESPONSE_ADD_HEADER_H +#define MHD_RESPONSE_ADD_HEADER_H 1 + +#include "mhd_sys_options.h" + +struct MHD_Response; /* forward declaration */ + +/** + * Remove all response headers + * @param r the response to use + */ +MHD_INTERNAL void +mhd_response_remove_all_headers (struct MHD_Response *restrict r) +MHD_FN_PAR_NONNULL_ (1); + + +#endif /* ! MHD_RESPONSE_ADD_HEADER_H */ diff --git a/src/mhd2/response_destroy.c b/src/mhd2/response_destroy.c new file mode 100644 index 00000000..95bfaf02 --- /dev/null +++ b/src/mhd2/response_destroy.c @@ -0,0 +1,88 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/response_destroy.c + * @brief The declarations of internal functions for response deletion + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" +#include "response_destroy.h" +#include "mhd_response.h" + +#include "mhd_assert.h" +#include "mhd_atomic_counter.h" + +#include "mhd_public_api.h" + +#include "response_add_header.h" +#include "response_funcs.h" + +/** + * Perform full response de-initialisation, with cleaning-up / freeing + * all content data and headers. + * The response settings (if any) must be already freed. + * @param r the response to free + */ +static MHD_FN_PAR_NONNULL_ (1) void +response_full_detinit (struct MHD_Response *restrict r) +{ + mhd_response_remove_all_headers (r); + if (r->reuse.reusable) + mhd_response_deinit_reusable(r); + mhd_response_deinit_content_data(r); + free (r); +} + + +MHD_INTERNAL void +mhd_response_dec_use_count (struct MHD_Response *restrict r) +{ + mhd_assert(r->frozen); + + if (r->reuse.reusable) + { + if (0 != mhd_atomic_counter_dec_get(&(r->reuse.counter))) + return; /* The response is still used somewhere */ + } + + response_full_detinit (r); +} + + +MHD_EXTERN_ MHD_FN_PAR_NONNULL_ (1) void +MHD_response_destroy (struct MHD_Response *response) +{ + if (! response->frozen) + { + /* This response has been never used for actions */ + mhd_assert (NULL != response->settings); + free (response->settings); +#ifndef NDEBUG + /* Decrement counter to avoid triggering assert in deinit function */ + mhd_assert (0 == mhd_atomic_counter_dec_get(&(response->reuse.counter))); +#endif + response_full_detinit (response); + return; + } + + mhd_response_dec_use_count (response); +} diff --git a/src/mhd2/response_destroy.h b/src/mhd2/response_destroy.h new file mode 100644 index 00000000..b4b59343 --- /dev/null +++ b/src/mhd2/response_destroy.h @@ -0,0 +1,43 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/response_destroy.h + * @brief The declarations of internal functions for response deletion + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_RESPONSE_DESTROY_H +#define MHD_RESPONSE_DESTROY_H 1 + +#include "mhd_sys_options.h" + +struct MHD_Response; /* forward declaration */ + +/** + * Free/destroy non-reusable response, decrement use count for reusable + * response and free/destroy if it is not used any more. + * @param response the response to manipulate + */ +MHD_INTERNAL void +mhd_response_dec_use_count (struct MHD_Response *restrict response); + + +#endif /* ! MHD_RESPONSE_DESTROY_H */ diff --git a/src/mhd2/response_from.c b/src/mhd2/response_from.c index 1502dbac..dbc73866 100644 --- a/src/mhd2/response_from.c +++ b/src/mhd2/response_from.c @@ -34,6 +34,7 @@ #include "compat_calloc.h" #include "sys_malloc.h" +#include "sys_file_fd.h" #include "mhd_public_api.h" @@ -43,15 +44,7 @@ #include "mhd_assert.h" -#include - -#ifndef ULONG_MAX -# define ULONG_MAX ((unsigned long) ~((unsigned long) 0)) -#endif - -#ifndef SIZE_MAX -# define ULONG_MAX ((unsigned long) ~((unsigned long) 0)) -#endif +#include "mhd_limits.h" static struct MHD_Response * response_create_basic (enum MHD_HTTP_StatusCode sc, @@ -76,7 +69,7 @@ response_create_basic (enum MHD_HTTP_StatusCode sc, #endif /* ! HAVE_NULL_PTR_ALL_ZEROS */ r->sc = sc; - r->size = cntn_size; + r->cntn_size = cntn_size; r->free.cb = free_cb; r->free.cls = free_cb_cls; r->settings = s; @@ -88,37 +81,18 @@ response_create_basic (enum MHD_HTTP_StatusCode sc, return NULL; /* Failure exit point */ } - -static void -response_destroy_unfrozen (struct MHD_Response *restrict r) +MHD_INTERNAL MHD_FN_PAR_NONNULL_ (1) void +mhd_response_deinit_content_data (struct MHD_Response *restrict r) { - mhd_assert (NULL != r->settings); - mhd_assert (! r->frozen); - - // TODO: remove headers - - if (mhd_RESPONSE_CONTENT_INVALID != r->cntn_type) - { /* Free callback has been set correctly already */ - if (mhd_RESPONSE_CONTENT_IOVEC == r->cntn_type) - free (r->cntn.iovec.iov); - else if (mhd_RESPONSE_CONTENT_FILE == r->cntn_type) - close (r->cntn.file.fd); - /* For #mhd_RESPONSE_CONTENT_IOVEC clean-up performed by callback - for both modes: internal copy and external cleanup */ - if (NULL != r->free.cb) - r->free.cb (r->free.cls); - } - free (r->settings); - free (r); -} - - -MHD_INTERNAL void -mhd_response_dec_use_count (struct MHD_Response *restrict response) -{ - (void) response; - // TODO: implement - return; + mhd_assert (mhd_RESPONSE_CONTENT_DATA_INVALID != r->cntn_dtype); + if (mhd_RESPONSE_CONTENT_DATA_IOVEC == r->cntn_dtype) + free (r->cntn.iovec.iov); + else if (mhd_RESPONSE_CONTENT_DATA_FILE == r->cntn_dtype) + close (r->cntn.file.fd); + /* For #mhd_RESPONSE_CONTENT_IOVEC clean-up performed by callback + for both modes: internal copy and external cleanup */ + if (NULL != r->free.cb) + r->free.cb (r->free.cls); } @@ -133,7 +107,7 @@ MHD_response_from_callback (enum MHD_HTTP_StatusCode sc, res = response_create_basic (sc, size, dyn_cont_fc, dyn_cont_cls); if (NULL != res) { - res->cntn_type = mhd_RESPONSE_CONTENT_CALLBACK; + res->cntn_dtype = mhd_RESPONSE_CONTENT_DATA_CALLBACK; res->cntn.dyn.cb = dyn_cont; res->cntn.dyn.cls = dyn_cont_cls; } @@ -160,7 +134,7 @@ MHD_response_from_buffer ( res = response_create_basic (sc, buffer_size, free_cb, free_cb_cls); if (NULL != res) { - res->cntn_type = mhd_RESPONSE_CONTENT_BUFFER; + res->cntn_dtype = mhd_RESPONSE_CONTENT_DATA_BUFFER; res->cntn.buf = (0 != buffer_size) ? (const unsigned char *) buffer : empty_buf; } @@ -207,7 +181,7 @@ MHD_response_from_buffer_copy ( if (NULL != res) { - res->cntn_type = mhd_RESPONSE_CONTENT_BUFFER; + res->cntn_dtype = mhd_RESPONSE_CONTENT_DATA_BUFFER; res->cntn.buf = buf_copy; } return res; @@ -255,8 +229,8 @@ MHD_response_from_iovec ( { size_t i_add; - i_add = (size_t) (iov[i].iov_len / MHD_IOV_ELMN_MAX_SIZE); - if (0 != iov[i].iov_len % MHD_IOV_ELMN_MAX_SIZE) + i_add = (size_t) (iov[i].iov_len / mhd_IOV_ELMN_MAX_SIZE); + if (0 != iov[i].iov_len % mhd_IOV_ELMN_MAX_SIZE) i_add++; i_cp += i_add; if (i_cp < i_add) @@ -271,7 +245,7 @@ MHD_response_from_iovec ( res = response_create_basic (sc, 0, free_cb, free_cb_cls); if (NULL != res) { - res->cntn_type = mhd_RESPONSE_CONTENT_BUFFER; + res->cntn_dtype = mhd_RESPONSE_CONTENT_DATA_BUFFER; res->cntn.buf = empty_buf; } return res; @@ -299,17 +273,17 @@ MHD_response_from_iovec ( if (0 == element_size) continue; /* skip zero-sized elements */ #if defined(MHD_WINSOCK_SOCKETS) && defined(_WIN64) - while (MHD_IOV_ELMN_MAX_SIZE < element_size) + while (mhd_IOV_ELMN_MAX_SIZE < element_size) { iov_copy[i_cp].iov_base = (char *) mhd_DROP_CONST (buf); - iov_copy[i_cp].iov_len = MHD_IOV_ELMN_MAX_SIZE; - buf += MHD_IOV_ELMN_MAX_SIZE; - element_size -= MHD_IOV_ELMN_MAX_SIZE; + iov_copy[i_cp].iov_len = mhd_IOV_ELMN_MAX_SIZE; + buf += mhd_IOV_ELMN_MAX_SIZE; + element_size -= mhd_IOV_ELMN_MAX_SIZE; i_cp++; } #endif /* MHD_WINSOCK_SOCKETS && _WIN64 */ iov_copy[i_cp].iov_base = mhd_DROP_CONST (buf); - iov_copy[i_cp].iov_len = (mhd_iov_size) element_size; + iov_copy[i_cp].iov_len = (mhd_iov_elmn_size) element_size; i_cp++; } mhd_assert (num_copy_elements == i_cp); @@ -318,7 +292,7 @@ MHD_response_from_iovec ( res = response_create_basic (sc, total_size, free_cb, free_cb_cls); if (NULL != res) { - res->cntn_type = mhd_RESPONSE_CONTENT_IOVEC; + res->cntn_dtype = mhd_RESPONSE_CONTENT_DATA_IOVEC; res->cntn.iovec.iov = iov_copy; res->cntn.iovec.cnt = i_cp; return res; @@ -340,7 +314,7 @@ MHD_response_from_fd (enum MHD_HTTP_StatusCode sc, res = response_create_basic (sc, size, NULL, NULL); if (NULL != res) { - res->cntn_type = mhd_RESPONSE_CONTENT_FILE; + res->cntn_dtype = mhd_RESPONSE_CONTENT_DATA_FILE; res->cntn.file.fd = fd; res->cntn.file.offset = offset; res->cntn.file.is_pipe = false; /* Not necessary */ @@ -358,7 +332,7 @@ MHD_response_from_pipe (enum MHD_HTTP_StatusCode sc, res = response_create_basic (sc, MHD_SIZE_UNKNOWN, NULL, NULL); if (NULL != res) { - res->cntn_type = mhd_RESPONSE_CONTENT_FILE; + res->cntn_dtype = mhd_RESPONSE_CONTENT_DATA_FILE; res->cntn.file.fd = fd; res->cntn.file.offset = 0; /* Not necessary */ res->cntn.file.is_pipe = true; diff --git a/src/mhd2/response_from.h b/src/mhd2/response_from.h index fe5e97ae..e4a7fe09 100644 --- a/src/mhd2/response_from.h +++ b/src/mhd2/response_from.h @@ -32,5 +32,13 @@ struct MHD_Response; /* forward declaration */ +/** + * Deinit / free / cleanup content data of the response + * @param r the response to use + */ +MHD_INTERNAL void +mhd_response_deinit_content_data (struct MHD_Response *restrict r) +MHD_FN_PAR_NONNULL_ (1); + #endif /* ! MHD_RESPONSE_FROM_H */ diff --git a/src/mhd2/response_funcs.c b/src/mhd2/response_funcs.c index 0ec5c534..a4b27312 100644 --- a/src/mhd2/response_funcs.c +++ b/src/mhd2/response_funcs.c @@ -26,11 +26,48 @@ #include "mhd_sys_options.h" +#include "sys_malloc.h" + #include "sys_null_macro.h" #include "mhd_response.h" #include "response_funcs.h" #include "mhd_locks.h" + +#include "mhd_atomic_counter.h" + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ (1) bool +response_make_reusable (struct MHD_Response *restrict r) +{ + mhd_assert (! r->reuse.reusable); + mhd_assert (! r->frozen); + mhd_assert (NULL != r->settings); + + if (mhd_mutex_init(&(r->reuse.settings_lock))) + { + if (mhd_atomic_counter_init(&(r->reuse.counter), 1)) + { + r->reuse.reusable = true; + return true; + } + (void) mhd_mutex_destroy(&(r->reuse.settings_lock)); + } + return false; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ (1) void +mhd_response_deinit_reusable (struct MHD_Response *restrict r) +{ + mhd_assert(r->reuse.reusable); + mhd_assert(0 == mhd_atomic_counter_get(&(r->reuse.counter))); + + mhd_atomic_counter_deinit(&(r->reuse.counter)); + mhd_mutex_destroy_chk(&(r->reuse.settings_lock)); +} + + static void response_set_properties (struct MHD_Response *restrict r) { @@ -47,12 +84,13 @@ response_set_properties (struct MHD_Response *restrict r) else { r->cfg.conn_close = s->conn_close; - r->cfg.chunked = s->chunked_enc || (MHD_SIZE_UNKNOWN == r->size); + r->cfg.chunked = s->chunked_enc || (MHD_SIZE_UNKNOWN == r->cntn_size); } r->cfg.mode_1_0 = s->http_1_0_server; r->cfg.cnt_len_by_app = s->insanity_header_content_length; // TODO: set only if "content-lengh" header is used r->settings = NULL; + free (s); } @@ -64,22 +102,22 @@ response_set_properties (struct MHD_Response *restrict r) MHD_INTERNAL void mhd_response_check_frozen_freeze (struct MHD_Response *restrict response) { - bool need_lock; + bool need_unlock; if (response->frozen) return; if (response->reuse.reusable) { - need_lock = true; + need_unlock = true; mhd_mutex_lock_chk (&(response->reuse.settings_lock)); - mhd_assert (1 == response->reuse.counter); + mhd_assert (1 == mhd_atomic_counter_get(&(response->reuse.counter))); } else - need_lock = false; + need_unlock = false; if (! response->frozen)/* Re-check under the lock */ response_set_properties (response); - if (need_lock) + if (need_unlock) mhd_mutex_unlock_chk (&(response->reuse.settings_lock)); } diff --git a/src/mhd2/response_funcs.h b/src/mhd2/response_funcs.h index e72aff19..c3d45312 100644 --- a/src/mhd2/response_funcs.h +++ b/src/mhd2/response_funcs.h @@ -28,24 +28,37 @@ #define MHD_RESPONSE_FUNCS_H 1 #include "mhd_sys_options.h" +#include "sys_bool_type.h" + struct MHD_Response; /* forward declaration */ +/** + * Make response re-usable, initialise all required data + * @param r the response to make re-usable + * @return 'true' if succeed, 'false' if failed + */ +MHD_INTERNAL bool +response_make_reusable (struct MHD_Response *restrict r) +MHD_FN_PAR_NONNULL_ (1); + + +/** + * De-initialise re-usability data + * @param r the response to de-initialise re-usability data + */ +MHD_INTERNAL void +mhd_response_deinit_reusable (struct MHD_Response *restrict r) +MHD_FN_PAR_NONNULL_ (1); + + /** * Check whether response is "frozen" (modifications blocked) and "freeze" * it if not frozen before * @param response the response to manipulate */ MHD_INTERNAL void -mhd_response_check_frozen_freeze (struct MHD_Response *restrict response); - -/** - * Free/destroy non-reusable response, decrement use count for reusable - * response and free/destroy if it is not used any more. - * @param response the response to manipulate - */ -MHD_INTERNAL void -mhd_response_dec_use_count (struct MHD_Response *restrict response); +mhd_response_check_frozen_freeze (struct MHD_Response *restrict r); #endif /* ! MHD_RESPONSE_FUNCS_H */ diff --git a/src/mhd2/response_set_options.c b/src/mhd2/response_set_options.c index 7dc2a8d1..cd56338f 100644 --- a/src/mhd2/response_set_options.c +++ b/src/mhd2/response_set_options.c @@ -10,13 +10,14 @@ #include "mhd_sys_options.h" +#include "response_set_options.h" #include "sys_base_types.h" +#include "sys_bool_type.h" #include "response_options.h" #include "mhd_response.h" #include "mhd_public_api.h" -#include "sys_bool_type.h" #include "mhd_locks.h" - +#include "mhd_assert.h" /** * Internal version of the #MHD_response_set_options() @@ -26,13 +27,13 @@ * @param options_max_num the maximum number of @a options to use * @return #MHD_SC_OK on success, error code otherwise */ -static enum MHD_StatusCode +static MHD_FN_PAR_NONNULL_ALL_ enum MHD_StatusCode response_set_options_int ( - struct MHD_Response *volatile response, - const struct MHD_ResponseOptionAndValue *volatile options, + struct MHD_Response *restrict response, + const struct MHD_ResponseOptionAndValue *restrict options, size_t options_max_num) { - struct ResponseOptions *volatile settings; + struct ResponseOptions *restrict settings; size_t i; if (response->frozen) @@ -55,20 +56,8 @@ response_set_options_int ( else if (response->reuse.reusable) continue; /* This flag has been set before */ else - { - if (mhd_mutex_init(&(response->reuse.settings_lock))) - { - if (mhd_mutex_init_short(&(response->reuse.cnt_lock))) - { - response->reuse.counter = 1; - response->reuse.reusable = true; - continue; - } - mhd_mutex_destroy_chk(&(response->reuse.settings_lock)); - } - return MHD_SC_RESPONSE_MUTEX_INIT_FAILED; - } - settings->reusable = option->val.reusable; + if (! response_make_reusable(response)) + return MHD_SC_RESPONSE_MUTEX_INIT_FAILED; continue; case MHD_R_O_HEAD_ONLY_RESPONSE: settings->head_only_response = option->val.head_only_response; @@ -106,25 +95,24 @@ MHD_response_set_options (struct MHD_Response *response, const struct MHD_ResponseOptionAndValue *options, size_t options_max_num) { - bool need_lock; + bool need_unlock; enum MHD_StatusCode res; if (response->frozen) return MHD_SC_TOO_LATE; + need_unlock = false; if (response->reuse.reusable) { - need_lock = true; + need_unlock = true; if (! mhd_mutex_lock(&(response->reuse.settings_lock))) return MHD_SC_RESPONSE_MUTEX_LOCK_FAILED; - mhd_assert (1 == response->reuse.counter); + mhd_assert (1 == mhd_atomic_counter_get(&(response->reuse.counter))); } - else - need_lock = false; res = response_set_options_int(response, options, options_max_num); - if (need_lock) + if (need_unlock) mhd_mutex_unlock_chk(&(response->reuse.settings_lock)); return res; diff --git a/src/mhd2/response_set_options.h b/src/mhd2/response_set_options.h new file mode 100644 index 00000000..2660e3b4 --- /dev/null +++ b/src/mhd2/response_set_options.h @@ -0,0 +1,35 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/response_set_options.h + * @brief The declarations of the internal response functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_RESPONSE_SET_OPTIONS_H +#define MHD_RESPONSE_SET_OPTIONS_H 1 + +#include "mhd_sys_options.h" + +struct MHD_Response; /* forward declaration */ + + +#endif /* ! MHD_RESPONSE_SET_OPTIONS_H */ diff --git a/src/mhd2/stream_funcs.c b/src/mhd2/stream_funcs.c new file mode 100644 index 00000000..51260411 --- /dev/null +++ b/src/mhd2/stream_funcs.c @@ -0,0 +1,371 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2022-2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/stream_funcs.c + * @brief The definition of the stream internal functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" +#include "mhd_connection.h" +#include "mhd_public_api.h" +#include "stream_funcs.h" +#include "mhd_assert.h" +#include "mhd_mempool.h" +#include "mhd_str.h" +#include "mhd_str_macros.h" +#include "request_get_value.h" + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void * +mhd_stream_alloc_memory (struct MHD_Connection *restrict c, + size_t size) +{ + struct MemoryPool *const restrict pool = c->pool; /* a short alias */ + size_t need_to_be_freed = 0; /**< The required amount of additional free memory */ + void *res; + + res = MHD_pool_try_alloc (pool, + size, + &need_to_be_freed); + if (NULL != res) + return res; + + if (MHD_pool_is_resizable_inplace (pool, + c->write_buffer, + c->write_buffer_size)) + { + if (c->write_buffer_size - c->write_buffer_append_offset >= + need_to_be_freed) + { + char *buf; + const size_t new_buf_size = c->write_buffer_size - need_to_be_freed; + buf = MHD_pool_reallocate (pool, + c->write_buffer, + c->write_buffer_size, + new_buf_size); + mhd_assert (c->write_buffer == buf); + mhd_assert (c->write_buffer_append_offset <= new_buf_size); + mhd_assert (c->write_buffer_send_offset <= new_buf_size); + c->write_buffer_size = new_buf_size; + c->write_buffer = buf; + } + else + return NULL; + } + else if (MHD_pool_is_resizable_inplace (pool, + c->read_buffer, + c->read_buffer_size)) + { + if (c->read_buffer_size - c->read_buffer_offset >= need_to_be_freed) + { + char *buf; + const size_t new_buf_size = c->read_buffer_size - need_to_be_freed; + buf = MHD_pool_reallocate (pool, + c->read_buffer, + c->read_buffer_size, + new_buf_size); + mhd_assert (c->read_buffer == buf); + mhd_assert (c->read_buffer_offset <= new_buf_size); + c->read_buffer_size = new_buf_size; + c->read_buffer = buf; + } + else + return NULL; + } + else + return NULL; + res = MHD_pool_allocate (pool, size, true); + mhd_assert (NULL != res); /* It has been checked that pool has enough space */ + return res; +} + + +#ifndef MHD_MAX_REASONABLE_HEADERS_SIZE_ +/** + * A reasonable headers size (excluding request line) that should be sufficient + * for most requests. + * If incoming data buffer free space is not enough to process the complete + * header (the request line and all headers) and the headers size is larger than + * this size then the status code 431 "Request Header Fields Too Large" is + * returned to the client. + * The larger headers are processed by MHD if enough space is available. + */ +# define MHD_MAX_REASONABLE_HEADERS_SIZE_ (6 * 1024) +#endif /* ! MHD_MAX_REASONABLE_HEADERS_SIZE_ */ + +#ifndef MHD_MAX_REASONABLE_REQ_TARGET_SIZE_ +/** + * A reasonable request target (the request URI) size that should be sufficient + * for most requests. + * If incoming data buffer free space is not enough to process the complete + * header (the request line and all headers) and the request target size is + * larger than this size then the status code 414 "URI Too Long" is + * returned to the client. + * The larger request targets are processed by MHD if enough space is available. + * The value chosen according to RFC 9112 Section 3, paragraph 5 + */ +# define MHD_MAX_REASONABLE_REQ_TARGET_SIZE_ 8000 +#endif /* ! MHD_MAX_REASONABLE_REQ_TARGET_SIZE_ */ + +#ifndef MHD_MIN_REASONABLE_HEADERS_SIZE_ +/** + * A reasonable headers size (excluding request line) that should be sufficient + * for basic simple requests. + * When no space left in the receiving buffer try to avoid replying with + * the status code 431 "Request Header Fields Too Large" if headers size + * is smaller then this value. + */ +# define MHD_MIN_REASONABLE_HEADERS_SIZE_ 26 +#endif /* ! MHD_MIN_REASONABLE_HEADERS_SIZE_ */ + +#ifndef MHD_MIN_REASONABLE_REQ_TARGET_SIZE_ +/** + * A reasonable request target (the request URI) size that should be sufficient + * for basic simple requests. + * When no space left in the receiving buffer try to avoid replying with + * the status code 414 "URI Too Long" if the request target size is smaller then + * this value. + */ +# define MHD_MIN_REASONABLE_REQ_TARGET_SIZE_ 40 +#endif /* ! MHD_MIN_REASONABLE_REQ_TARGET_SIZE_ */ + +#ifndef MHD_MIN_REASONABLE_REQ_METHOD_SIZE_ +/** + * A reasonable request method string size that should be sufficient + * for basic simple requests. + * When no space left in the receiving buffer try to avoid replying with + * the status code 501 "Not Implemented" if the request method size is + * smaller then this value. + */ +# define MHD_MIN_REASONABLE_REQ_METHOD_SIZE_ 16 +#endif /* ! MHD_MIN_REASONABLE_REQ_METHOD_SIZE_ */ + +#ifndef MHD_MIN_REASONABLE_REQ_CHUNK_LINE_LENGTH_ +/** + * A reasonable minimal chunk line length. + * When no space left in the receiving buffer reply with 413 "Content Too Large" + * if the chunk line length is larger than this value. + */ +# define MHD_MIN_REASONABLE_REQ_CHUNK_LINE_LENGTH_ 4 +#endif /* ! MHD_MIN_REASONABLE_REQ_CHUNK_LINE_LENGTH_ */ + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_(1) MHD_FN_PAR_IN_SIZE_(4,3) unsigned int +mhd_stream_get_no_space_err_status_code (struct MHD_Connection *restrict c, + enum MHD_ProcRecvDataStage stage, + size_t add_element_size, + const char *restrict add_element) +{ + size_t method_size; + size_t uri_size; + size_t opt_headers_size; + size_t host_field_line_size; + + mhd_assert (MHD_CONNECTION_REQ_LINE_RECEIVED < c->state); + mhd_assert (MHD_PROC_RECV_HEADERS <= stage); + mhd_assert ((0 == add_element_size) || (NULL != add_element)); + + if (MHD_CONNECTION_HEADERS_RECEIVED > c->state) + { + mhd_assert (NULL != c->rq.field_lines.start); + opt_headers_size = + (size_t) ((c->read_buffer + c->read_buffer_offset) + - c->rq.field_lines.start); + } + else + opt_headers_size = c->rq.field_lines.size; + + /* The read buffer is fully used by the request line, the field lines + (headers) and internal information. + The return status code works as a suggestion for the client to reduce + one of the request elements. */ + + if ((MHD_PROC_RECV_BODY_CHUNKED == stage) && + (MHD_MIN_REASONABLE_REQ_CHUNK_LINE_LENGTH_ < add_element_size)) + { + /* Request could be re-tried easily with smaller chunk sizes */ + return MHD_HTTP_STATUS_CONTENT_TOO_LARGE; + } + + host_field_line_size = 0; + /* The "Host:" field line is mandatory. + The total size of the field lines (headers) cannot be smaller than + the size of the "Host:" field line. */ + if ((MHD_PROC_RECV_HEADERS == stage) + && (0 != add_element_size)) + { + static const size_t header_host_key_len = + mhd_SSTR_LEN (MHD_HTTP_HEADER_HOST); + const bool is_host_header = + (header_host_key_len + 1 <= add_element_size) + && ( (0 == add_element[header_host_key_len]) + || (':' == add_element[header_host_key_len]) ) + && mhd_str_equal_caseless_bin_n (MHD_HTTP_HEADER_HOST, + add_element, + header_host_key_len); + if (is_host_header) + { + const bool is_parsed = ! ( + (MHD_CONNECTION_HEADERS_RECEIVED > c->state) && + (add_element_size == c->read_buffer_offset) && + (c->read_buffer == add_element) ); + size_t actual_element_size; + + mhd_assert (! is_parsed || (0 == add_element[header_host_key_len])); + /* The actual size should be larger due to CRLF or LF chars, + however the exact termination sequence is not known here and + as perfect precision is not required, to simplify the code + assume the minimal length. */ + if (is_parsed) + actual_element_size = add_element_size + 1; /* "1" for LF */ + else + actual_element_size = add_element_size; + + host_field_line_size = actual_element_size; + mhd_assert (opt_headers_size >= actual_element_size); + opt_headers_size -= actual_element_size; + } + } + if (0 == host_field_line_size) + { + static const size_t host_field_name_len = + mhd_SSTR_LEN (MHD_HTTP_HEADER_HOST); + struct MHD_String *host_value; + host_value = mhd_request_get_value_len(&(c->rq), + MHD_VK_HEADER, + MHD_HTTP_HEADER_HOST, + host_field_name_len); + if (NULL != host_value) + { + /* Calculate the minimal size of the field line: no space between + colon and the field value, line terminated by LR */ + host_field_line_size = + host_field_name_len + host_value->len + 2; /* "2" for ':' and LF */ + + /* The "Host:" field could be added by application */ + if (opt_headers_size >= host_field_line_size) + { + opt_headers_size -= host_field_line_size; + /* Take into account typical space after colon and CR at the end of the line */ + if (opt_headers_size >= 2) + opt_headers_size -= 2; + } + else + host_field_line_size = 0; /* No "Host:" field line set by the client */ + } + } + + uri_size = c->rq.req_target_len; + if (mhd_HTTP_METHOD_OTHER != c->rq.http_mthd) + method_size = 0; /* Do not recommend shorter request method */ + else + { + mhd_assert (NULL != c->rq.method); + method_size = strlen (c->rq.method); + } + + if ((size_t) MHD_MAX_REASONABLE_HEADERS_SIZE_ < opt_headers_size) + { + /* Typically the easiest way to reduce request header size is + a removal of some optional headers. */ + if (opt_headers_size > (uri_size / 8)) + { + if ((opt_headers_size / 2) > method_size) + return MHD_HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE; + else + return MHD_HTTP_STATUS_NOT_IMPLEMENTED; /* The length of the HTTP request method is unreasonably large */ + } + else + { /* Request target is MUCH larger than headers */ + if ((uri_size / 16) > method_size) + return MHD_HTTP_STATUS_URI_TOO_LONG; + else + return MHD_HTTP_STATUS_NOT_IMPLEMENTED; /* The length of the HTTP request method is unreasonably large */ + } + } + if ((size_t) MHD_MAX_REASONABLE_REQ_TARGET_SIZE_ < uri_size) + { + /* If request target size if larger than maximum reasonable size + recommend client to reduce the request target size (length). */ + if ((uri_size / 16) > method_size) + return MHD_HTTP_STATUS_URI_TOO_LONG; /* Request target is MUCH larger than headers */ + else + return MHD_HTTP_STATUS_NOT_IMPLEMENTED; /* The length of the HTTP request method is unreasonably large */ + } + + /* The read buffer is too small to handle reasonably large requests */ + + if ((size_t) MHD_MIN_REASONABLE_HEADERS_SIZE_ < opt_headers_size) + { + /* Recommend application to retry with minimal headers */ + if ((opt_headers_size * 4) > uri_size) + { + if (opt_headers_size > method_size) + return MHD_HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE; + else + return MHD_HTTP_STATUS_NOT_IMPLEMENTED; /* The length of the HTTP request method is unreasonably large */ + } + else + { /* Request target is significantly larger than headers */ + if (uri_size > method_size * 4) + return MHD_HTTP_STATUS_URI_TOO_LONG; + else + return MHD_HTTP_STATUS_NOT_IMPLEMENTED; /* The length of the HTTP request method is unreasonably large */ + } + } + if ((size_t) MHD_MIN_REASONABLE_REQ_TARGET_SIZE_ < uri_size) + { + /* Recommend application to retry with a shorter request target */ + if (uri_size > method_size * 4) + return MHD_HTTP_STATUS_URI_TOO_LONG; + else + return MHD_HTTP_STATUS_NOT_IMPLEMENTED; /* The length of the HTTP request method is unreasonably large */ + } + + if ((size_t) MHD_MIN_REASONABLE_REQ_METHOD_SIZE_ < method_size) + { + /* The request target (URI) and headers are (reasonably) very small. + Some non-standard long request method is used. */ + /* The last resort response as it means "the method is not supported + by the server for any URI". */ + return MHD_HTTP_STATUS_NOT_IMPLEMENTED; + } + + /* The almost impossible situation: all elements are small, but cannot + fit the buffer. The application set the buffer size to + critically low value? */ + + if ((1 < opt_headers_size) || (1 < uri_size)) + { + if (opt_headers_size >= uri_size) + return MHD_HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE; + else + return MHD_HTTP_STATUS_URI_TOO_LONG; + } + + /* Nothing to reduce in the request. + Reply with some status. */ + if (0 != host_field_line_size) + return MHD_HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE; + + return MHD_HTTP_STATUS_URI_TOO_LONG; +} diff --git a/src/mhd2/stream_funcs.h b/src/mhd2/stream_funcs.h new file mode 100644 index 00000000..0e775dbe --- /dev/null +++ b/src/mhd2/stream_funcs.h @@ -0,0 +1,86 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2022-2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/stream_funcs.h + * @brief The declaration of the stream internal functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_STREAM_FUNCS_H +#define MHD_STREAM_FUNCS_H 1 + +#include "mhd_sys_options.h" +#include "sys_base_types.h" + +struct MHD_Connection; /* forward declaration */ + + +/** + * The stage of input data processing. + * Used for out-of-memory (in the pool) handling. + */ +enum MHD_FIXED_ENUM_ MHD_ProcRecvDataStage +{ + MHD_PROC_RECV_INIT, /**< No data HTTP request data have been processed yet */ + MHD_PROC_RECV_METHOD, /**< Processing/receiving the request HTTP method */ + MHD_PROC_RECV_URI, /**< Processing/receiving the request URI */ + MHD_PROC_RECV_HTTPVER, /**< Processing/receiving the request HTTP version string */ + MHD_PROC_RECV_HEADERS, /**< Processing/receiving the request HTTP headers */ + MHD_PROC_RECV_COOKIE, /**< Processing the received request cookie header */ + MHD_PROC_RECV_BODY_NORMAL, /**< Processing/receiving the request non-chunked body */ + MHD_PROC_RECV_BODY_CHUNKED,/**< Processing/receiving the request chunked body */ + MHD_PROC_RECV_FOOTERS /**< Processing/receiving the request footers */ +}; + +/** + * Allocate memory from connection's memory pool. + * If memory pool doesn't have enough free memory but read or write buffer + * have some unused memory, the size of the buffer will be reduced as needed. + * @param connection the connection to use + * @param size the size of allocated memory area + * @return pointer to allocated memory region in the pool or + * NULL if no memory is available + */ +MHD_INTERNAL void * +mhd_stream_alloc_memory (struct MHD_Connection *restrict connection, + size_t size) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Select the HTTP error status code for "out of receive buffer space" error. + * @param c the connection to process + * @param stage the current stage of request receiving + * @param add_element_size the size of the @a add_element; + * zero if @a add_element is NULL + * @param add_element the optional pointer to the element failed to be processed + * or added, the meaning of the element depends on + * the @a stage. Could be not zero-terminated and can + * contain binary zeros. Can be NULL. + * @return the HTTP error code to use in the error reply + */ +MHD_INTERNAL unsigned int +mhd_stream_get_no_space_err_status_code (struct MHD_Connection *restrict c, + enum MHD_ProcRecvDataStage stage, + size_t add_element_size, + const char *restrict add_element) +MHD_FN_PAR_NONNULL_(1) MHD_FN_PAR_IN_SIZE_(4,3); + +#endif /* ! MHD_STREAM_FUNCS_H */ diff --git a/src/mhd2/stream_process_request.c b/src/mhd2/stream_process_request.c new file mode 100644 index 00000000..5250559a --- /dev/null +++ b/src/mhd2/stream_process_request.c @@ -0,0 +1,2149 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/stream_process_request.c + * @brief The implementation of internal functions for requests parsing + * and processing + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include "mhd_str_macros.h" + +#include + +#include "mhd_connection.h" + +#include "mhd_str_types.h" +#include "daemon_logger.h" + +#include "request_funcs.h" + +/** + * Response text used when the request (http header) is + * malformed. + */ +#define ERR_RSP_REQUEST_MALFORMED \ + "Request malformed" \ + "HTTP request is syntactically incorrect." + +/** + * Response text used when the request HTTP version is too old. + */ +#define ERR_RSP_REQ_HTTP_VER_IS_TOO_OLD \ + "" \ + "Requested HTTP version is not supported" \ + "Requested HTTP version is too old and not " \ + "supported." +/** + * Response text used when the request HTTP version is not supported. + */ +#define ERR_RSP_REQ_HTTP_VER_IS_NOT_SUPPORTED \ + "" \ + "Requested HTTP version is not supported" \ + "Requested HTTP version is not supported." + +/** + * Response text used when the request HTTP header has bare CR character + * without LF character (and CR is not allowed to be treated as whitespace). + */ +#define ERR_RSP_BARE_CR_IN_HEADER \ + "" \ + "Request broken" \ + "Request HTTP header has bare CR character without " \ + "following LF character." \ + "" + +/** + * Response text used when the request HTTP footer has bare CR character + * without LF character (and CR is not allowed to be treated as whitespace). + */ +#define ERR_RSP_BARE_CR_IN_FOOTER \ + "" \ + "Request broken" \ + "Request HTTP footer has bare CR character without " \ + "following LF character." \ + "" + +/** + * Response text used when the request HTTP header has bare LF character + * without CR character. + */ +#define ERR_RSP_BARE_LF_IN_HEADER \ + "" \ + "Request broken" \ + "Request HTTP header has bare LF character without " \ + "preceding CR character." \ + "" +/** + * Response text used when the request HTTP footer has bare LF character + * without CR character. + */ +#define ERR_RSP_BARE_LF_IN_FOOTER \ + "" \ + "Request broken" \ + "Request HTTP footer has bare LF character without " \ + "preceding CR character." \ + "" + +/** + * Response text used when the request line has more then two whitespaces. + */ +#define ERR_RSP_RQ_LINE_TOO_MANY_WSP \ + "" \ + "Request broken" \ + "The request line has more then two whitespaces." \ + "" + +/** + * Response text used when the request line has invalid characters in URI. + */ +#define ERR_RSP_RQ_TARGET_INVALID_CHAR \ + "" \ + "Request broken" \ + "HTTP request has invalid characters in " \ + "the request-target." \ + "" + +/** + * Response text used when line folding is used in request headers. + */ +#define ERR_RSP_OBS_FOLD \ + "" \ + "Request broken" \ + "Obsolete line folding is used in HTTP request header." \ + "" + +/** + * Response text used when line folding is used in request footers. + */ +#define ERR_RSP_OBS_FOLD_FOOTER \ + "" \ + "Request broken" \ + "Obsolete line folding is used in HTTP request footer." \ + "" + +/** + * Response text used when request header has no colon character. + */ +#define ERR_RSP_HEADER_WITHOUT_COLON \ + "" \ + "Request broken" \ + "HTTP request header line has no colon character." \ + "" + +/** + * Response text used when request footer has no colon character. + */ +#define ERR_RSP_FOOTER_WITHOUT_COLON \ + "" \ + "Request broken" \ + "HTTP request footer line has no colon character." \ + "" +/** + * Response text used when the request has whitespace at the start + * of the first header line. + */ +#define ERR_RSP_WSP_BEFORE_HEADER \ + "" \ + "Request broken" \ + "HTTP request has whitespace between the request line and " \ + "the first header." \ + "" + +/** + * Response text used when the request has whitespace at the start + * of the first footer line. + */ +#define ERR_RSP_WSP_BEFORE_FOOTER \ + "" \ + "Request broken" \ + "First HTTP footer line has whitespace at the first " \ + "position." \ + "" + +/** + * Response text used when the whitespace found before colon (inside header + * name or between header name and colon). + */ +#define ERR_RSP_WSP_IN_HEADER_NAME \ + "" \ + "Request broken" \ + "HTTP request has whitespace before the first colon " \ + "in header line." \ + "" + +/** + * Response text used when the whitespace found before colon (inside header + * name or between header name and colon). + */ +#define ERR_RSP_WSP_IN_FOOTER_NAME \ + "" \ + "Request broken" \ + "HTTP request has whitespace before the first colon " \ + "in footer line." \ + "" +/** + * Response text used when request header has invalid character. + */ +#define ERR_RSP_INVALID_CHR_IN_HEADER \ + "" \ + "Request broken" \ + "HTTP request has invalid character in header." \ + "" + +/** + * Response text used when request header has invalid character. + */ +#define ERR_RSP_INVALID_CHR_IN_FOOTER \ + "" \ + "Request broken" \ + "HTTP request has invalid character in footer." \ + "" + +/** + * Response text used when request header has zero-length header (filed) name. + */ +#define ERR_RSP_EMPTY_HEADER_NAME \ + "" \ + "Request broken" \ + "HTTP request header has empty header name." \ + "" + +/** + * Response text used when request header has zero-length header (filed) name. + */ +#define ERR_RSP_EMPTY_FOOTER_NAME \ + "" \ + "Request broken" \ + "HTTP request footer has empty footer name." \ + "" + +/** + * Response text used when the request header is too big to be processed. + */ +#define ERR_RSP_REQUEST_HEADER_TOO_BIG \ + "" \ + "Request too big" \ + "

The total size of the request headers, which includes the " \ + "request target and the request field lines, exceeds the memory " \ + "constraints of this web server.

" \ + "

The request could be re-tried with shorter field lines, a shorter " \ + "request target or a shorter request method token.

" \ + "" + +/** + * Get whether bare LF in HTTP header and other protocol elements + * should be treated as the line termination depending on the configured + * strictness level. + * RFC 9112, section 2.2 + */ +#define MHD_ALLOW_BARE_LF_AS_CRLF_(discp_lvl) (0 >= discp_lvl) + +/** + * The valid length of any HTTP version string + */ +#define HTTP_VER_LEN (mhd_SSTR_LEN(MHD_HTTP_VERSION_1_1_STR)) + +/** + * Detect HTTP version, send error response if version is not supported + * + * @param connection the connection + * @param http_string the pointer to HTTP version string + * @param len the length of @a http_string in bytes + * @return true if HTTP version is correct and supported, + * false if HTTP version is not correct or unsupported. + */ +static bool +parse_http_version (struct MHD_Connection *restrict connection, + const char *restrict http_string, + size_t len) +{ + const char *const h = http_string; /**< short alias */ + mhd_assert (NULL != http_string); + + /* String must start with 'HTTP/d.d', case-sensetive match. + * See https://www.rfc-editor.org/rfc/rfc9112#name-http-version */ + if ((HTTP_VER_LEN != len) || + ('H' != h[0]) || ('T' != h[1]) || ('T' != h[2]) || ('P' != h[3]) || + ('/' != h[4]) + || ('.' != h[6])) + { + connection->rq.http_ver = MHD_HTTP_VERSION_INVALID; + transmit_error_response_static (connection, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_REQUEST_MALFORMED); + return false; + } + if (1 == h[5] - '0') + { + /* HTTP/1.x */ + if (1 == h[7] - '0') + { + connection->rq.http_ver = MHD_HTTP_VERSION_1_1; + return true; + } + else if (0 == h[7] - '0') + { + connection->rq.http_ver = MHD_HTTP_VERSION_1_0; + return true; + } + else + connection->rq.http_ver = MHD_HTTP_VERSION_INVALID; + + } + else if (0 == h[5] - '0') + { + /* Too old major version */ + connection->rq.http_ver = MHD_HTTP_VERSION_INVALID; + transmit_error_response_static (connection, + MHD_HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED, + ERR_RSP_REQ_HTTP_VER_IS_TOO_OLD); + return false; + } + else if ((2 == h[5] - '0') && + (('0' > h[7]) || ('9' < h[7]))) + connection->rq.http_ver = MHD_HTTP_VERSION_2; + else + connection->rq.http_ver = MHD_HTTP_VERSION_INVALID; + + transmit_error_response_static (connection, + MHD_HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED, + ERR_RSP_REQ_HTTP_VER_IS_NOT_SUPPORTED); + return false; +} + + +#ifndef MHD_MAX_EMPTY_LINES_SKIP +/** + * The maximum number of ignored empty line before the request line + * at default "strictness" level. + */ +#define MHD_MAX_EMPTY_LINES_SKIP 1024 +#endif /* ! MHD_MAX_EMPTY_LINES_SKIP */ + + +/** + * Find and parse the request line. + * @param c the connection to process + * @return true if request line completely processed (or unrecoverable error + * found) and state is changed, + * false if not enough data yet in the receive buffer + */ +static MHD_FN_PAR_NONNULL_ALL_ bool +mhd_get_request_line_inner (struct MHD_Connection *restrict c) +{ + size_t p; /**< The current processing position */ + const int discp_lvl = c->daemon->req_cfg.strictnees; + /* Allow to skip one or more empty lines before the request line. + RFC 9112, section 2.2 */ + const bool skip_empty_lines = (1 >= discp_lvl); + /* Allow to skip more then one empty line before the request line. + RFC 9112, section 2.2 */ + const bool skip_several_empty_lines = (skip_empty_lines && (0 >= discp_lvl)); + /* Allow to skip unlimited number of empty lines before the request line. + RFC 9112, section 2.2 */ + const bool skip_unlimited_empty_lines = + (skip_empty_lines && (-3 >= discp_lvl)); + /* Treat bare LF as the end of the line. + RFC 9112, section 2.2 */ + const bool bare_lf_as_crlf = MHD_ALLOW_BARE_LF_AS_CRLF_ (discp_lvl); + /* Treat tab as whitespace delimiter. + RFC 9112, section 3 */ + const bool tab_as_wsp = (0 >= discp_lvl); + /* Treat VT (vertical tab) and FF (form feed) as whitespace delimiters. + RFC 9112, section 3 */ + const bool other_wsp_as_wsp = (-1 >= discp_lvl); + /* Treat continuous whitespace block as a single space. + RFC 9112, section 3 */ + const bool wsp_blocks = (-1 >= discp_lvl); + /* Parse whitespace in URI, special parsing of the request line. + RFC 9112, section 3.2 */ + const bool wsp_in_uri = (0 >= discp_lvl); + /* Keep whitespace in URI, give app URI with whitespace instead of + automatic redirect to fixed URI. + Violates RFC 9112, section 3.2 */ + const bool wsp_in_uri_keep = (-2 >= discp_lvl); + /* Keep bare CR character as is. + Violates RFC 9112, section 2.2 */ + const bool bare_cr_keep = (wsp_in_uri_keep && (-3 >= discp_lvl)); + /* Treat bare CR as space; replace it with space before processing. + RFC 9112, section 2.2 */ + const bool bare_cr_as_sp = ((! bare_cr_keep) && (-1 >= discp_lvl)); + + mhd_assert (MHD_CONNECTION_INIT == c->state || \ + MHD_CONNECTION_REQ_LINE_RECEIVING == c->state); + mhd_assert (NULL == c->rq.method || \ + MHD_CONNECTION_REQ_LINE_RECEIVING == c->state); + mhd_assert (mhd_HTTP_METHOD_NO_METHOD == c->rq.http_mthd || \ + MHD_CONNECTION_REQ_LINE_RECEIVING == c->state); + mhd_assert (mhd_HTTP_METHOD_NO_METHOD == c->rq.http_mthd || \ + 0 != c->rq.hdrs.rq_line.proc_pos); + + if (0 == c->read_buffer_offset) + { + mhd_assert (MHD_CONNECTION_INIT == c->state); + return false; /* No data to process */ + } + p = c->rq.hdrs.rq_line.proc_pos; + mhd_assert (p <= c->read_buffer_offset); + + /* Skip empty lines, if any (and if allowed) */ + /* See RFC 9112, section 2.2 */ + if ((0 == p) + && (skip_empty_lines)) + { + /* Skip empty lines before the request line. + See RFC 9112, section 2.2 */ + bool is_empty_line; + mhd_assert (MHD_CONNECTION_INIT == c->state); + mhd_assert (NULL == c->rq.method); + mhd_assert (NULL == c->rq.url); + mhd_assert (0 == c->rq.url_len); + mhd_assert (NULL == c->rq.hdrs.rq_line.rq_tgt); + mhd_assert (0 == c->rq.req_target_len); + mhd_assert (NULL == c->rq.version); + do + { + is_empty_line = false; + if ('\r' == c->read_buffer[0]) + { + if (1 == c->read_buffer_offset) + return false; /* Not enough data yet */ + if ('\n' == c->read_buffer[1]) + { + is_empty_line = true; + c->read_buffer += 2; + c->read_buffer_size -= 2; + c->read_buffer_offset -= 2; + c->rq.hdrs.rq_line.skipped_empty_lines++; + } + } + else if (('\n' == c->read_buffer[0]) && + (bare_lf_as_crlf)) + { + is_empty_line = true; + c->read_buffer += 1; + c->read_buffer_size -= 1; + c->read_buffer_offset -= 1; + c->rq.hdrs.rq_line.skipped_empty_lines++; + } + if (is_empty_line) + { + if ((! skip_unlimited_empty_lines) && + (((unsigned int) ((skip_several_empty_lines) ? + MHD_MAX_EMPTY_LINES_SKIP : 1)) < + c->rq.hdrs.rq_line.skipped_empty_lines)) + { + connection_close_error (c, + _ ("Too many meaningless extra empty lines " \ + "received before the request")); + return true; /* Process connection closure */ + } + if (0 == c->read_buffer_offset) + return false; /* No more data to process */ + } + } while (is_empty_line); + } + /* All empty lines are skipped */ + + c->state = MHD_CONNECTION_REQ_LINE_RECEIVING; + /* Read and parse the request line */ + mhd_assert (1 <= c->read_buffer_offset); + + while (p < c->read_buffer_offset) + { + char *const restrict read_buffer = c->read_buffer; + const char chr = read_buffer[p]; + bool end_of_line; + /* + The processing logic is different depending on the configured strictness: + + When whitespace BLOCKS are NOT ALLOWED, the end of the whitespace is + processed BEFORE processing of the current character. + When whitespace BLOCKS are ALLOWED, the end of the whitespace is + processed AFTER processing of the current character. + + When space char in the URI is ALLOWED, the delimiter between the URI and + the HTTP version string is processed only at the END of the line. + When space in the URI is NOT ALLOWED, the delimiter between the URI and + the HTTP version string is processed as soon as the FIRST whitespace is + found after URI start. + */ + + end_of_line = false; + + mhd_assert ((0 == c->rq.hdrs.rq_line.last_ws_end) || \ + (c->rq.hdrs.rq_line.last_ws_end > \ + c->rq.hdrs.rq_line.last_ws_start)); + mhd_assert ((0 == c->rq.hdrs.rq_line.last_ws_start) || \ + (0 != c->rq.hdrs.rq_line.last_ws_end)); + + /* Check for the end of the line */ + if ('\r' == chr) + { + if (p + 1 == c->read_buffer_offset) + { + c->rq.hdrs.rq_line.proc_pos = p; + return false; /* Not enough data yet */ + } + else if ('\n' == read_buffer[p + 1]) + end_of_line = true; + else + { + /* Bare CR alone */ + /* Must be rejected or replaced with space char. + See RFC 9112, section 2.2 */ + if (bare_cr_as_sp) + { + read_buffer[p] = ' '; + c->rq.num_cr_sp_replaced++; + continue; /* Re-start processing of the current character */ + } + else if (! bare_cr_keep) + { + /* A quick simple check whether this line looks like an HTTP request */ + if ((mhd_HTTP_METHOD_GET <= c->rq.http_mthd) && + (mhd_HTTP_METHOD_DELETE >= c->rq.http_mthd)) + { + transmit_error_response_static (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_BARE_CR_IN_HEADER); + } + else + connection_close_error (c, + _ ("Bare CR characters are not allowed " \ + "in the request line.\n")); + return true; /* Error in the request */ + } + } + } + else if ('\n' == chr) + { + /* Bare LF may be recognised as a line delimiter. + See RFC 9112, section 2.2 */ + if (bare_lf_as_crlf) + end_of_line = true; + else + { + /* While RFC does not enforce error for bare LF character, + if this char is not treated as a line delimiter, it should be + rejected to avoid any security weakness due to request smuggling. */ + /* A quick simple check whether this line looks like an HTTP request */ + if ((mhd_HTTP_METHOD_GET <= c->rq.http_mthd) && + (mhd_HTTP_METHOD_DELETE >= c->rq.http_mthd)) + { + transmit_error_response_static (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_BARE_LF_IN_HEADER); + } + else + connection_close_error (c, + _ ("Bare LF characters are not allowed " \ + "in the request line.\n")); + return true; /* Error in the request */ + } + } + + if (end_of_line) + { + /* Handle the end of the request line */ + + if (NULL != c->rq.method) + { + if (wsp_in_uri) + { + /* The end of the URI and the start of the HTTP version string + should be determined now. */ + mhd_assert (NULL == c->rq.version); + mhd_assert (0 == c->rq.req_target_len); + if (0 != c->rq.hdrs.rq_line.last_ws_end) + { + /* Determine the end and the length of the URI */ + if (NULL != c->rq.hdrs.rq_line.rq_tgt) + { + read_buffer [c->rq.hdrs.rq_line.last_ws_start] = 0; /* Zero terminate the URI */ + c->rq.req_target_len = + c->rq.hdrs.rq_line.last_ws_start + - (size_t) (c->rq.hdrs.rq_line.rq_tgt - read_buffer); + } + else if ((c->rq.hdrs.rq_line.last_ws_start + 1 < + c->rq.hdrs.rq_line.last_ws_end) && + (HTTP_VER_LEN == (p - c->rq.hdrs.rq_line.last_ws_end))) + { + /* Found only HTTP method and HTTP version and more than one + whitespace between them. Assume zero-length URI. */ + mhd_assert (wsp_blocks); + c->rq.hdrs.rq_line.last_ws_start++; + read_buffer[c->rq.hdrs.rq_line.last_ws_start] = 0; /* Zero terminate the URI */ + c->rq.hdrs.rq_line.rq_tgt = + read_buffer + c->rq.hdrs.rq_line.last_ws_start; + c->rq.req_target_len = 0; + c->rq.hdrs.rq_line.num_ws_in_uri = 0; + c->rq.hdrs.rq_line.rq_tgt_qmark = NULL; + } + /* Determine the start of the HTTP version string */ + if (NULL != c->rq.hdrs.rq_line.rq_tgt) + { + c->rq.version = read_buffer + c->rq.hdrs.rq_line.last_ws_end; + } + } + } + else + { + /* The end of the URI and the start of the HTTP version string + should be already known. */ + if ((NULL == c->rq.version) + && (NULL != c->rq.hdrs.rq_line.rq_tgt) + && (HTTP_VER_LEN == p - (size_t) (c->rq.hdrs.rq_line.rq_tgt + - read_buffer)) + && (0 != read_buffer[(size_t) + (c->rq.hdrs.rq_line.rq_tgt + - read_buffer) - 1])) + { + /* Found only HTTP method and HTTP version and more than one + whitespace between them. Assume zero-length URI. */ + size_t uri_pos; + mhd_assert (wsp_blocks); + mhd_assert (0 == c->rq.req_target_len); + uri_pos = (size_t) (c->rq.hdrs.rq_line.rq_tgt - read_buffer) - 1; + mhd_assert (uri_pos < p); + c->rq.version = c->rq.hdrs.rq_line.rq_tgt; + read_buffer[uri_pos] = 0; /* Zero terminate the URI */ + c->rq.hdrs.rq_line.rq_tgt = read_buffer + uri_pos; + c->rq.req_target_len = 0; + c->rq.hdrs.rq_line.num_ws_in_uri = 0; + c->rq.hdrs.rq_line.rq_tgt_qmark = NULL; + } + } + + if (NULL != c->rq.version) + { + mhd_assert (NULL != c->rq.hdrs.rq_line.rq_tgt); + if (! parse_http_version (c, c->rq.version, + p + - (size_t) (c->rq.version + - read_buffer))) + { + mhd_assert (MHD_CONNECTION_REQ_LINE_RECEIVING < c->state); + return true; /* Unsupported / broken HTTP version */ + } + read_buffer[p] = 0; /* Zero terminate the HTTP version strings */ + if ('\r' == chr) + { + p++; /* Consume CR */ + mhd_assert (p < c->read_buffer_offset); /* The next character has been already checked */ + } + p++; /* Consume LF */ + c->read_buffer += p; + c->read_buffer_size -= p; + c->read_buffer_offset -= p; + mhd_assert (c->rq.hdrs.rq_line.num_ws_in_uri <= \ + c->rq.req_target_len); + mhd_assert ((NULL == c->rq.hdrs.rq_line.rq_tgt_qmark) || \ + (0 != c->rq.req_target_len)); + mhd_assert ((NULL == c->rq.hdrs.rq_line.rq_tgt_qmark) || \ + ((size_t) (c->rq.hdrs.rq_line.rq_tgt_qmark \ + - c->rq.hdrs.rq_line.rq_tgt) < \ + c->rq.req_target_len)); + mhd_assert ((NULL == c->rq.hdrs.rq_line.rq_tgt_qmark) || \ + (c->rq.hdrs.rq_line.rq_tgt_qmark >= \ + c->rq.hdrs.rq_line.rq_tgt)); + return true; /* The request line is successfully parsed */ + } + } + /* Error in the request line */ + + /* A quick simple check whether this line looks like an HTTP request */ + if ((mhd_HTTP_METHOD_GET <= c->rq.http_mthd) && + (mhd_HTTP_METHOD_DELETE >= c->rq.http_mthd)) + { + transmit_error_response_static (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_REQUEST_MALFORMED); + } + else + connection_close_error (c, + _ ("The request line is malformed.\n")); + + return true; + } + + /* Process possible end of the previously found whitespace delimiter */ + if ((! wsp_blocks) && + (p == c->rq.hdrs.rq_line.last_ws_end) && + (0 != c->rq.hdrs.rq_line.last_ws_end)) + { + /* Previous character was a whitespace char and whitespace blocks + are not allowed. */ + /* The current position is the next character after + a whitespace delimiter */ + if (NULL == c->rq.hdrs.rq_line.rq_tgt) + { + /* The current position is the start of the URI */ + mhd_assert (0 == c->rq.req_target_len); + mhd_assert (NULL == c->rq.version); + c->rq.hdrs.rq_line.rq_tgt = read_buffer + p; + /* Reset the whitespace marker */ + c->rq.hdrs.rq_line.last_ws_start = 0; + c->rq.hdrs.rq_line.last_ws_end = 0; + } + else + { + /* It was a whitespace after the start of the URI */ + if (! wsp_in_uri) + { + mhd_assert ((0 != c->rq.req_target_len) || \ + (c->rq.hdrs.rq_line.rq_tgt + 1 == read_buffer + p)); + mhd_assert (NULL == c->rq.version); /* Too many whitespaces? This error is handled at whitespace start */ + c->rq.version = read_buffer + p; + /* Reset the whitespace marker */ + c->rq.hdrs.rq_line.last_ws_start = 0; + c->rq.hdrs.rq_line.last_ws_end = 0; + } + } + } + + /* Process the current character. + Is it not the end of the line. */ + if ((' ' == chr) + || (('\t' == chr) && (tab_as_wsp)) + || ((other_wsp_as_wsp) && ((0xb == chr) || (0xc == chr)))) + { + /* A whitespace character */ + if ((0 == c->rq.hdrs.rq_line.last_ws_end) || + (p != c->rq.hdrs.rq_line.last_ws_end) || + (! wsp_blocks)) + { + /* Found first whitespace char of the new whitespace block */ + if (NULL == c->rq.method) + { + /* Found the end of the HTTP method string */ + mhd_assert (0 == c->rq.hdrs.rq_line.last_ws_start); + mhd_assert (0 == c->rq.hdrs.rq_line.last_ws_end); + mhd_assert (NULL == c->rq.hdrs.rq_line.rq_tgt); + mhd_assert (0 == c->rq.req_target_len); + mhd_assert (NULL == c->rq.version); + if (0 == p) + { + connection_close_error (c, + _ ("The request line starts with " + "a whitespace.\n")); + return true; /* Error in the request */ + } + read_buffer[p] = 0; /* Zero-terminate the request method string */ + c->rq.method = read_buffer; + parse_http_std_method (c, c->rq.method, p); + } + else + { + /* A whitespace after the start of the URI */ + if (! wsp_in_uri) + { + /* Whitespace in URI is not allowed to be parsed */ + if (NULL == c->rq.version) + { + mhd_assert (NULL != c->rq.hdrs.rq_line.rq_tgt); + /* This is a delimiter between URI and HTTP version string */ + read_buffer[p] = 0; /* Zero-terminate request URI string */ + mhd_assert (((size_t) (c->rq.hdrs.rq_line.rq_tgt \ + - read_buffer)) <= p); + c->rq.req_target_len = + p - (size_t) (c->rq.hdrs.rq_line.rq_tgt - read_buffer); + } + else + { + /* This is a delimiter AFTER version string */ + + /* A quick simple check whether this line looks like an HTTP request */ + if ((mhd_HTTP_METHOD_GET <= c->rq.http_mthd) && + (mhd_HTTP_METHOD_DELETE >= c->rq.http_mthd)) + { + transmit_error_response_static (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_RQ_LINE_TOO_MANY_WSP); + } + else + connection_close_error (c, + _ ("The request line has more than " + "two whitespaces.\n")); + return true; /* Error in the request */ + } + } + else + { + /* Whitespace in URI is allowed to be parsed */ + if (0 != c->rq.hdrs.rq_line.last_ws_end) + { + /* The whitespace after the start of the URI has been found already */ + c->rq.hdrs.rq_line.num_ws_in_uri += + c->rq.hdrs.rq_line.last_ws_end + - c->rq.hdrs.rq_line.last_ws_start; + } + } + } + c->rq.hdrs.rq_line.last_ws_start = p; + c->rq.hdrs.rq_line.last_ws_end = p + 1; /* Will be updated on the next char parsing */ + } + else + { + /* Continuation of the whitespace block */ + mhd_assert (0 != c->rq.hdrs.rq_line.last_ws_end); + mhd_assert (0 != p); + c->rq.hdrs.rq_line.last_ws_end = p + 1; + } + } + else + { + /* Non-whitespace char, not the end of the line */ + mhd_assert ((0 == c->rq.hdrs.rq_line.last_ws_end) || \ + (c->rq.hdrs.rq_line.last_ws_end == p) || \ + wsp_in_uri); + + if ((p == c->rq.hdrs.rq_line.last_ws_end) && + (0 != c->rq.hdrs.rq_line.last_ws_end) && + (wsp_blocks)) + { + /* The end of the whitespace block */ + if (NULL == c->rq.hdrs.rq_line.rq_tgt) + { + /* This is the first character of the URI */ + mhd_assert (0 == c->rq.req_target_len); + mhd_assert (NULL == c->rq.version); + c->rq.hdrs.rq_line.rq_tgt = read_buffer + p; + /* Reset the whitespace marker */ + c->rq.hdrs.rq_line.last_ws_start = 0; + c->rq.hdrs.rq_line.last_ws_end = 0; + } + else + { + if (! wsp_in_uri) + { + /* This is the first character of the HTTP version */ + mhd_assert (NULL != c->rq.hdrs.rq_line.rq_tgt); + mhd_assert ((0 != c->rq.req_target_len) || \ + (c->rq.hdrs.rq_line.rq_tgt + 1 == read_buffer + p)); + mhd_assert (NULL == c->rq.version); /* Handled at whitespace start */ + c->rq.version = read_buffer + p; + /* Reset the whitespace marker */ + c->rq.hdrs.rq_line.last_ws_start = 0; + c->rq.hdrs.rq_line.last_ws_end = 0; + } + } + } + + /* Handle other special characters */ + if ('?' == chr) + { + if ((NULL == c->rq.hdrs.rq_line.rq_tgt_qmark) && + (NULL != c->rq.hdrs.rq_line.rq_tgt)) + { + c->rq.hdrs.rq_line.rq_tgt_qmark = read_buffer + p; + } + } + else if ((0xb == chr) || (0xc == chr)) + { + /* VT or LF characters */ + mhd_assert (! other_wsp_as_wsp); + if ((NULL != c->rq.hdrs.rq_line.rq_tgt) && + (NULL == c->rq.version) && + (wsp_in_uri)) + { + c->rq.hdrs.rq_line.num_ws_in_uri++; + } + else + { + connection_close_error (c, + _ ("Invalid character is in the " + "request line.\n")); + return true; /* Error in the request */ + } + } + else if (0 == chr) + { + /* NUL character */ + connection_close_error (c, + _ ("The NUL character is in the " + "request line.\n")); + return true; /* Error in the request */ + } + } + + p++; + } + + c->rq.hdrs.rq_line.proc_pos = p; + return false; /* Not enough data yet */ +} + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_get_request_line (struct MHD_Connection *restrict c) +{ + const int discp_lvl = c->daemon->req_cfg.strictnees; + /* Parse whitespace in URI, special parsing of the request line */ + const bool wsp_in_uri = (0 >= discp_lvl); + /* Keep whitespace in URI, give app URI with whitespace instead of + automatic redirect to fixed URI */ + const bool wsp_in_uri_keep = (-2 >= discp_lvl); + + if (! get_request_line_inner (c)) + { + /* End of the request line has not been found yet */ + mhd_assert ((! wsp_in_uri) || NULL == c->rq.version); + if ((NULL != c->rq.version) && + (HTTP_VER_LEN < + (c->rq.hdrs.rq_line.proc_pos + - (size_t) (c->rq.version - c->read_buffer)))) + { + c->rq.http_ver = MHD_HTTP_VERSION_INVALID; + transmit_error_response_static (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_REQUEST_MALFORMED); + return true; /* Error in the request */ + } + return false; + } + if (MHD_CONNECTION_REQ_LINE_RECEIVING < c->state) + return true; /* Error in the request */ + + mhd_assert (MHD_CONNECTION_REQ_LINE_RECEIVING == c->state); + mhd_assert (NULL == c->rq.url); + mhd_assert (0 == c->rq.url_len); + mhd_assert (NULL != c->rq.hdrs.rq_line.rq_tgt); + if (0 != c->rq.hdrs.rq_line.num_ws_in_uri) + { + if (! wsp_in_uri) + { + transmit_error_response_static (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_RQ_TARGET_INVALID_CHAR); + return true; /* Error in the request */ + } + if (! wsp_in_uri_keep) + { + send_redirect_fixed_rq_target (c); + return true; /* Error in the request */ + } + } + if (! process_request_target (c)) + return true; /* Error in processing */ + + c->state = MHD_CONNECTION_REQ_LINE_RECEIVED; + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_switch_to_rq_headers_processing (struct MHD_Connection *restrict c) +{ + c->rq.field_lines.start = c->read_buffer; + memset (&c->rq.hdrs.hdr, 0, sizeof(c->rq.hdrs.hdr)); + c->state = MHD_CONNECTION_REQ_HEADERS_RECEIVING; +} + +/** + * Send error reply when receive buffer space exhausted while receiving or + * storing the request headers + * @param c the connection to handle + * @param add_header the optional pointer to the current header string being + * processed or the header failed to be added. + * Could be not zero-terminated and can contain binary zeros. + * Can be NULL. + * @param add_header_size the size of the @a add_header + */ +static void +handle_req_headers_no_space (struct MHD_Connection *restrict c, + const char *restrict add_header, + size_t add_header_size) +{ + unsigned int err_code; + + err_code = get_no_space_err_status_code (c, + MHD_PROC_RECV_HEADERS, + add_header, + add_header_size); + transmit_error_response_static (c, + err_code, + ERR_RSP_REQUEST_HEADER_TOO_BIG); +} + + +/** + * Results of header line reading + */ +enum mhd_HdrLineReadRes +{ + /** + * Not enough data yet + */ + MHD_HDR_LINE_READING_NEED_MORE_DATA = 0, + /** + * New header line has been read + */ + MHD_HDR_LINE_READING_GOT_HEADER, + /** + * Error in header data, error response has been queued + */ + MHD_HDR_LINE_READING_DATA_ERROR, + /** + * Found the end of the request header (end of field lines) + */ + MHD_HDR_LINE_READING_GOT_END_OF_HEADER +} _MHD_FIXED_ENUM; + + +/** + * Find the end of the request header line and make basic header parsing. + * Handle errors and header folding. + * @param c the connection to process + * @param process_footers if true then footers are processed, + * if false then headers are processed + * @param[out] hdr_name the name of the parsed header (field) + * @param[out] hdr_name the value of the parsed header (field) + * @return true if request header line completely processed, + * false if not enough data yet in the receive buffer + */ +static enum MHD_HdrLineReadRes_ +get_req_header (struct MHD_Connection *restrict c, + bool process_footers, + struct MHD_String *restrict hdr_name, + struct MHD_String *restrict hdr_value) +{ + const int discp_lvl = c->daemon->req_cfg.strictnees; + /* Treat bare LF as the end of the line. + RFC 9112, section 2.2-3 + Note: MHD never replaces bare LF with space (RFC 9110, section 5.5-5). + Bare LF is processed as end of the line or rejected as broken request. */ + const bool bare_lf_as_crlf = MHD_ALLOW_BARE_LF_AS_CRLF_ (discp_lvl); + /* Keep bare CR character as is. + Violates RFC 9112, section 2.2-4 */ + const bool bare_cr_keep = (-3 >= discp_lvl); + /* Treat bare CR as space; replace it with space before processing. + RFC 9112, section 2.2-4 */ + const bool bare_cr_as_sp = ((! bare_cr_keep) && (-1 >= discp_lvl)); + /* Treat NUL as space; replace it with space before processing. + RFC 9110, section 5.5-5 */ + const bool nul_as_sp = (-1 >= discp_lvl); + /* Allow folded header lines. + RFC 9112, section 5.2-4 */ + const bool allow_folded = (0 >= discp_lvl); + /* Do not reject headers with the whitespace at the start of the first line. + When allowed, the first line with whitespace character at the first + position is ignored (as well as all possible line foldings of the first + line). + RFC 9112, section 2.2-8 */ + const bool allow_wsp_at_start = allow_folded && (-1 >= discp_lvl); + /* Allow whitespace in header (field) name. + Violates RFC 9110, section 5.1-2 */ + const bool allow_wsp_in_name = (-2 >= discp_lvl); + /* Allow zero-length header (field) name. + Violates RFC 9110, section 5.1-2 */ + const bool allow_empty_name = (-2 >= discp_lvl); + /* Allow whitespace before colon. + Violates RFC 9112, section 5.1-2 */ + const bool allow_wsp_before_colon = (-3 >= discp_lvl); + /* Do not abort the request when header line has no colon, just skip such + bad lines. + RFC 9112, section 5-1 */ + const bool allow_line_without_colon = (-2 >= discp_lvl); + + size_t p; /**< The position of the currently processed character */ + + (void) process_footers; /* Unused parameter in non-debug and no messages */ + + mhd_assert ((process_footers ? MHD_CONNECTION_FOOTERS_RECEIVING : \ + MHD_CONNECTION_REQ_HEADERS_RECEIVING) == \ + c->state); + + p = c->rq.hdrs.hdr.proc_pos; + + mhd_assert (p <= c->read_buffer_offset); + while (p < c->read_buffer_offset) + { + char *const restrict read_buffer = c->read_buffer; + const char chr = read_buffer[p]; + bool end_of_line; + + mhd_assert ((0 == c->rq.hdrs.hdr.name_len) || \ + (c->rq.hdrs.hdr.name_len < p)); + mhd_assert ((0 == c->rq.hdrs.hdr.name_len) || (0 != p)); + mhd_assert ((0 == c->rq.hdrs.hdr.name_len) || \ + (c->rq.hdrs.hdr.name_end_found)); + mhd_assert ((0 == c->rq.hdrs.hdr.value_start) || \ + (c->rq.hdrs.hdr.name_len < c->rq.hdrs.hdr.value_start)); + mhd_assert ((0 == c->rq.hdrs.hdr.value_start) || \ + (0 != c->rq.hdrs.hdr.name_len)); + mhd_assert ((0 == c->rq.hdrs.hdr.ws_start) || \ + (0 == c->rq.hdrs.hdr.name_len) || \ + (c->rq.hdrs.hdr.ws_start > c->rq.hdrs.hdr.name_len)); + mhd_assert ((0 == c->rq.hdrs.hdr.ws_start) || \ + (0 == c->rq.hdrs.hdr.value_start) || \ + (c->rq.hdrs.hdr.ws_start > c->rq.hdrs.hdr.value_start)); + + /* Check for the end of the line */ + if ('\r' == chr) + { + if (0 != p) + { + /* Line is not empty, need to check for possible line folding */ + if (p + 2 >= c->read_buffer_offset) + break; /* Not enough data yet to check for folded line */ + } + else + { + /* Line is empty, no need to check for possible line folding */ + if (p + 2 > c->read_buffer_offset) + break; /* Not enough data yet to check for the end of the line */ + } + if ('\n' == read_buffer[p + 1]) + end_of_line = true; + else + { + /* Bare CR alone */ + /* Must be rejected or replaced with space char. + See RFC 9112, section 2.2-4 */ + if (bare_cr_as_sp) + { + read_buffer[p] = ' '; + c->rq.num_cr_sp_replaced++; + continue; /* Re-start processing of the current character */ + } + else if (! bare_cr_keep) + { + if (! process_footers) + transmit_error_response_static (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_BARE_CR_IN_HEADER); + else + transmit_error_response_static (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_BARE_CR_IN_FOOTER); + return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */ + } + end_of_line = false; + } + } + else if ('\n' == chr) + { + /* Bare LF may be recognised as a line delimiter. + See RFC 9112, section 2.2-3 */ + if (bare_lf_as_crlf) + { + if (0 != p) + { + /* Line is not empty, need to check for possible line folding */ + if (p + 1 >= c->read_buffer_offset) + break; /* Not enough data yet to check for folded line */ + } + end_of_line = true; + } + else + { + if (! process_footers) + transmit_error_response_static (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_BARE_LF_IN_HEADER); + else + transmit_error_response_static (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_BARE_LF_IN_FOOTER); + return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */ + } + } + else + end_of_line = false; + + if (end_of_line) + { + /* Handle the end of the line */ + /** + * The full length of the line, including CRLF (or bare LF). + */ + const size_t line_len = p + (('\r' == chr) ? 2 : 1); + char next_line_char; + mhd_assert (line_len <= c->read_buffer_offset); + + if (0 == p) + { + /* Zero-length header line. This is the end of the request header + section. + RFC 9112, Section 2.1-1 */ + mhd_assert (! c->rq.hdrs.hdr.starts_with_ws); + mhd_assert (! c->rq.hdrs.hdr.name_end_found); + mhd_assert (0 == c->rq.hdrs.hdr.name_len); + mhd_assert (0 == c->rq.hdrs.hdr.ws_start); + mhd_assert (0 == c->rq.hdrs.hdr.value_start); + /* Consume the line with CRLF (or bare LF) */ + c->read_buffer += line_len; + c->read_buffer_offset -= line_len; + c->read_buffer_size -= line_len; + return MHD_HDR_LINE_READING_GOT_END_OF_HEADER; + } + + mhd_assert (line_len < c->read_buffer_offset); + mhd_assert (0 != line_len); + mhd_assert ('\n' == read_buffer[line_len - 1]); + next_line_char = read_buffer[line_len]; + if ((' ' == next_line_char) || + ('\t' == next_line_char)) + { + /* Folded line */ + if (! allow_folded) + { + if (! process_footers) + transmit_error_response_static (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_OBS_FOLD); + else + transmit_error_response_static (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_OBS_FOLD_FOOTER); + + return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */ + } + /* Replace CRLF (or bare LF) character(s) with space characters. + See RFC 9112, Section 5.2-4 */ + read_buffer[p] = ' '; + if ('\r' == chr) + read_buffer[p + 1] = ' '; + continue; /* Re-start processing of the current character */ + } + else + { + /* It is not a folded line, it's the real end of the non-empty line */ + bool skip_line = false; + mhd_assert (0 != p); + if (c->rq.hdrs.hdr.starts_with_ws) + { + /* This is the first line and it starts with whitespace. This line + must be discarded completely. + See RFC 9112, Section 2.2-8 */ + mhd_assert (allow_wsp_at_start); + + MHD_LOG_MSG (c->daemon, MHD_SC_REQ_FIRST_HEADER_LINE_SPACE_PREFIXED, + "Whitespace-prefixed first header line " \ + "has been skipped."); + skip_line = true; + } + else if (! c->rq.hdrs.hdr.name_end_found) + { + if (! allow_line_without_colon) + { + if (! process_footers) + transmit_error_response_static (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_HEADER_WITHOUT_COLON); + else + transmit_error_response_static (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_FOOTER_WITHOUT_COLON); + + return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */ + } + /* Skip broken line completely */ + c->rq.skipped_broken_lines++; + skip_line = true; + } + if (skip_line) + { + /* Skip the entire line */ + c->read_buffer += line_len; + c->read_buffer_offset -= line_len; + c->read_buffer_size -= line_len; + p = 0; + /* Reset processing state */ + memset (&c->rq.hdrs.hdr, 0, sizeof(c->rq.hdrs.hdr)); + /* Start processing of the next line */ + continue; + } + else + { + /* This line should be valid header line */ + size_t value_len; + mhd_assert ((0 != c->rq.hdrs.hdr.name_len) || allow_empty_name); + + hdr_name->cstr = read_buffer + 0; /* The name always starts at the first character */ + hdr_name->len = c->rq.hdrs.hdr.name_len; + mhd_assert (0 == hdr_name->cstr[hdr_name->len]); + + if (0 == c->rq.hdrs.hdr.value_start) + { + c->rq.hdrs.hdr.value_start = p; + read_buffer[p] = 0; + value_len = 0; + } + else if (0 != c->rq.hdrs.hdr.ws_start) + { + mhd_assert (p > c->rq.hdrs.hdr.ws_start); + mhd_assert (c->rq.hdrs.hdr.ws_start > c->rq.hdrs.hdr.value_start); + read_buffer[c->rq.hdrs.hdr.ws_start] = 0; + value_len = c->rq.hdrs.hdr.ws_start - c->rq.hdrs.hdr.value_start; + } + else + { + mhd_assert (p > c->rq.hdrs.hdr.ws_start); + read_buffer[p] = 0; + value_len = p - c->rq.hdrs.hdr.value_start; + } + hdr_value->cstr = read_buffer + c->rq.hdrs.hdr.value_start; + hdr_value->len = value_len; + mhd_assert (0 == hdr_value->cstr[hdr_value->len]); + /* Consume the entire line */ + c->read_buffer += line_len; + c->read_buffer_offset -= line_len; + c->read_buffer_size -= line_len; + return MHD_HDR_LINE_READING_GOT_HEADER; + } + } + } + else if ((' ' == chr) || ('\t' == chr)) + { + if (0 == p) + { + if (! allow_wsp_at_start) + { + if (! process_footers) + transmit_error_response_static (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_WSP_BEFORE_HEADER); + else + transmit_error_response_static (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_WSP_BEFORE_FOOTER); + return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */ + } + c->rq.hdrs.hdr.starts_with_ws = true; + } + else if ((! c->rq.hdrs.hdr.name_end_found) && + (! c->rq.hdrs.hdr.starts_with_ws)) + { + /* Whitespace in header name / between header name and colon */ + if (allow_wsp_in_name || allow_wsp_before_colon) + { + if (0 == c->rq.hdrs.hdr.ws_start) + c->rq.hdrs.hdr.ws_start = p; + } + else + { + if (! process_footers) + transmit_error_response_static (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_WSP_IN_HEADER_NAME); + else + transmit_error_response_static (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_WSP_IN_FOOTER_NAME); + + return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */ + } + } + else + { + /* Whitespace before/inside/after header (field) value */ + if (0 == c->rq.hdrs.hdr.ws_start) + c->rq.hdrs.hdr.ws_start = p; + } + } + else if (0 == chr) + { + if (! nul_as_sp) + { + if (! process_footers) + transmit_error_response_static (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_INVALID_CHR_IN_HEADER); + else + transmit_error_response_static (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_INVALID_CHR_IN_FOOTER); + + return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */ + } + read_buffer[p] = ' '; + continue; /* Re-start processing of the current character */ + } + else + { + /* Not a whitespace, not the end of the header line */ + mhd_assert ('\r' != chr); + mhd_assert ('\n' != chr); + mhd_assert ('\0' != chr); + if ((! c->rq.hdrs.hdr.name_end_found) && + (! c->rq.hdrs.hdr.starts_with_ws)) + { + /* Processing the header (field) name */ + if (':' == chr) + { + if (0 == c->rq.hdrs.hdr.ws_start) + c->rq.hdrs.hdr.name_len = p; + else + { + mhd_assert (allow_wsp_in_name || allow_wsp_before_colon); + if (! allow_wsp_before_colon) + { + if (! process_footers) + transmit_error_response_static (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_WSP_IN_HEADER_NAME); + else + transmit_error_response_static (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_WSP_IN_FOOTER_NAME); + return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */ + } + c->rq.hdrs.hdr.name_len = c->rq.hdrs.hdr.ws_start; +#ifndef MHD_FAVOR_SMALL_CODE + c->rq.hdrs.hdr.ws_start = 0; /* Not on whitespace anymore */ +#endif /* ! MHD_FAVOR_SMALL_CODE */ + } + if ((0 == c->rq.hdrs.hdr.name_len) && ! allow_empty_name) + { + if (! process_footers) + transmit_error_response_static (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_EMPTY_HEADER_NAME); + else + transmit_error_response_static (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_EMPTY_FOOTER_NAME); + return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */ + } + c->rq.hdrs.hdr.name_end_found = true; + read_buffer[c->rq.hdrs.hdr.name_len] = 0; /* Zero-terminate the name */ + } + else + { + if (0 != c->rq.hdrs.hdr.ws_start) + { + /* End of the whitespace in header (field) name */ + mhd_assert (allow_wsp_in_name || allow_wsp_before_colon); + if (! allow_wsp_in_name) + { + if (! process_footers) + transmit_error_response_static (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_WSP_IN_HEADER_NAME); + else + transmit_error_response_static (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_WSP_IN_FOOTER_NAME); + + return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */ + } +#ifndef MHD_FAVOR_SMALL_CODE + c->rq.hdrs.hdr.ws_start = 0; /* Not on whitespace anymore */ +#endif /* ! MHD_FAVOR_SMALL_CODE */ + } + } + } + else + { + /* Processing the header (field) value */ + if (0 == c->rq.hdrs.hdr.value_start) + c->rq.hdrs.hdr.value_start = p; +#ifndef MHD_FAVOR_SMALL_CODE + c->rq.hdrs.hdr.ws_start = 0; /* Not on whitespace anymore */ +#endif /* ! MHD_FAVOR_SMALL_CODE */ + } +#ifdef MHD_FAVOR_SMALL_CODE + c->rq.hdrs.hdr.ws_start = 0; /* Not on whitespace anymore */ +#endif /* MHD_FAVOR_SMALL_CODE */ + } + p++; + } + c->rq.hdrs.hdr.proc_pos = p; + return MHD_HDR_LINE_READING_NEED_MORE_DATA; /* Not enough data yet */ +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_get_req_headers (struct MHD_Connection *restrict c, bool process_footers) +{ + do + { + struct MHD_String hdr_name; + struct MHD_String hdr_value; + enum mhd_HdrLineReadRes res; + + mhd_assert ((process_footers ? MHD_CONNECTION_FOOTERS_RECEIVING : \ + MHD_CONNECTION_REQ_HEADERS_RECEIVING) == \ + c->state); + +#ifndef NDEBUG + hdr_name.cstr = NULL; + hdr_value.cstr = NULL; +#endif /* ! NDEBUG */ + res = get_req_header (c, process_footers, &hdr_name, &hdr_value); + if (MHD_HDR_LINE_READING_GOT_HEADER == res) + { + mhd_assert ((process_footers ? MHD_CONNECTION_FOOTERS_RECEIVING : \ + MHD_CONNECTION_REQ_HEADERS_RECEIVING) == \ + c->state); + mhd_assert (NULL != hdr_name.cstr); + mhd_assert (NULL != hdr_value.cstr); + /* Values must be zero-terminated and must not have binary zeros */ + mhd_assert (strlen (hdr_name.cstr) == hdr_name.len); + mhd_assert (strlen (hdr_value.cstr) == hdr_value.len); + /* Values must not have whitespaces at the start or at the end */ + mhd_assert ((hdr_name.len == 0) || (hdr_name.cstr[0] != ' ')); + mhd_assert ((hdr_name.len == 0) || (hdr_name.cstr[0] != '\t')); + mhd_assert ((hdr_name.len == 0) || \ + (hdr_name.cstr[hdr_name.len - 1] != ' ')); + mhd_assert ((hdr_name.len == 0) || \ + (hdr_name.cstr[hdr_name.len - 1] != '\t')); + mhd_assert ((hdr_value.len == 0) || (hdr_value.cstr[0] != ' ')); + mhd_assert ((hdr_value.len == 0) || (hdr_value.cstr[0] != '\t')); + mhd_assert ((hdr_value.len == 0) || \ + (hdr_value.cstr[hdr_value.len - 1] != ' ')); + mhd_assert ((hdr_value.len == 0) || \ + (hdr_value.cstr[hdr_value.len - 1] != '\t')); + + if (! mhd_stream_add_field (c, + process_footers ? + MHD_VK_FOOTER : MHD_VK_HEADER, + &hdr_name, + &hdr_value)) + { + size_t add_element_size; + + mhd_assert (hdr_name.cstr < hdr_value.cstr); + + if (!process_footers) + MHD_LOG_MSG (c->daemon, MHD_SC_CONNECTION_POOL_MALLOC_FAILURE_REQ, \ + "Failed to allocate memory in the connection memory " \ + "pool to store header."); + else + MHD_LOG_MSG (c->daemon, MHD_SC_CONNECTION_POOL_MALLOC_FAILURE_REQ, \ + "Failed to allocate memory in the connection memory " \ + "pool to store footer."); + + add_element_size = hdr_value.len + + (size_t) (hdr_value.cstr - hdr_name.cstr); + + if (! process_footers) + handle_req_headers_no_space (c, hdr_name.cstr, add_element_size); + else + handle_req_footers_no_space (c, hdr_name.cstr, add_element_size); + + mhd_assert (MHD_CONNECTION_FULL_REQ_RECEIVED < c->state); + return true; + } + /* Reset processing state */ + reset_rq_header_processing_state (c); + mhd_assert ((process_footers ? MHD_CONNECTION_FOOTERS_RECEIVING : \ + MHD_CONNECTION_REQ_HEADERS_RECEIVING) == \ + c->state); + /* Read the next header (field) line */ + continue; + } + else if (MHD_HDR_LINE_READING_NEED_MORE_DATA == res) + { + mhd_assert ((process_footers ? MHD_CONNECTION_FOOTERS_RECEIVING : \ + MHD_CONNECTION_REQ_HEADERS_RECEIVING) == \ + c->state); + return false; + } + else if (MHD_HDR_LINE_READING_DATA_ERROR == res) + { + mhd_assert ((process_footers ? \ + MHD_CONNECTION_FOOTERS_RECEIVING : \ + MHD_CONNECTION_REQ_HEADERS_RECEIVING) < c->state); + mhd_assert (c->stop_with_error); + mhd_assert (c->discard_request); + return true; + } + mhd_assert (MHD_HDR_LINE_READING_GOT_END_OF_HEADER == res); + break; + } while (1); + + if (1 == c->rq.num_cr_sp_replaced) + { + if (! process_footers) + MHD_LOG_MSG (c->daemon, MHD_SC_REQ_HEADER_CR_REPLACED, \ + "One bare CR character has been replaced with space " \ + "in the request line or in the request headers."); + else + MHD_LOG_MSG (c->daemon, MHD_SC_REQ_FOOTER_CR_REPLACED, \ + "One bare CR character has been replaced with space " \ + "in the request footers."); + } + else if (0 != c->rq.num_cr_sp_replaced) + { + if (! process_footers) + MHD_LOG_PRINT (c->daemon, MHD_SC_REQ_HEADER_CR_REPLACED, \ + MHD_LOG_FMT("%" PRIuFAST64 " bare CR characters have " \ + "been replaced with spaces in the request " \ + "line and/or in the request headers."), \ + (uint_fast64_t) c->rq.num_cr_sp_replaced); + else + MHD_LOG_PRINT (c->daemon, MHD_SC_REQ_HEADER_CR_REPLACED, \ + MHD_LOG_FMT("%" PRIuFAST64 " bare CR characters have " \ + "been replaced with spaces in the request " \ + "footers."), \ + (uint_fast64_t) c->rq.num_cr_sp_replaced); + + + } + if (1 == c->rq.skipped_broken_lines) + { + if (! process_footers) + MHD_LOG_MSG (c->daemon, MHD_SC_REQ_HEADER_LINE_NO_COLON, \ + "One header line without colon has been skipped."); + else + MHD_LOG_MSG (c->daemon, MHD_SC_REQ_FOOTER_LINE_NO_COLON, \ + "One footer line without colon has been skipped."); + } + else if (0 != c->rq.skipped_broken_lines) + { + if (! process_footers) + MHD_LOG_PRINT (c->daemon, MHD_SC_REQ_HEADER_CR_REPLACED, \ + MHD_LOG_FMT("%" PRIu64 " header lines without colons " + "have been skipped."), + (uint_fast64_t) c->rq.skipped_broken_lines); + else + MHD_LOG_PRINT (c->daemon, MHD_SC_REQ_HEADER_CR_REPLACED, \ + MHD_LOG_FMT("%" PRIu64 " footer lines without colons " + "have been skipped."), + (uint_fast64_t) c->rq.skipped_broken_lines); + } + + mhd_assert (c->rq.method < c->read_buffer); + if (! process_footers) + { + c->rq.header_size = (size_t) (c->read_buffer - c->rq.method); + mhd_assert (NULL != c->rq.field_lines.start); + c->rq.field_lines.size = + (size_t) ((c->read_buffer - c->rq.field_lines.start) - 1); + if ('\r' == *(c->read_buffer - 2)) + c->rq.field_lines.size--; + c->state = MHD_CONNECTION_HEADERS_RECEIVED; + + if (mhd_BUF_INC_SIZE > c->read_buffer_size) + { + /* Try to re-use some of the last bytes of the request header */ + /* Do this only if space in the read buffer is limited AND + amount of read ahead data is small. */ + /** + * The position of the terminating NUL after the last character of + * the last header element. + */ + const char *last_elmnt_end; + size_t shift_back_size; + struct mhd_RequestField *header; + header = mhd_DLINKEDL_GET_LAST(&(c->rq), fields); + if (NULL != header) + last_elmnt_end = + header->field.nv.value.cstr + header->field.nv.value.len; + else + last_elmnt_end = c->rq.version + HTTP_VER_LEN; + mhd_assert ((last_elmnt_end + 1) < c->read_buffer); + shift_back_size = (size_t) (c->read_buffer - (last_elmnt_end + 1)); + if (0 != c->read_buffer_offset) + memmove (c->read_buffer - shift_back_size, + c->read_buffer, + c->read_buffer_offset); + c->read_buffer -= shift_back_size; + c->read_buffer_size += shift_back_size; + } + } + else + c->state = MHD_CONNECTION_FOOTERS_RECEIVED; + + return true; +} + + +#ifdef COOKIE_SUPPORT + +/** + * Cookie parsing result + */ +enum _MHD_ParseCookie +{ + MHD_PARSE_COOKIE_OK = MHD_YES, /**< Success or no cookies in headers */ + MHD_PARSE_COOKIE_OK_LAX = 2, /**< Cookies parsed, but workarounds used */ + MHD_PARSE_COOKIE_MALFORMED = -1, /**< Invalid cookie header */ + MHD_PARSE_COOKIE_NO_MEMORY = MHD_NO /**< Not enough memory in the pool */ +}; + + +/** + * Parse the cookies string (see RFC 6265). + * + * Try to parse the cookies string even if it is not strictly formed + * as specified by RFC 6265. + * + * @param str the string to parse, without leading whitespaces + * @param str_len the size of the @a str, not including mandatory + * zero-termination + * @param connection the connection to add parsed cookies + * @return #MHD_PARSE_COOKIE_OK for success, error code otherwise + */ +static MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_CSTR_(2) +MHD_FN_PAR_INOUT_SIZE_(2,1) enum _MHD_ParseCookie +parse_cookies_string (const size_t str_len, + char *restrict str, + struct MHD_Connection *restrict connection) +{ + size_t i; + bool non_strict; + /* Skip extra whitespaces and empty cookies */ + const bool allow_wsp_empty = (0 >= connection->daemon->req_cfg.strictnees); + /* Allow whitespaces around '=' character */ + const bool wsp_around_eq = (-3 >= connection->daemon->req_cfg.strictnees); + /* Allow whitespaces in quoted cookie value */ + const bool wsp_in_quoted = (-2 >= connection->daemon->req_cfg.strictnees); + /* Allow tab as space after semicolon between cookies */ + const bool tab_as_sp = (0 >= connection->daemon->req_cfg.strictnees); + /* Allow no space after semicolon between cookies */ + const bool allow_no_space = (0 >= connection->daemon->req_cfg.strictnees); + + non_strict = false; + i = 0; + while (i < str_len) + { + size_t name_start; + size_t name_len; + size_t value_start; + size_t value_len; + bool val_quoted; + /* Skip any whitespaces and empty cookies */ + while (' ' == str[i] || '\t' == str[i] || ';' == str[i]) + { + if (! allow_wsp_empty) + return MHD_PARSE_COOKIE_MALFORMED; + non_strict = true; + i++; + if (i == str_len) + return non_strict? MHD_PARSE_COOKIE_OK_LAX : MHD_PARSE_COOKIE_OK; + } + /* 'i' must point to the first char of cookie-name */ + name_start = i; + /* Find the end of the cookie-name */ + do + { + const char l = str[i]; + if (('=' == l) || (' ' == l) || ('\t' == l) || ('"' == l) || (',' == l) || + (';' == l) || (0 == l)) + break; + } while (str_len > ++i); + name_len = i - name_start; + /* Skip any whitespaces */ + while (str_len > i && (' ' == str[i] || '\t' == str[i])) + { + if (! wsp_around_eq) + return MHD_PARSE_COOKIE_MALFORMED; + non_strict = true; + i++; + } + if ((str_len == i) || ('=' != str[i]) || (0 == name_len)) + return MHD_PARSE_COOKIE_MALFORMED; /* Incomplete cookie name */ + /* 'i' must point to the '=' char */ + mhd_assert ('=' == str[i]); + i++; + /* Skip any whitespaces */ + while (str_len > i && (' ' == str[i] || '\t' == str[i])) + { + if (! wsp_around_eq) + return MHD_PARSE_COOKIE_MALFORMED; + non_strict = true; + i++; + } + /* 'i' must point to the first char of cookie-value */ + if (str_len == i) + { + value_start = 0; + value_len = 0; +#ifndef NDEBUG + val_quoted = false; /* This assignment used in assert */ +#endif + } + else + { + bool valid_cookie; + val_quoted = ('"' == str[i]); + if (val_quoted) + i++; + value_start = i; + /* Find the end of the cookie-value */ + while (str_len > i) + { + const char l = str[i]; + if ((';' == l) || ('"' == l) || (',' == l) || (';' == l) || + ('\\' == l) || (0 == l)) + break; + if ((' ' == l) || ('\t' == l)) + { + if (! val_quoted) + break; + if (! wsp_in_quoted) + return MHD_PARSE_COOKIE_MALFORMED; + non_strict = true; + } + i++; + } + value_len = i - value_start; + if (val_quoted) + { + if ((str_len == i) || ('"' != str[i])) + return MHD_PARSE_COOKIE_MALFORMED; /* Incomplete cookie value, no closing quote */ + i++; + } + /* Skip any whitespaces */ + if ((str_len > i) && ((' ' == str[i]) || ('\t' == str[i]))) + { + do + { + i++; + } while (str_len > i && (' ' == str[i] || '\t' == str[i])); + /* Whitespace at the end? */ + if (str_len > i) + { + if (! allow_wsp_empty) + return MHD_PARSE_COOKIE_MALFORMED; + non_strict = true; + } + } + if (str_len == i) + valid_cookie = true; + else if (';' == str[i]) + valid_cookie = true; + else + valid_cookie = false; + + if (! valid_cookie) + return MHD_PARSE_COOKIE_MALFORMED; /* Garbage at the end of the cookie value */ + } + mhd_assert (0 != name_len); + str[name_start + name_len] = 0; /* Zero-terminate the name */ + if (0 != value_len) + { + struct MHD_String name; + struct MHD_String value; + mhd_assert (value_start + value_len <= str_len); + name.cstr = str + name_start; + name.len = name_len; + str[value_start + value_len] = 0; /* Zero-terminate the value */ + value.cstr = str + value_start; + value.len = value_len; + if (! mhd_stream_add_field(connection, + MHD_VK_COOKIE, + &name, + &value)) + return MHD_PARSE_COOKIE_NO_MEMORY; + } + else + { + struct MHD_String name; + struct MHD_String value; + name.cstr = str + name_start; + name.len = name_len; + value.cstr = "" + value.len = 0; + if (! mhd_stream_add_field(connection, + MHD_VK_COOKIE, + &name, + &value)) + return MHD_PARSE_COOKIE_NO_MEMORY; + } + if (str_len > i) + { + mhd_assert (0 == str[i] || ';' == str[i]); + mhd_assert (! val_quoted || ';' == str[i]); + mhd_assert (';' != str[i] || val_quoted || non_strict || 0 == value_len); + i++; + if (str_len == i) + { /* No next cookie after semicolon */ + if (! allow_wsp_empty) + return MHD_PARSE_COOKIE_MALFORMED; + non_strict = true; + } + else if (' ' != str[i]) + {/* No space after semicolon */ + if (('\t' == str[i]) && tab_as_sp) + i++; + else if (! allow_no_space) + return MHD_PARSE_COOKIE_MALFORMED; + non_strict = true; + } + else + { + i++; + if (str_len == i) + { + if (! allow_wsp_empty) + return MHD_PARSE_COOKIE_MALFORMED; + non_strict = true; + } + } + } + } + return non_strict? MHD_PARSE_COOKIE_OK_LAX : MHD_PARSE_COOKIE_OK; +} + + +/** + * Parse the cookie header (see RFC 6265). + * + * @param connection connection to parse header of + * @return #MHD_PARSE_COOKIE_OK for success, error code otherwise + */ +static enum _MHD_ParseCookie +parse_cookie_header (struct MHD_Connection *connection) +{ + struct MHD_String *hvalue; + char *cpy; + size_t i; + enum _MHD_ParseCookie parse_res; + struct mhd_RequestField *const saved_tail = + connection->rq.fields.last; + const bool allow_partially_correct_cookie = + (1 >= connection->daemon->req_cfg.strictnees); + + hvalue = mhd_request_get_value_len(&(connection->rq), + MHD_VK_HEADER, + mhd_SSTR_LEN (MHD_HTTP_HEADER_COOKIE), + MHD_HTTP_HEADER_COOKIE); + if (NULL == hvalue) + return MHD_PARSE_COOKIE_OK; + if (0 == hvalue->len) + return MHD_PARSE_COOKIE_OK; + + cpy = mhd_stream_alloc_memory (connection, + hvalue->len + 1); + if (NULL == cpy) + parse_res = MHD_PARSE_COOKIE_NO_MEMORY; + else + { + memcpy (cpy, + hdr, + hdr_len); + cpy[hdr_len] = '\0'; + + i = 0; + /* Skip all initial whitespaces */ + while (i < hdr_len && (' ' == cpy[i] || '\t' == cpy[i])) + i++; + + parse_res = parse_cookies_string (cpy + i, hdr_len - i, connection); + } + + switch (parse_res) + { + case MHD_PARSE_COOKIE_OK: + break; + case MHD_PARSE_COOKIE_OK_LAX: +#ifdef HAVE_MESSAGES + if (saved_tail != connection->rq.headers_received_tail) + MHD_DLOG (connection->daemon, + _ ("The Cookie header has been parsed, but it is not fully " + "compliant with the standard.\n")); +#endif /* HAVE_MESSAGES */ + break; + case MHD_PARSE_COOKIE_MALFORMED: + if (saved_tail != connection->rq.headers_received_tail) + { + if (! allow_partially_correct_cookie) + { + /* Remove extracted values from partially broken cookie */ + /* Memory remains allocated until the end of the request processing */ + connection->rq.headers_received_tail = saved_tail; + saved_tail->next = NULL; +#ifdef HAVE_MESSAGES + MHD_DLOG (connection->daemon, + _ ("The Cookie header has been ignored as it contains " + "malformed data.\n")); +#endif /* HAVE_MESSAGES */ + } +#ifdef HAVE_MESSAGES + else + MHD_DLOG (connection->daemon, + _ ("The Cookie header has been only partially parsed as it " + "contains malformed data.\n")); +#endif /* HAVE_MESSAGES */ + } +#ifdef HAVE_MESSAGES + else + MHD_DLOG (connection->daemon, + _ ("The Cookie header has malformed data.\n")); +#endif /* HAVE_MESSAGES */ + break; + case MHD_PARSE_COOKIE_NO_MEMORY: +#ifdef HAVE_MESSAGES + MHD_DLOG (connection->daemon, + _ ("Not enough memory in the connection pool to " + "parse client cookies!\n")); +#endif /* HAVE_MESSAGES */ + break; + default: + mhd_assert (0); + break; + } +#ifndef HAVE_MESSAGES + (void) saved_tail; /* Mute compiler warning */ +#endif /* ! HAVE_MESSAGES */ + + return parse_res; +} + + +#endif /* COOKIE_SUPPORT */ + + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_parse_connection_headers (struct MHD_Connection *restrict c) +{ + const char *clen; + const char *enc; + size_t val_len; + +#ifdef COOKIE_SUPPORT + if (MHD_PARSE_COOKIE_NO_MEMORY == parse_cookie_header (connection)) + { + handle_req_cookie_no_space (connection); + return; + } +#endif /* COOKIE_SUPPORT */ + if ( (-3 < connection->daemon->client_discipline) && + (MHD_IS_HTTP_VER_1_1_COMPAT (connection->rq.http_ver)) && + (MHD_NO == + MHD_lookup_connection_value_n (connection, + MHD_VK_HEADER, + MHD_HTTP_HEADER_HOST, + mhd_SSTR_LEN ( + MHD_HTTP_HEADER_HOST), + NULL, + NULL)) ) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (connection->daemon, + _ ("Received HTTP/1.1 request without `Host' header.\n")); +#endif + transmit_error_response_static (connection, + MHD_HTTP_STATUS_BAD_REQUEST, + REQUEST_LACKS_HOST); + return; + } + + /* The presence of the request body is indicated by "Content-Length:" or + "Transfer-Encoding:" request headers. + Unless one of these two headers is used, the request has no request body. + See RFC9112, Section 6, paragraph 4. */ + connection->rq.remaining_upload_size = 0; + if (MHD_NO != + MHD_lookup_connection_value_n (connection, + MHD_VK_HEADER, + MHD_HTTP_HEADER_TRANSFER_ENCODING, + mhd_SSTR_LEN ( + MHD_HTTP_HEADER_TRANSFER_ENCODING), + &enc, + NULL)) + { + if (! MHD_str_equal_caseless_ (enc, + "chunked")) + { + transmit_error_response_static (connection, + MHD_HTTP_STATUS_BAD_REQUEST, + REQUEST_UNSUPPORTED_TR_ENCODING); + return; + } + else if (MHD_NO != + MHD_lookup_connection_value_n (connection, + MHD_VK_HEADER, + MHD_HTTP_HEADER_CONTENT_LENGTH, + mhd_SSTR_LEN ( \ + MHD_HTTP_HEADER_CONTENT_LENGTH), + NULL, + NULL)) + { + /* TODO: add individual settings */ + if (1 <= connection->daemon->client_discipline) + { + transmit_error_response_static (connection, + MHD_HTTP_STATUS_BAD_REQUEST, + REQUEST_LENGTH_WITH_TR_ENCODING); + return; + } + else + { + /* Must close connection after reply to prevent potential attack */ + connection->keepalive = MHD_CONN_MUST_CLOSE; +#ifdef HAVE_MESSAGES + MHD_DLOG (connection->daemon, + _ ("The 'Content-Length' request header is ignored " + "as chunked Transfer-Encoding is used " + "for this request.\n")); +#endif /* HAVE_MESSAGES */ + } + } + connection->rq.have_chunked_upload = true; + connection->rq.remaining_upload_size = MHD_SIZE_UNKNOWN; + } + else if (MHD_NO != + MHD_lookup_connection_value_n (connection, + MHD_VK_HEADER, + MHD_HTTP_HEADER_CONTENT_LENGTH, + mhd_SSTR_LEN ( + MHD_HTTP_HEADER_CONTENT_LENGTH), + &clen, + &val_len)) + { + size_t num_digits; + + num_digits = MHD_str_to_uint64_n_ (clen, + val_len, + &connection->rq.remaining_upload_size); + + if (((0 == num_digits) && + (0 != val_len) && + ('0' <= clen[0]) && ('9' >= clen[0])) + || (MHD_SIZE_UNKNOWN == connection->rq.remaining_upload_size)) + { + connection->rq.remaining_upload_size = 0; +#ifdef HAVE_MESSAGES + MHD_DLOG (connection->daemon, + _ ("Too large value of 'Content-Length' header. " \ + "Closing connection.\n")); +#endif + transmit_error_response_static (connection, + MHD_HTTP_STATUS_CONTENT_TOO_LARGE, + REQUEST_CONTENTLENGTH_TOOLARGE); + } + else if ((val_len != num_digits) || + (0 == num_digits)) + { + connection->rq.remaining_upload_size = 0; +#ifdef HAVE_MESSAGES + MHD_DLOG (connection->daemon, + _ ("Failed to parse 'Content-Length' header. " \ + "Closing connection.\n")); +#endif + transmit_error_response_static (connection, + MHD_HTTP_STATUS_BAD_REQUEST, + REQUEST_CONTENTLENGTH_MALFORMED); + } + } +} diff --git a/src/mhd2/stream_process_request.h b/src/mhd2/stream_process_request.h new file mode 100644 index 00000000..e71a39b7 --- /dev/null +++ b/src/mhd2/stream_process_request.h @@ -0,0 +1,81 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/stream_process_request.h + * @brief The declarations of internal functions for requests parsing + * and processing + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_STREAM_PROCESS_REQUEST_H +#define MHD_STREAM_PROCESS_STATES_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" + +struct MHD_Connection; /* forward declaration */ + +/** + * Find and parse the request line. + * @param c the connection to process + * @return true if request line completely processed (or unrecoverable error + * found) and state is changed, + * false if not enough data yet in the receive buffer + */ +MHD_INTERNAL bool +mhd_get_request_line (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Switch to request headers (field lines) processing state. + * @param c the connection to process + */ +MHD_INTERNAL void +mhd_switch_to_rq_headers_processing (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Find the end of the request headers and make basic header parsing. + * Advance to the next state when done, handle errors. + * @param c the connection to process + * @param process_footers if true then footers are processed, + * if false then headers are processed + * @return true if request headers reading finished (either successfully + * or with error), + * false if not enough data yet in the receive buffer + */ +MHD_INTERNAL bool +mhd_get_req_headers (struct MHD_Connection *restrict c, bool process_footers) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Parse the various headers; figure out the size + * of the upload and make sure the headers follow + * the protocol. Advance to the appropriate state. + * + * @param c the connection to process + */ +MHD_INTERNAL void +mhd_parse_connection_headers (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + +#endif /* ! MHD_STREAM_PROCESS_STATES_H */ diff --git a/src/mhd2/stream_process_states.c b/src/mhd2/stream_process_states.c new file mode 100644 index 00000000..32b3d22e --- /dev/null +++ b/src/mhd2/stream_process_states.c @@ -0,0 +1,359 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/stream_process_states.h + * @brief The definitions of internal functions for processing + * stream states + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" +#include "sys_bool_type.h" + +#include "mhd_connection.h" +#include "stream_process_states.h" + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_conn_process_data (struct MHD_Connection *restrict c) +{ + struct MHD_Daemon *const restrict d = c->daemon; + + enum MHD_Result ret; + + /* 'daemon' is not used if epoll is not available and asserts are disabled */ + (void) d; /* Mute compiler warning */ + + if ((mhd_SOCKET_ERR_NO_ERROR != c->sk_discnt_err) || + (0 != (c->sk_ready & mhd_SOCKET_NET_STATE_ERROR_READY))) + { + mhd_assert ((mhd_SOCKET_ERR_NO_ERROR != c->sk_discnt_err) || \ + mhd_SOCKET_ERR_IS_HARD(c->sk_discnt_err)); + if ((mhd_SOCKET_ERR_NO_ERROR == c->sk_discnt_err) || + (mhd_SOCKET_ERR_NOT_CHECKED == c->sk_discnt_err)) + c->sk_discnt_err = mhd_socket_error_get_from_socket(c->socket_fd); + mhd_conn_pre_close_skt_err (c); + return false; + } + + while (true) // TODO: support suspend + { +#ifdef HTTPS_SUPPORT + // TODO: support TLS, handshake +#endif /* HTTPS_SUPPORT */ + switch (c->state) + { + case MHD_CONNECTION_INIT: + case MHD_CONNECTION_REQ_LINE_RECEIVING: + if (mhd_get_request_line (c)) + { + mhd_assert (MHD_CONNECTION_REQ_LINE_RECEIVING < c->state); + mhd_assert ((MHD_IS_HTTP_VER_SUPPORTED (c->rq.http_ver)) \ + || (c->discard_request)); + continue; + } + mhd_assert (MHD_CONNECTION_REQ_LINE_RECEIVING >= c->state); + break; + case MHD_CONNECTION_REQ_LINE_RECEIVED: + mhd_switch_to_rq_headers_processing (c); + mhd_assert (MHD_CONNECTION_REQ_LINE_RECEIVED != c->state); + continue; + case MHD_CONNECTION_REQ_HEADERS_RECEIVING: + if (mhd_get_req_headers (c, false)) + { + mhd_assert (MHD_CONNECTION_REQ_HEADERS_RECEIVING < c->state); + mhd_assert ((MHD_CONNECTION_HEADERS_RECEIVED == c->state) || \ + (c->discard_request)); + continue; + } + mhd_assert (MHD_CONNECTION_REQ_HEADERS_RECEIVING == c->state); + break; + case MHD_CONNECTION_HEADERS_RECEIVED: + mhd_parse_connection_headers (c); + if (MHD_CONNECTION_HEADERS_RECEIVED != c->state) + continue; + c->state = MHD_CONNECTION_HEADERS_PROCESSED; + if (c->suspended) + break; + continue; + case MHD_CONNECTION_HEADERS_PROCESSED: + call_connection_handler (c); + if (MHD_CONNECTION_HEADERS_PROCESSED != c->state) + continue; + if (c->suspended) + continue; + + if ( (NULL == c->rp.response) && + (need_100_continue (c)) && + /* If the client is already sending the payload (body) + there is no need to send "100 Continue" */ + (0 == c->read_buffer_offset) ) + { + c->state = MHD_CONNECTION_CONTINUE_SENDING; + break; + } + if ( (NULL != c->rp.response) && + (0 != c->rq.remaining_upload_size) ) + { + /* we refused (no upload allowed!) */ + c->rq.remaining_upload_size = 0; + /* force close, in case client still tries to upload... */ + c->discard_request = true; + } + c->state = (0 == c->rq.remaining_upload_size) + ? MHD_CONNECTION_FULL_REQ_RECEIVED + : MHD_CONNECTION_BODY_RECEIVING; + if (c->suspended) + break; + continue; + case MHD_CONNECTION_CONTINUE_SENDING: + if (c->continue_message_write_offset == + mhd_SSTR_LEN (HTTP_100_CONTINUE)) + { + c->state = MHD_CONNECTION_BODY_RECEIVING; + continue; + } + break; + case MHD_CONNECTION_BODY_RECEIVING: + mhd_assert (0 != c->rq.remaining_upload_size); + mhd_assert (! c->discard_request); + mhd_assert (NULL == c->rp.response); + if (0 != c->read_buffer_offset) + { + process_request_body (c); /* loop call */ + if (MHD_CONNECTION_BODY_RECEIVING != c->state) + continue; + } + /* Modify here when queueing of the response during data processing + will be supported */ + mhd_assert (! c->discard_request); + mhd_assert (NULL == c->rp.response); + if (0 == c->rq.remaining_upload_size) + { + c->state = MHD_CONNECTION_BODY_RECEIVED; + continue; + } + break; + case MHD_CONNECTION_BODY_RECEIVED: + mhd_assert (! c->discard_request); + mhd_assert (NULL == c->rp.response); + if (0 == c->rq.remaining_upload_size) + { + if (c->rq.have_chunked_upload) + { + /* Reset counter variables reused for footers */ + c->rq.num_cr_sp_replaced = 0; + c->rq.skipped_broken_lines = 0; + reset_rq_header_processing_state (c); + c->state = MHD_CONNECTION_FOOTERS_RECEIVING; + } + else + c->state = MHD_CONNECTION_FULL_REQ_RECEIVED; + continue; + } + break; + case MHD_CONNECTION_FOOTERS_RECEIVING: + if (get_req_headers (c, true)) + { + mhd_assert (MHD_CONNECTION_FOOTERS_RECEIVING < c->state); + mhd_assert ((MHD_CONNECTION_FOOTERS_RECEIVED == c->state) || \ + (c->discard_request)); + continue; + } + mhd_assert (MHD_CONNECTION_FOOTERS_RECEIVING == c->state); + break; + case MHD_CONNECTION_FOOTERS_RECEIVED: + /* The header, the body, and the footers of the request has been received, + * switch to the final processing of the request. */ + c->state = MHD_CONNECTION_FULL_REQ_RECEIVED; + continue; + case MHD_CONNECTION_FULL_REQ_RECEIVED: + call_connection_handler (c); /* "final" call */ + if (c->state != MHD_CONNECTION_FULL_REQ_RECEIVED) + continue; + if (NULL == c->rp.response) + break; /* try again next time */ + /* Response is ready, start reply */ + c->state = MHD_CONNECTION_START_REPLY; + continue; + case MHD_CONNECTION_START_REPLY: + mhd_assert (NULL != c->rp.response); + connection_switch_from_recv_to_send (c); + if (MHD_NO == build_header_response (c)) + { + /* oops - close! */ + CONNECTION_CLOSE_ERROR (c, + _ ("Closing connection (failed to create " + "response header).\n")); + continue; + } + c->state = MHD_CONNECTION_HEADERS_SENDING; + break; + + case MHD_CONNECTION_HEADERS_SENDING: + /* no default action */ + break; + case MHD_CONNECTION_HEADERS_SENT: + mhd_assert (0 && "Not implemented yet"); +#if 0 //def UPGRADE_SUPPORT // TODO: upgrade support + if (NULL != c->rp.response->upgrade_handler) + { + c->state = MHD_CONNECTION_UPGRADE; + /* This connection is "upgraded". Pass socket to application. */ + if (MHD_NO == + MHD_response_execute_upgrade_ (c->rp.response, + connection)) + { + /* upgrade failed, fail hard */ + CONNECTION_CLOSE_ERROR (connection, + NULL); + continue; + } + /* Response is not required anymore for this connection. */ + if (1) + { + struct MHD_Response *const resp = c->rp.response; + + c->rp.response = NULL; + MHD_destroy_response (resp); + } + continue; + } +#endif /* UPGRADE_SUPPORT */ + + if (c->rp.props.send_reply_body) + { + if (c->rp.props.chunked) + c->state = MHD_CONNECTION_CHUNKED_BODY_UNREADY; + else + c->state = MHD_CONNECTION_NORMAL_BODY_UNREADY; + } + else + c->state = MHD_CONNECTION_FULL_REPLY_SENT; + continue; + case MHD_CONNECTION_NORMAL_BODY_READY: + mhd_assert (c->rp.props.send_reply_body); + mhd_assert (! c->rp.props.chunked); + /* nothing to do here */ + break; + case MHD_CONNECTION_NORMAL_BODY_UNREADY: + mhd_assert (c->rp.props.send_reply_body); + mhd_assert (! c->rp.props.chunked); + if (0 == c->rp.response->cntn_size) + { + if (c->rp.props.chunked) + c->state = MHD_CONNECTION_CHUNKED_BODY_SENT; + else + c->state = MHD_CONNECTION_FULL_REPLY_SENT; + continue; + } + if (MHD_NO != try_ready_normal_body (c)) + { + c->state = MHD_CONNECTION_NORMAL_BODY_READY; + /* Buffering for flushable socket was already enabled*/ + + break; + } + /* mutex was already unlocked by "try_ready_normal_body */ + /* not ready, no socket action */ + break; + case MHD_CONNECTION_CHUNKED_BODY_READY: + mhd_assert (c->rp.props.send_reply_body); + mhd_assert (c->rp.props.chunked); + /* nothing to do here */ + break; + case MHD_CONNECTION_CHUNKED_BODY_UNREADY: + mhd_assert (c->rp.props.send_reply_body); + mhd_assert (c->rp.props.chunked); + if ( (0 == c->rp.response->cntn_size) || + (c->rp.rsp_cntn_read_pos == + c->rp.response->cntn_size) ) + { + c->state = MHD_CONNECTION_CHUNKED_BODY_SENT; + continue; + } + if (1) + { /* pseudo-branch for local variables scope */ + bool finished; + if (MHD_NO != try_ready_chunked_body (c, &finished)) + { + c->state = finished ? MHD_CONNECTION_CHUNKED_BODY_SENT : + MHD_CONNECTION_CHUNKED_BODY_READY; + continue; + } + /* mutex was already unlocked by try_ready_chunked_body */ + } + break; + case MHD_CONNECTION_CHUNKED_BODY_SENT: + mhd_assert (c->rp.props.send_reply_body); + mhd_assert (c->rp.props.chunked); + mhd_assert (c->write_buffer_send_offset <= \ + c->write_buffer_append_offset); + + if (MHD_NO == build_connection_chunked_response_footer (c)) + { + /* oops - close! */ + CONNECTION_CLOSE_ERROR (c, + _ ("Closing connection (failed to create " \ + "response footer).")); + continue; + } + mhd_assert (c->write_buffer_send_offset < \ + c->write_buffer_append_offset); + c->state = MHD_CONNECTION_FOOTERS_SENDING; + continue; + case MHD_CONNECTION_FOOTERS_SENDING: + mhd_assert (c->rp.props.send_reply_body); + mhd_assert (c->rp.props.chunked); + /* no default action */ + break; + case MHD_CONNECTION_FULL_REPLY_SENT: + // FIXME: support MHD_HTTP_STATUS_PROCESSING ? + /* Reset connection after complete reply */ + connection_reset (c, + MHD_CONN_USE_KEEPALIVE == c->keepalive && + ! c->read_closed && + ! c->discard_request); + continue; + case MHD_CONNECTION_CLOSED: + cleanup_connection (c); + return MHD_NO; +#if 0 // def UPGRADE_SUPPORT + case MHD_CONNECTION_UPGRADE: + return MHD_YES; /* keep open */ +#endif /* UPGRADE_SUPPORT */ + default: + mhd_assert (0); + break; + } + break; + } + if (connection_check_timedout (c)) + { + MHD_connection_close_ (c, + MHD_REQUEST_TERMINATED_TIMEOUT_REACHED); + return MHD_YES; + } + mhd_conn_update_active_state (c); + /* MHD_connection_update_event_loop_info (c);*/ + + return true; +} diff --git a/src/mhd2/stream_process_states.h b/src/mhd2/stream_process_states.h new file mode 100644 index 00000000..b33a37a9 --- /dev/null +++ b/src/mhd2/stream_process_states.h @@ -0,0 +1,48 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/stream_process_states.h + * @brief The declarations of internal functions for processing + * stream states + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_STREAM_PROCESS_STATES_H +#define MHD_STREAM_PROCESS_STATES_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" + +struct MHD_Connection; /* forward declaration */ + +/** + * Process states and the data for the connection + * For HTTP/1.1 connection is equal stream + * @param c the connection to process + * @return true if states and data has been successfully processed, + * false if connection needs to be closed + */ +MHD_INTERNAL bool +mhd_conn_process_data (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + +#endif /* ! MHD_STREAM_PROCESS_STATES_H */ diff --git a/src/mhd2/sys_base_types.h b/src/mhd2/sys_base_types.h index 41e71e5d..618a38cb 100644 --- a/src/mhd2/sys_base_types.h +++ b/src/mhd2/sys_base_types.h @@ -48,6 +48,9 @@ #ifdef HAVE_CRTDEFS_H # include /* W32-specific header */ #endif +#ifdef HAVE_INTTYPES_H +# include +#endif #ifndef HAVE_SSIZE_T # if defined(HAVE_PTRDIFF_T) @@ -65,4 +68,12 @@ typedef intptr_t ssize_t; # endif #endif /* ! HAVE_SSIZE_T */ +#ifndef PRIuFAST64 +# ifdef PRIu64 +# define PRIuFAST64 PRIu64 +# else +# define PRIuFAST64 "llu" +# endif +#endif + #endif /* ! MHD_SYS_BASE_TYPES_H */ diff --git a/src/mhd2/sys_ip_headers.h b/src/mhd2/sys_ip_headers.h index 9d659429..d0138963 100644 --- a/src/mhd2/sys_ip_headers.h +++ b/src/mhd2/sys_ip_headers.h @@ -53,4 +53,63 @@ # include #endif +#ifdef IPPROTO_TCP +# if defined(TCP_CORK) +/** + * Value of TCP_CORK or TCP_NOPUSH + */ +# define mhd_TCP_CORK_NOPUSH TCP_CORK +# elif defined(TCP_NOPUSH) +/** + * Value of TCP_CORK or TCP_NOPUSH + */ +# define mhd_TCP_CORK_NOPUSH TCP_NOPUSH +# endif /* TCP_NOPUSH */ +#endif /* IPPROTO_TCP */ + +#ifdef mhd_TCP_CORK_NOPUSH +# ifdef __linux__ +/** + * Indicate that reset of TCP_CORK / TCP_NOPUSH push data to the network + */ +# define mhd_CORK_RESET_PUSH_DATA 1 +/** + * Indicate that reset of TCP_CORK / TCP_NOPUSH push data to the network + * even if TCP_CORK/TCP_NOPUSH was in switched off state. + */ +# define mhd_CORK_RESET_PUSH_DATA_ALWAYS 1 +#endif /* __linux__ */ +#if (defined(__FreeBSD__) && \ + ((__FreeBSD__ + 0) >= 5 || (__FreeBSD_version + 0) >= 450000)) || \ + (defined(__FreeBSD_kernel_version) && \ + (__FreeBSD_kernel_version + 0) >= 450000) +/* FreeBSD pushes data to the network with reset of TCP_NOPUSH + * starting from version 4.5. */ +/** + * Indicate that reset of TCP_CORK / TCP_NOPUSH push data to the network + */ +#define mhd_CORK_RESET_PUSH_DATA 1 +#endif /* __FreeBSD_version >= 450000 */ +#ifdef __OpenBSD__ +/* OpenBSD took implementation from FreeBSD */ +/** + * Indicate that reset of TCP_CORK / TCP_NOPUSH push data to the network + */ +#define mhd_CORK_RESET_PUSH_DATA 1 +#endif /* __OpenBSD__ */ +#endif /* MHD_TCP_CORK_NOPUSH */ + +#ifdef __linux__ +/** + * Indicate that set of TCP_NODELAY push data to the network + */ +# define mhd_NODELAY_SET_PUSH_DATA 1 +/** + * Indicate that set of TCP_NODELAY push data to the network even + * if TCP_DELAY was already set and regardless of TCP_CORK / TCP_NOPUSH state + */ +# define mhd_NODELAY_SET_PUSH_DATA_ALWAYS 1 +#endif /* __linux__ */ + + #endif /* ! MHD_SYS_IP_HEADERS_H */ diff --git a/src/mhd2/sys_sockets_headers.h b/src/mhd2/sys_sockets_headers.h index 03488a9d..93f6c46c 100644 --- a/src/mhd2/sys_sockets_headers.h +++ b/src/mhd2/sys_sockets_headers.h @@ -49,33 +49,45 @@ #endif #if defined(HAVE_SOCK_NONBLOCK) && ! defined(MHD_WINSOCK_SOCKETS) -# define MHD_SOCK_NONBLOCK SOCK_NONBLOCK +# define mhd_SOCK_NONBLOCK SOCK_NONBLOCK #else -# define MHD_SOCK_NONBLOCK (0) +# define mhd_SOCK_NONBLOCK (0) #endif #if defined(SOCK_CLOEXEC) && ! defined(MHD_WINSOCK_SOCKETS) -# define MHD_SOCK_CLOEXEC SOCK_CLOEXEC +# define mhd_SOCK_CLOEXEC SOCK_CLOEXEC #else -# define MHD_SOCK_CLOEXEC (0) +# define mhd_SOCK_CLOEXEC (0) #endif #if defined(SOCK_NOSIGPIPE) && ! defined(MHD_WINSOCK_SOCKETS) -# define MHD_SOCK_NOSIGPIPE SOCK_NOSIGPIPE +# define mhd_SOCK_NOSIGPIPE SOCK_NOSIGPIPE #else -# define MHD_SOCK_NOSIGPIPE (0) +# define mhd_SOCK_NOSIGPIPE (0) #endif #if defined(MSG_NOSIGNAL) && ! defined(MHD_WINSOCK_SOCKETS) -# define MHD_MSG_NOSIGNAL MSG_NOSIGNAL +# define mhd_MSG_NOSIGNAL MSG_NOSIGNAL #else -# define MHD_MSG_NOSIGNAL (0) +# define mhd_MSG_NOSIGNAL (0) #endif -#if defined(MSG_NOSIGNAL) && ! defined(MHD_WINSOCK_SOCKETS) -# define MHD_MSG_NOSIGNAL MSG_MORE +#ifdef MSG_MORE +# ifdef __linux__ +/* MSG_MORE signal kernel to buffer outbond data and works like + * TCP_CORK per call without actually setting TCP_CORK value. + * It's known to work on Linux. Add more OSes if they are compatible. */ +/** + * Indicate MSG_MORE is usable for buffered send(). + */ +# define mhd_USE_MSG_MORE 1 +# endif /* __linux__ */ +#endif /* MSG_MORE */ + +#ifdef mhd_USE_MSG_MORE +# define mhd_MSG_MORE MSG_MORE #else -# define MHD_MSG_NOSIGNAL (0) +# define mhd_MSG_MORE (0) #endif @@ -128,4 +140,26 @@ typedef int mhd_SCKT_SEND_SIZE; # define MHD_ACCEPTED_DOES_NOT_INHERIT_NONBLOCK 1 #endif + +#if defined(MHD_socket_nosignal_) || \ + (defined(SOL_SOCKET) && defined(SO_NOSIGPIPE)) +/** + * Indicate that SIGPIPE can be suppressed by MHD for normal send() by flags + * or socket options. + * If this macro is undefined, MHD cannot suppress SIGPIPE for socket functions + * so sendfile() or writev() calls are avoided in application threads. + */ +# define mhd_SEND_SPIPE_SUPPRESS_POSSIBLE 1 +#endif /* MHD_WINSOCK_SOCKETS || MHD_socket_nosignal_ || MSG_NOSIGNAL */ + + +#if ! defined(MHD_WINSOCK_SOCKETS) +/** + * Indicate that suppression of SIGPIPE is required for some network + * system calls. + */ +# define mhd_SEND_SPIPE_SUPPRESS_NEEDED 1 +#endif + + #endif /* ! MHD_SYS_SOCKETS_HEADERS_H */ diff --git a/src/microhttpd/mhd_limits.h b/src/microhttpd/mhd_limits.h deleted file mode 100644 index 84551a4a..00000000 --- a/src/microhttpd/mhd_limits.h +++ /dev/null @@ -1,156 +0,0 @@ -/* - This file is part of libmicrohttpd - Copyright (C) 2015-2022 Karlson2k (Evgeny Grin) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -/** - * @file microhttpd/mhd_limits.h - * @brief limits values definitions - * @author Karlson2k (Evgeny Grin) - */ - -#ifndef MHD_LIMITS_H -#define MHD_LIMITS_H - -#include "platform.h" - -#ifdef HAVE_LIMITS_H -#include -#endif /* HAVE_LIMITS_H */ - -#define MHD_UNSIGNED_TYPE_MAX_(type) ((type) - 1) -/* Assume 8 bits per byte, no padding bits. */ -#define MHD_SIGNED_TYPE_MAX_(type) \ - ( (type) ((( ((type) 1) << (sizeof(type) * 8 - 2)) - 1) * 2 + 1) ) -#define MHD_TYPE_IS_SIGNED_(type) (((type) 0)>((type) - 1)) - -#ifndef INT_MAX -#ifdef __INT_MAX__ -#define INT_MAX __INT_MAX__ -#else /* ! __UINT_MAX__ */ -#define INT_MAX MHD_SIGNED_TYPE_MAX_ (int) -#endif /* ! __UINT_MAX__ */ -#endif /* !UINT_MAX */ - -#ifndef UINT_MAX -#ifdef __UINT_MAX__ -#define UINT_MAX __UINT_MAX__ -#else /* ! __UINT_MAX__ */ -#define UINT_MAX MHD_UNSIGNED_TYPE_MAX_ (unsigned int) -#endif /* ! __UINT_MAX__ */ -#endif /* !UINT_MAX */ - -#ifndef LONG_MAX -#ifdef __LONG_MAX__ -#define LONG_MAX __LONG_MAX__ -#else /* ! __LONG_MAX__ */ -#define LONG_MAX MHD_SIGNED_TYPE_MAX (long) -#endif /* ! __LONG_MAX__ */ -#endif /* !LONG_MAX */ - -#ifndef ULLONG_MAX -#ifdef ULONGLONG_MAX -#define ULLONG_MAX ULONGLONG_MAX -#else /* ! ULONGLONG_MAX */ -#define ULLONG_MAX MHD_UNSIGNED_TYPE_MAX_ (MHD_UNSIGNED_LONG_LONG) -#endif /* ! ULONGLONG_MAX */ -#endif /* !ULLONG_MAX */ - -#ifndef INT32_MAX -#ifdef __INT32_MAX__ -#define INT32_MAX __INT32_MAX__ -#else /* ! __INT32_MAX__ */ -#define INT32_MAX ((int32_t) 0x7FFFFFFF) -#endif /* ! __INT32_MAX__ */ -#endif /* !INT32_MAX */ - -#ifndef UINT32_MAX -#ifdef __UINT32_MAX__ -#define UINT32_MAX __UINT32_MAX__ -#else /* ! __UINT32_MAX__ */ -#define UINT32_MAX ((int32_t) 0xFFFFFFFF) -#endif /* ! __UINT32_MAX__ */ -#endif /* !UINT32_MAX */ - -#ifndef UINT64_MAX -#ifdef __UINT64_MAX__ -#define UINT64_MAX __UINT64_MAX__ -#else /* ! __UINT64_MAX__ */ -#define UINT64_MAX ((uint64_t) 0xFFFFFFFFFFFFFFFF) -#endif /* ! __UINT64_MAX__ */ -#endif /* !UINT64_MAX */ - -#ifndef INT64_MAX -#ifdef __INT64_MAX__ -#define INT64_MAX __INT64_MAX__ -#else /* ! __INT64_MAX__ */ -#define INT64_MAX ((int64_t) 0x7FFFFFFFFFFFFFFF) -#endif /* ! __UINT64_MAX__ */ -#endif /* !INT64_MAX */ - -#ifndef SIZE_MAX -#ifdef __SIZE_MAX__ -#define SIZE_MAX __SIZE_MAX__ -#elif defined(UINTPTR_MAX) -#define SIZE_MAX UINTPTR_MAX -#else /* ! __SIZE_MAX__ */ -#define SIZE_MAX MHD_UNSIGNED_TYPE_MAX_ (size_t) -#endif /* ! __SIZE_MAX__ */ -#endif /* !SIZE_MAX */ - -#ifndef SSIZE_MAX -#ifdef __SSIZE_MAX__ -#define SSIZE_MAX __SSIZE_MAX__ -#elif defined(INTPTR_MAX) -#define SSIZE_MAX INTPTR_MAX -#else -#define SSIZE_MAX MHD_SIGNED_TYPE_MAX_ (ssize_t) -#endif -#endif /* ! SSIZE_MAX */ - -#ifndef OFF_T_MAX -#ifdef OFF_MAX -#define OFF_T_MAX OFF_MAX -#elif defined(OFFT_MAX) -#define OFF_T_MAX OFFT_MAX -#elif defined(__APPLE__) && defined(__MACH__) -#define OFF_T_MAX INT64_MAX -#else -#define OFF_T_MAX MHD_SIGNED_TYPE_MAX_ (off_t) -#endif -#endif /* !OFF_T_MAX */ - -#if defined(_LARGEFILE64_SOURCE) && ! defined(OFF64_T_MAX) -#define OFF64_T_MAX MHD_SIGNED_TYPE_MAX_ (uint64_t) -#endif /* _LARGEFILE64_SOURCE && !OFF64_T_MAX */ - -#ifndef TIME_T_MAX -#define TIME_T_MAX ((time_t) \ - (MHD_TYPE_IS_SIGNED_ (time_t) ? \ - MHD_SIGNED_TYPE_MAX_ (time_t) : \ - MHD_UNSIGNED_TYPE_MAX_ (time_t))) -#endif /* !TIME_T_MAX */ - -#ifndef TIMEVAL_TV_SEC_MAX -#ifndef _WIN32 -#define TIMEVAL_TV_SEC_MAX TIME_T_MAX -#else /* _WIN32 */ -#define TIMEVAL_TV_SEC_MAX LONG_MAX -#endif /* _WIN32 */ -#endif /* !TIMEVAL_TV_SEC_MAX */ - -#endif /* MHD_LIMITS_H */