| /* |
| This file is part of libmicrohttpd |
| Copyright (C) 2007-2020 Daniel Pittman and Christian Grothoff |
| Copyright (C) 2015-2022 Evgeny Grin (Karlson2k) |
| |
| 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 connection.c |
| * @brief Methods for managing connections |
| * @author Daniel Pittman |
| * @author Christian Grothoff |
| * @author Karlson2k (Evgeny Grin) |
| */ |
| #include "internal.h" |
| #include "mhd_limits.h" |
| #include "connection.h" |
| #include "memorypool.h" |
| #include "response.h" |
| #include "mhd_mono_clock.h" |
| #include "mhd_str.h" |
| #if defined(MHD_USE_POSIX_THREADS) || defined(MHD_USE_W32_THREADS) |
| #include "mhd_locks.h" |
| #endif |
| #include "mhd_sockets.h" |
| #include "mhd_compat.h" |
| #include "mhd_itc.h" |
| #ifdef MHD_LINUX_SOLARIS_SENDFILE |
| #include <sys/sendfile.h> |
| #endif /* MHD_LINUX_SOLARIS_SENDFILE */ |
| #if defined(HAVE_FREEBSD_SENDFILE) || defined(HAVE_DARWIN_SENDFILE) |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <sys/uio.h> |
| #endif /* HAVE_FREEBSD_SENDFILE || HAVE_DARWIN_SENDFILE */ |
| #ifdef HTTPS_SUPPORT |
| #include "connection_https.h" |
| #endif /* HTTPS_SUPPORT */ |
| #ifdef HAVE_SYS_PARAM_H |
| /* For FreeBSD version identification */ |
| #include <sys/param.h> |
| #endif /* HAVE_SYS_PARAM_H */ |
| #include "mhd_send.h" |
| #include "mhd_assert.h" |
| |
| /** |
| * Message to transmit when http 1.1 request is received |
| */ |
| #define HTTP_100_CONTINUE "HTTP/1.1 100 Continue\r\n\r\n" |
| |
| /** |
| * Response text used when the request (http header) is too big to |
| * be processed. |
| * |
| * Intentionally empty here to keep our memory footprint |
| * minimal. |
| */ |
| #ifdef HAVE_MESSAGES |
| #define REQUEST_TOO_BIG \ |
| "<html><head><title>Request too big</title></head><body>Your HTTP header was too big for the memory constraints of this webserver.</body></html>" |
| #else |
| #define REQUEST_TOO_BIG "" |
| #endif |
| |
| /** |
| * Response text used when the request (http header) does not |
| * contain a "Host:" header and still claims to be HTTP 1.1. |
| * |
| * Intentionally empty here to keep our memory footprint |
| * minimal. |
| */ |
| #ifdef HAVE_MESSAGES |
| #define REQUEST_LACKS_HOST \ |
| "<html><head><title>"Host:" header required</title></head><body>In HTTP 1.1, requests must include a "Host:" header, and your HTTP 1.1 request lacked such a header.</body></html>" |
| #else |
| #define REQUEST_LACKS_HOST "" |
| #endif |
| |
| /** |
| * Response text used when the request has unsupported "Transfer-Enconding:". |
| */ |
| #ifdef HAVE_MESSAGES |
| #define REQUEST_UNSUPPORTED_TR_ENCODING \ |
| "<html>" \ |
| "<head><title>Unsupported Transfer-Encoding</title></head>" \ |
| "<body>The Transfer-Encoding used in request is not supported.</body>" \ |
| "</html>" |
| #else |
| #define REQUEST_UNSUPPORTED_TR_ENCODING "" |
| #endif |
| |
| /** |
| * Response text used when the request has unsupported both headers: |
| * "Transfer-Enconding:" and "Content-Length:" |
| */ |
| #ifdef HAVE_MESSAGES |
| #define REQUEST_LENGTH_WITH_TR_ENCODING \ |
| "<html>" \ |
| "<head><title>Malformed request</title></head>" \ |
| "<body>Wrong combination of the request headers: both Transfer-Encoding " \ |
| "and Content-Length headers are used at the same time.</body>" \ |
| "</html>" |
| #else |
| #define REQUEST_LENGTH_WITH_TR_ENCODING "" |
| #endif |
| |
| /** |
| * Response text used when the request (http header) is |
| * malformed. |
| * |
| * Intentionally empty here to keep our memory footprint |
| * minimal. |
| */ |
| #ifdef HAVE_MESSAGES |
| #define REQUEST_MALFORMED \ |
| "<html><head><title>Request malformed</title></head><body>Your HTTP request was syntactically incorrect.</body></html>" |
| #else |
| #define REQUEST_MALFORMED "" |
| #endif |
| |
| /** |
| * Response text used when the request HTTP chunked encoding is |
| * malformed. |
| */ |
| #ifdef HAVE_MESSAGES |
| #define REQUEST_CHUNKED_MALFORMED \ |
| "<html><head><title>Request malformed</title></head><body>Your HTTP chunked encoding was syntactically incorrect.</body></html>" |
| #else |
| #define REQUEST_CHUNKED_MALFORMED "" |
| #endif |
| |
| /** |
| * Response text used when the request HTTP chunk is too large. |
| */ |
| #ifdef HAVE_MESSAGES |
| #define REQUEST_CHUNK_TOO_LARGE \ |
| "<html><head><title>Request content too large</title></head><body>The chunk size used in your HTTP chunked encoded request is too large.</body></html>" |
| #else |
| #define REQUEST_CHUNK_TOO_LARGE "" |
| #endif |
| |
| /** |
| * Response text used when the request HTTP content is too large. |
| */ |
| #ifdef HAVE_MESSAGES |
| #define REQUEST_CONTENTLENGTH_TOOLARGE \ |
| "<html><head><title>Request content too large</title></head>" \ |
| "<body>Your HTTP request has too large value for <b>Content-Length</b> header.</body></html>" |
| #else |
| #define REQUEST_CONTENTLENGTH_TOOLARGE "" |
| #endif |
| |
| /** |
| * Response text used when the request HTTP chunked encoding is |
| * malformed. |
| */ |
| #ifdef HAVE_MESSAGES |
| #define REQUEST_CONTENTLENGTH_MALFORMED \ |
| "<html><head><title>Request malformed</title></head>" \ |
| "<body>Your HTTP request has wrong value for <b>Content-Length</b> header.</body></html>" |
| #else |
| #define REQUEST_CONTENTLENGTH_MALFORMED "" |
| #endif |
| |
| /** |
| * Response text used when there is an internal server error. |
| * |
| * Intentionally empty here to keep our memory footprint |
| * minimal. |
| */ |
| #ifdef HAVE_MESSAGES |
| #define INTERNAL_ERROR \ |
| "<html><head><title>Internal server error</title></head><body>Please ask the developer of this Web server to carefully read the GNU libmicrohttpd documentation about connection management and blocking.</body></html>" |
| #else |
| #define INTERNAL_ERROR "" |
| #endif |
| |
| /** |
| * Response text used when the request HTTP version is too old. |
| */ |
| #ifdef HAVE_MESSAGES |
| #define REQ_HTTP_VER_IS_TOO_OLD \ |
| "<html><head><title>Requested HTTP version is not supported</title></head><body>Requested HTTP version is too old and not supported.</body></html>" |
| #else |
| #define REQ_HTTP_VER_IS_TOO_OLD "" |
| #endif |
| |
| /** |
| * Response text used when the request HTTP version is not supported. |
| */ |
| #ifdef HAVE_MESSAGES |
| #define REQ_HTTP_VER_IS_NOT_SUPPORTED \ |
| "<html><head><title>Requested HTTP version is not supported</title></head><body>Requested HTTP version is not supported.</body></html>" |
| #else |
| #define REQ_HTTP_VER_IS_NOT_SUPPORTED "" |
| #endif |
| |
| |
| /** |
| * sendfile() chuck size |
| */ |
| #define MHD_SENFILE_CHUNK_ (0x20000) |
| |
| /** |
| * sendfile() chuck size for thread-per-connection |
| */ |
| #define MHD_SENFILE_CHUNK_THR_P_C_ (0x200000) |
| |
| #ifdef HAVE_MESSAGES |
| /** |
| * Return text description for MHD_ERR_*_ codes |
| * @param mhd_err_code the error code |
| * @return pointer to static string with error description |
| */ |
| static const char * |
| str_conn_error_ (ssize_t mhd_err_code) |
| { |
| switch (mhd_err_code) |
| { |
| case MHD_ERR_AGAIN_: |
| return _ ("The operation would block, retry later"); |
| case MHD_ERR_CONNRESET_: |
| return _ ("The connection was forcibly closed by remote peer"); |
| case MHD_ERR_NOTCONN_: |
| return _ ("The socket is not connected"); |
| case MHD_ERR_NOMEM_: |
| return _ ("Not enough system resources to serve the request"); |
| case MHD_ERR_BADF_: |
| return _ ("Bad FD value"); |
| case MHD_ERR_INVAL_: |
| return _ ("Argument value is invalid"); |
| case MHD_ERR_OPNOTSUPP_: |
| return _ ("Argument value is not supported"); |
| case MHD_ERR_PIPE_: |
| return _ ("The socket is no longer available for sending"); |
| case MHD_ERR_TLS_: |
| return _ ("TLS encryption or decryption error"); |
| default: |
| break; /* Mute compiler warning */ |
| } |
| if (0 <= mhd_err_code) |
| return _ ("Not an error code"); |
| |
| mhd_assert (0); /* Should never be reachable */ |
| return _ ("Wrong error code value"); |
| } |
| |
| |
| #endif /* HAVE_MESSAGES */ |
| |
| /** |
| * 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 |
| */ |
| void * |
| MHD_connection_alloc_memory_ (struct MHD_Connection *connection, |
| size_t size) |
| { |
| struct MHD_Connection *const c = connection; /* a short alias */ |
| struct MemoryPool *const pool = c->pool; /* a short alias */ |
| size_t need_to_free; /**< The required amount of free memory */ |
| void *res; |
| |
| res = MHD_pool_try_alloc (pool, size, &need_to_free); |
| if (NULL == res) |
| { |
| if (NULL != c->write_buffer) |
| { |
| /* The connection is in the sending phase */ |
| mhd_assert (MHD_CONNECTION_START_REPLY <= c->state); |
| if (c->write_buffer_size - c->write_buffer_append_offset >= need_to_free) |
| { |
| char *buf; |
| const size_t new_buf_size = c->write_buffer_size - need_to_free; |
| 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 (NULL != c->read_buffer) |
| { |
| /* The connection is in the receiving phase */ |
| if (c->read_buffer_size - c->read_buffer_offset >= need_to_free) |
| { |
| char *buf; |
| const size_t new_buf_size = c->read_buffer_size - need_to_free; |
| 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; |
| } |
| |
| |
| /** |
| * Callback for receiving data from the socket. |
| * |
| * @param connection the MHD connection structure |
| * @param other where to write received data to |
| * @param i maximum size of other (in bytes) |
| * @return positive value for number of bytes actually received or |
| * negative value for error number MHD_ERR_xxx_ |
| */ |
| static ssize_t |
| recv_param_adapter (struct MHD_Connection *connection, |
| void *other, |
| size_t i) |
| { |
| ssize_t ret; |
| |
| if ( (MHD_INVALID_SOCKET == connection->socket_fd) || |
| (MHD_CONNECTION_CLOSED == connection->state) ) |
| { |
| return MHD_ERR_NOTCONN_; |
| } |
| if (i > MHD_SCKT_SEND_MAX_SIZE_) |
| i = MHD_SCKT_SEND_MAX_SIZE_; /* return value limit */ |
| |
| ret = MHD_recv_ (connection->socket_fd, |
| other, |
| i); |
| if (0 > ret) |
| { |
| const int err = MHD_socket_get_error_ (); |
| if (MHD_SCKT_ERR_IS_EAGAIN_ (err)) |
| { |
| #ifdef EPOLL_SUPPORT |
| /* Got EAGAIN --- no longer read-ready */ |
| connection->epoll_state &= |
| ~((enum MHD_EpollState) MHD_EPOLL_STATE_READ_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_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 (i > (size_t) ret) |
| connection->epoll_state &= |
| ~((enum MHD_EpollState) MHD_EPOLL_STATE_READ_READY); |
| #endif /* EPOLL_SUPPORT */ |
| return ret; |
| } |
| |
| |
| /** |
| * Get all of the headers from the request. |
| * |
| * @param connection connection to get values from |
| * @param kind types of values to iterate over, can be a bitmask |
| * @param iterator callback to call on each header; |
| * maybe NULL (then just count headers) |
| * @param iterator_cls extra argument to @a iterator |
| * @return number of entries iterated over |
| * -1 if connection is NULL. |
| * @ingroup request |
| */ |
| _MHD_EXTERN int |
| MHD_get_connection_values (struct MHD_Connection *connection, |
| enum MHD_ValueKind kind, |
| MHD_KeyValueIterator iterator, |
| void *iterator_cls) |
| { |
| int ret; |
| struct MHD_HTTP_Req_Header *pos; |
| |
| if (NULL == connection) |
| return -1; |
| ret = 0; |
| for (pos = connection->rq.headers_received; NULL != pos; pos = pos->next) |
| if (0 != (pos->kind & kind)) |
| { |
| ret++; |
| if ( (NULL != iterator) && |
| (MHD_NO == iterator (iterator_cls, |
| pos->kind, |
| pos->header, |
| pos->value)) ) |
| return ret; |
| } |
| return ret; |
| } |
| |
| |
| /** |
| * Get all of the headers from the request. |
| * |
| * @param connection connection to get values from |
| * @param kind types of values to iterate over, can be a bitmask |
| * @param iterator callback to call on each header; |
| * maybe NULL (then just count headers) |
| * @param iterator_cls extra argument to @a iterator |
| * @return number of entries iterated over, |
| * -1 if connection is NULL. |
| * @ingroup request |
| */ |
| _MHD_EXTERN int |
| MHD_get_connection_values_n (struct MHD_Connection *connection, |
| enum MHD_ValueKind kind, |
| MHD_KeyValueIteratorN iterator, |
| void *iterator_cls) |
| { |
| int ret; |
| struct MHD_HTTP_Req_Header *pos; |
| |
| if (NULL == connection) |
| return -1; |
| ret = 0; |
| |
| if (NULL == iterator) |
| for (pos = connection->rq.headers_received; NULL != pos; pos = pos->next) |
| { |
| if (0 != (kind & pos->kind)) |
| ret++; |
| } |
| else |
| for (pos = connection->rq.headers_received; NULL != pos; pos = pos->next) |
| if (0 != (kind & pos->kind)) |
| { |
| ret++; |
| if (MHD_NO == iterator (iterator_cls, |
| pos->kind, |
| pos->header, |
| pos->header_size, |
| pos->value, |
| pos->value_size)) |
| return ret; |
| } |
| return ret; |
| } |
| |
| |
| /** |
| * This function can be used to add an arbitrary entry to connection. |
| * Internal version of #MHD_set_connection_value_n() without checking |
| * of arguments values. |
| * |
| * @param connection the connection for which a |
| * value should be set |
| * @param kind kind of the value |
| * @param key key for the value, must be zero-terminated |
| * @param key_size number of bytes in @a key (excluding 0-terminator) |
| * @param value the value itself, must be zero-terminated |
| * @param value_size number of bytes in @a value (excluding 0-terminator) |
| * @return #MHD_NO if the operation could not be |
| * performed due to insufficient memory; |
| * #MHD_YES on success |
| * @ingroup request |
| */ |
| static enum MHD_Result |
| MHD_set_connection_value_n_nocheck_ (struct MHD_Connection *connection, |
| enum MHD_ValueKind kind, |
| const char *key, |
| size_t key_size, |
| const char *value, |
| size_t value_size) |
| { |
| struct MHD_HTTP_Req_Header *pos; |
| |
| pos = MHD_connection_alloc_memory_ (connection, |
| sizeof (struct MHD_HTTP_Res_Header)); |
| if (NULL == pos) |
| return MHD_NO; |
| pos->header = key; |
| pos->header_size = key_size; |
| pos->value = value; |
| pos->value_size = value_size; |
| pos->kind = kind; |
| pos->next = NULL; |
| /* append 'pos' to the linked list of headers */ |
| if (NULL == connection->rq.headers_received_tail) |
| { |
| connection->rq.headers_received = pos; |
| connection->rq.headers_received_tail = pos; |
| } |
| else |
| { |
| connection->rq.headers_received_tail->next = pos; |
| connection->rq.headers_received_tail = pos; |
| } |
| return MHD_YES; |
| } |
| |
| |
| /** |
| * This function can be used to add an arbitrary entry to connection. |
| * This function could add entry with binary zero, which is allowed |
| * for #MHD_GET_ARGUMENT_KIND. For other kind on entries it is |
| * recommended to use #MHD_set_connection_value. |
| * |
| * This function MUST only be called from within the |
| * #MHD_AccessHandlerCallback (otherwise, access maybe improperly |
| * synchronized). Furthermore, the client must guarantee that the key |
| * and value arguments are 0-terminated strings that are NOT freed |
| * until the connection is closed. (The easiest way to do this is by |
| * passing only arguments to permanently allocated strings.). |
| * |
| * @param connection the connection for which a |
| * value should be set |
| * @param kind kind of the value |
| * @param key key for the value, must be zero-terminated |
| * @param key_size number of bytes in @a key (excluding 0-terminator) |
| * @param value the value itself, must be zero-terminated |
| * @param value_size number of bytes in @a value (excluding 0-terminator) |
| * @return #MHD_NO if the operation could not be |
| * performed due to insufficient memory; |
| * #MHD_YES on success |
| * @ingroup request |
| */ |
| _MHD_EXTERN enum MHD_Result |
| MHD_set_connection_value_n (struct MHD_Connection *connection, |
| enum MHD_ValueKind kind, |
| const char *key, |
| size_t key_size, |
| const char *value, |
| size_t value_size) |
| { |
| if ( (MHD_GET_ARGUMENT_KIND != kind) && |
| ( ((key ? strlen (key) : 0) != key_size) || |
| ((value ? strlen (value) : 0) != value_size) ) ) |
| return MHD_NO; /* binary zero is allowed only in GET arguments */ |
| |
| return MHD_set_connection_value_n_nocheck_ (connection, |
| kind, |
| key, |
| key_size, |
| value, |
| value_size); |
| } |
| |
| |
| /** |
| * This function can be used to add an entry to the HTTP headers of a |
| * connection (so that the #MHD_get_connection_values function will |
| * return them -- and the `struct MHD_PostProcessor` will also see |
| * them). This maybe required in certain situations (see Mantis |
| * #1399) where (broken) HTTP implementations fail to supply values |
| * needed by the post processor (or other parts of the application). |
| * |
| * This function MUST only be called from within the |
| * #MHD_AccessHandlerCallback (otherwise, access maybe improperly |
| * synchronized). Furthermore, the client must guarantee that the key |
| * and value arguments are 0-terminated strings that are NOT freed |
| * until the connection is closed. (The easiest way to do this is by |
| * passing only arguments to permanently allocated strings.). |
| * |
| * @param connection the connection for which a |
| * value should be set |
| * @param kind kind of the value |
| * @param key key for the value |
| * @param value the value itself |
| * @return #MHD_NO if the operation could not be |
| * performed due to insufficient memory; |
| * #MHD_YES on success |
| * @ingroup request |
| */ |
| _MHD_EXTERN enum MHD_Result |
| MHD_set_connection_value (struct MHD_Connection *connection, |
| enum MHD_ValueKind kind, |
| const char *key, |
| const char *value) |
| { |
| return MHD_set_connection_value_n_nocheck_ (connection, |
| kind, |
| key, |
| NULL != key |
| ? strlen (key) |
| : 0, |
| value, |
| NULL != value |
| ? strlen (value) |
| : 0); |
| } |
| |
| |
| /** |
| * Get a particular header value. If multiple |
| * values match the kind, return any one of them. |
| * |
| * @param connection connection 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 |
| * @return NULL if no such item was found |
| * @ingroup request |
| */ |
| _MHD_EXTERN const char * |
| MHD_lookup_connection_value (struct MHD_Connection *connection, |
| enum MHD_ValueKind kind, |
| const char *key) |
| { |
| const char *value; |
| |
| value = NULL; |
| (void) MHD_lookup_connection_value_n (connection, |
| kind, |
| key, |
| (NULL == key) ? 0 : strlen (key), |
| &value, |
| NULL); |
| return value; |
| } |
| |
| |
| /** |
| * Get a particular header value. If multiple |
| * values match the kind, return any one of them. |
| * @note Since MHD_VERSION 0x00096304 |
| * |
| * @param connection connection 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_size the length of @a key in bytes |
| * @param[out] value_ptr the pointer to variable, which will be set to found value, |
| * will not be updated if key not found, |
| * could be NULL to just check for presence of @a key |
| * @param[out] value_size_ptr the pointer variable, which will set to found value, |
| * will not be updated if key not found, |
| * could be NULL |
| * @return #MHD_YES if key is found, |
| * #MHD_NO otherwise. |
| * @ingroup request |
| */ |
| _MHD_EXTERN enum MHD_Result |
| MHD_lookup_connection_value_n (struct MHD_Connection *connection, |
| enum MHD_ValueKind kind, |
| const char *key, |
| size_t key_size, |
| const char **value_ptr, |
| size_t *value_size_ptr) |
| { |
| struct MHD_HTTP_Req_Header *pos; |
| |
| if (NULL == connection) |
| return MHD_NO; |
| |
| if (NULL == key) |
| { |
| for (pos = connection->rq.headers_received; NULL != pos; pos = pos->next) |
| { |
| if ( (0 != (kind & pos->kind)) && |
| (NULL == pos->header) ) |
| break; |
| } |
| } |
| else |
| { |
| for (pos = connection->rq.headers_received; NULL != pos; pos = pos->next) |
| { |
| if ( (0 != (kind & pos->kind)) && |
| (key_size == pos->header_size) && |
| ( (key == pos->header) || |
| (MHD_str_equal_caseless_bin_n_ (key, |
| pos->header, |
| key_size) ) ) ) |
| break; |
| } |
| } |
| |
| if (NULL == pos) |
| return MHD_NO; |
| |
| if (NULL != value_ptr) |
| *value_ptr = pos->value; |
| |
| if (NULL != value_size_ptr) |
| *value_size_ptr = pos->value_size; |
| |
| return MHD_YES; |
| } |
| |
| |
| /** |
| * Check whether request header contains particular token. |
| * |
| * Token could be surrounded by spaces and tabs and delimited by comma. |
| * Case-insensitive match used for header names and tokens. |
| * @param connection the connection to get values from |
| * @param header the header name |
| * @param header_len the length of header, not including optional |
| * terminating null-character |
| * @param token the token to find |
| * @param token_len the length of token, not including optional |
| * terminating null-character. |
| * @return true if token is found in specified header, |
| * false otherwise |
| */ |
| static bool |
| MHD_lookup_header_token_ci (const struct MHD_Connection *connection, |
| const char *header, |
| size_t header_len, |
| const char *token, |
| size_t token_len) |
| { |
| struct MHD_HTTP_Req_Header *pos; |
| |
| if ((NULL == connection) || (NULL == header) || (0 == header[0]) || |
| (NULL == token) || (0 == token[0])) |
| return false; |
| |
| for (pos = connection->rq.headers_received; NULL != pos; pos = pos->next) |
| { |
| if ((0 != (pos->kind & MHD_HEADER_KIND)) && |
| (header_len == pos->header_size) && |
| ( (header == pos->header) || |
| (MHD_str_equal_caseless_bin_n_ (header, |
| pos->header, |
| header_len)) ) && |
| (MHD_str_has_token_caseless_ (pos->value, token, token_len))) |
| return true; |
| } |
| return false; |
| } |
| |
| |
| /** |
| * Check whether request header contains particular static @a tkn. |
| * |
| * Token could be surrounded by spaces and tabs and delimited by comma. |
| * Case-insensitive match used for header names and tokens. |
| * @param c the connection to get values from |
| * @param h the static string of header name |
| * @param tkn the static string of token to find |
| * @return true if token is found in specified header, |
| * false otherwise |
| */ |
| #define MHD_lookup_header_s_token_ci(c,h,tkn) \ |
| MHD_lookup_header_token_ci ((c),(h),MHD_STATICSTR_LEN_ (h), \ |
| (tkn),MHD_STATICSTR_LEN_ (tkn)) |
| |
| |
| /** |
| * Do we (still) need to send a 100 continue |
| * message for this connection? |
| * |
| * @param connection connection to test |
| * @return false if we don't need 100 CONTINUE, true if we do |
| */ |
| static bool |
| need_100_continue (struct MHD_Connection *connection) |
| { |
| const char *expect; |
| |
| if (! MHD_IS_HTTP_VER_1_1_COMPAT (connection->rq.http_ver)) |
| return false; |
| |
| if (0 == connection->rq.remaining_upload_size) |
| return false; |
| |
| if (MHD_NO == |
| MHD_lookup_connection_value_n (connection, |
| MHD_HEADER_KIND, |
| MHD_HTTP_HEADER_EXPECT, |
| MHD_STATICSTR_LEN_ ( \ |
| MHD_HTTP_HEADER_EXPECT), |
| &expect, |
| NULL)) |
| return false; |
| |
| if (MHD_str_equal_caseless_ (expect, |
| "100-continue")) |
| return true; |
| |
| return false; |
| } |
| |
| |
| /** |
| * Mark connection as "closed". |
| * @remark To be called from any thread. |
| * |
| * @param connection connection to close |
| */ |
| void |
| MHD_connection_mark_closed_ (struct MHD_Connection *connection) |
| { |
| const struct MHD_Daemon *daemon = connection->daemon; |
| |
| if (0 == (daemon->options & MHD_USE_TURBO)) |
| { |
| #ifdef HTTPS_SUPPORT |
| /* For TLS connection use shutdown of TLS layer |
| * and do not shutdown TCP socket. This give more |
| * chances to send TLS closure data to remote side. |
| * Closure of TLS layer will be interpreted by |
| * remote side as end of transmission. */ |
| if (0 != (daemon->options & MHD_USE_TLS)) |
| { |
| if (! MHD_tls_connection_shutdown (connection)) |
| shutdown (connection->socket_fd, |
| SHUT_WR); |
| } |
| else /* Combined with next 'shutdown()'. */ |
| #endif /* HTTPS_SUPPORT */ |
| shutdown (connection->socket_fd, |
| SHUT_WR); |
| } |
| connection->state = MHD_CONNECTION_CLOSED; |
| connection->event_loop_info = MHD_EVENT_LOOP_INFO_CLEANUP; |
| } |
| |
| |
| /** |
| * Close the given connection and give the |
| * specified termination code to the user. |
| * @remark To be called only from thread that |
| * process connection's recv(), send() and response. |
| * |
| * @param connection connection to close |
| * @param termination_code termination reason to give |
| */ |
| void |
| MHD_connection_close_ (struct MHD_Connection *connection, |
| enum MHD_RequestTerminationCode termination_code) |
| { |
| struct MHD_Daemon *daemon = connection->daemon; |
| struct MHD_Response *resp = connection->rp.response; |
| |
| mhd_assert (! connection->suspended); |
| #ifdef MHD_USE_THREADS |
| mhd_assert ( (0 == (daemon->options & MHD_USE_INTERNAL_POLLING_THREAD)) || \ |
| MHD_thread_ID_match_current_ (connection->pid) ); |
| #endif /* MHD_USE_THREADS */ |
| if ( (NULL != daemon->notify_completed) && |
| (connection->rq.client_aware) ) |
| daemon->notify_completed (daemon->notify_completed_cls, |
| connection, |
| &connection->rq.client_context, |
| termination_code); |
| connection->rq.client_aware = false; |
| if (NULL != resp) |
| { |
| connection->rp.response = NULL; |
| MHD_destroy_response (resp); |
| } |
| if (NULL != connection->pool) |
| { |
| MHD_pool_destroy (connection->pool); |
| connection->pool = NULL; |
| } |
| |
| MHD_connection_mark_closed_ (connection); |
| } |
| |
| |
| #if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT) |
| /** |
| * Stop TLS forwarding on upgraded connection and |
| * reflect remote disconnect state to socketpair. |
| * @remark In thread-per-connection mode this function |
| * can be called from any thread, in other modes this |
| * function must be called only from thread that process |
| * daemon's select()/poll()/etc. |
| * |
| * @param connection the upgraded connection |
| */ |
| void |
| MHD_connection_finish_forward_ (struct MHD_Connection *connection) |
| { |
| struct MHD_Daemon *daemon = connection->daemon; |
| struct MHD_UpgradeResponseHandle *urh = connection->urh; |
| |
| #ifdef MHD_USE_THREADS |
| mhd_assert ( (0 == (daemon->options & MHD_USE_INTERNAL_POLLING_THREAD)) || \ |
| (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) || \ |
| MHD_thread_ID_match_current_ (daemon->pid) ); |
| #endif /* MHD_USE_THREADS */ |
| |
| if (0 == (daemon->options & MHD_USE_TLS)) |
| return; /* Nothing to do with non-TLS connection. */ |
| |
| if (0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) |
| DLL_remove (daemon->urh_head, |
| daemon->urh_tail, |
| urh); |
| #ifdef EPOLL_SUPPORT |
| if ( (0 != (daemon->options & MHD_USE_EPOLL)) && |
| (0 != epoll_ctl (daemon->epoll_upgrade_fd, |
| EPOLL_CTL_DEL, |
| connection->socket_fd, |
| NULL)) ) |
| { |
| MHD_PANIC (_ ("Failed to remove FD from epoll set.\n")); |
| } |
| if (urh->in_eready_list) |
| { |
| EDLL_remove (daemon->eready_urh_head, |
| daemon->eready_urh_tail, |
| urh); |
| urh->in_eready_list = false; |
| } |
| #endif /* EPOLL_SUPPORT */ |
| if (MHD_INVALID_SOCKET != urh->mhd.socket) |
| { |
| #ifdef EPOLL_SUPPORT |
| if ( (0 != (daemon->options & MHD_USE_EPOLL)) && |
| (0 != epoll_ctl (daemon->epoll_upgrade_fd, |
| EPOLL_CTL_DEL, |
| urh->mhd.socket, |
| NULL)) ) |
| { |
| MHD_PANIC (_ ("Failed to remove FD from epoll set.\n")); |
| } |
| #endif /* EPOLL_SUPPORT */ |
| /* Reflect remote disconnect to application by breaking |
| * socketpair connection. */ |
| shutdown (urh->mhd.socket, SHUT_RDWR); |
| } |
| /* Socketpair sockets will remain open as they will be |
| * used with MHD_UPGRADE_ACTION_CLOSE. They will be |
| * closed by cleanup_upgraded_connection() during |
| * connection's final cleanup. |
| */ |
| } |
| |
| |
| #endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT*/ |
| |
| |
| /** |
| * A serious error occurred, close the |
| * connection (and notify the application). |
| * |
| * @param connection connection to close with error |
| * @param emsg error message (can be NULL) |
| */ |
| static void |
| connection_close_error (struct MHD_Connection *connection, |
| const char *emsg) |
| { |
| connection->stop_with_error = true; |
| connection->discard_request = true; |
| #ifdef HAVE_MESSAGES |
| if (NULL != emsg) |
| MHD_DLOG (connection->daemon, |
| "%s\n", |
| emsg); |
| #else /* ! HAVE_MESSAGES */ |
| (void) emsg; /* Mute compiler warning. */ |
| #endif /* ! HAVE_MESSAGES */ |
| MHD_connection_close_ (connection, |
| MHD_REQUEST_TERMINATED_WITH_ERROR); |
| } |
| |
| |
| /** |
| * Macro to only include error message in call to |
| * #connection_close_error() if we have HAVE_MESSAGES. |
| */ |
| #ifdef HAVE_MESSAGES |
| #define CONNECTION_CLOSE_ERROR(c, emsg) connection_close_error (c, emsg) |
| #else |
| #define CONNECTION_CLOSE_ERROR(c, emsg) connection_close_error (c, NULL) |
| #endif |
| |
| |
| /** |
| * A serious error occurred, check whether error response is already |
| * queued and close the connection if response wasn't queued. |
| * |
| * @param connection connection to close with error |
| * @param emsg error message (can be NULL) |
| */ |
| static void |
| connection_close_error_check (struct MHD_Connection *connection, |
| const char *emsg) |
| { |
| if ( (NULL != connection->rp.response) && |
| (400 <= connection->rp.responseCode) && |
| (NULL == connection->rp.response->crc) && /* Static response only! */ |
| (connection->stop_with_error) && |
| (MHD_CONNECTION_HEADERS_SENDING == connection->state) ) |
| return; /* An error response was already queued */ |
| |
| connection_close_error (connection, emsg); |
| } |
| |
| |
| /** |
| * Macro to only include error message in call to |
| * #connection_close_error_check() if we have HAVE_MESSAGES. |
| */ |
| #ifdef HAVE_MESSAGES |
| #define CONNECTION_CLOSE_ERROR_CHECK(c, emsg) \ |
| connection_close_error_check (c, emsg) |
| #else |
| #define CONNECTION_CLOSE_ERROR_CHECK(c, emsg) \ |
| connection_close_error_check (c, NULL) |
| #endif |
| |
| |
| /** |
| * Prepare the response buffer of this connection for |
| * sending. Assumes that the response mutex is |
| * already held. If the transmission is complete, |
| * this function may close the socket (and return |
| * #MHD_NO). |
| * |
| * @param connection the connection |
| * @return #MHD_NO if readying the response failed (the |
| * lock on the response will have been released already |
| * in this case). |
| */ |
| static enum MHD_Result |
| try_ready_normal_body (struct MHD_Connection *connection) |
| { |
| ssize_t ret; |
| struct MHD_Response *response; |
| |
| response = connection->rp.response; |
| mhd_assert (connection->rp.props.send_reply_body); |
| |
| if ( (0 == response->total_size) || |
| /* TODO: replace the next check with assert */ |
| (connection->rp.rsp_write_position == response->total_size) ) |
| return MHD_YES; /* 0-byte response is always ready */ |
| if (NULL != response->data_iov) |
| { |
| size_t copy_size; |
| |
| if (NULL != connection->rp.resp_iov.iov) |
| return MHD_YES; |
| copy_size = response->data_iovcnt * sizeof(MHD_iovec_); |
| connection->rp.resp_iov.iov = MHD_connection_alloc_memory_ (connection, |
| copy_size); |
| if (NULL == connection->rp.resp_iov.iov) |
| { |
| MHD_mutex_unlock_chk_ (&response->mutex); |
| /* not enough memory */ |
| CONNECTION_CLOSE_ERROR (connection, |
| _ ("Closing connection (out of memory).")); |
| return MHD_NO; |
| } |
| memcpy (connection->rp.resp_iov.iov, |
| response->data_iov, |
| copy_size); |
| connection->rp.resp_iov.cnt = response->data_iovcnt; |
| connection->rp.resp_iov.sent = 0; |
| return MHD_YES; |
| } |
| if (NULL == response->crc) |
| return MHD_YES; |
| if ( (response->data_start <= |
| connection->rp.rsp_write_position) && |
| (response->data_size + response->data_start > |
| connection->rp.rsp_write_position) ) |
| return MHD_YES; /* response already ready */ |
| #if defined(_MHD_HAVE_SENDFILE) |
| if (MHD_resp_sender_sendfile == connection->rp.resp_sender) |
| { |
| /* will use sendfile, no need to bother response crc */ |
| return MHD_YES; |
| } |
| #endif /* _MHD_HAVE_SENDFILE */ |
| |
| ret = response->crc (response->crc_cls, |
| connection->rp.rsp_write_position, |
| (char *) response->data, |
| (size_t) MHD_MIN ((uint64_t) response->data_buffer_size, |
| response->total_size |
| - connection->rp.rsp_write_position)); |
| if (0 > ret) |
| { |
| /* either error or http 1.0 transfer, close socket! */ |
| /* TODO: do not update total size, check whether response |
| * was really with unknown size */ |
| response->total_size = connection->rp.rsp_write_position; |
| #if defined(MHD_USE_POSIX_THREADS) || defined(MHD_USE_W32_THREADS) |
| MHD_mutex_unlock_chk_ (&response->mutex); |
| #endif |
| if (MHD_CONTENT_READER_END_OF_STREAM == ret) |
| MHD_connection_close_ (connection, |
| MHD_REQUEST_TERMINATED_COMPLETED_OK); |
| else |
| CONNECTION_CLOSE_ERROR (connection, |
| _ ("Closing connection (application reported " \ |
| "error generating data).")); |
| return MHD_NO; |
| } |
| response->data_start = connection->rp.rsp_write_position; |
| response->data_size = (size_t) ret; |
| if (0 == ret) |
| { |
| connection->state = MHD_CONNECTION_NORMAL_BODY_UNREADY; |
| #if defined(MHD_USE_POSIX_THREADS) || defined(MHD_USE_W32_THREADS) |
| MHD_mutex_unlock_chk_ (&response->mutex); |
| #endif |
| return MHD_NO; |
| } |
| return MHD_YES; |
| } |
| |
| |
| /** |
| * Prepare the response buffer of this connection for sending. |
| * Assumes that the response mutex is already held. If the |
| * transmission is complete, this function may close the socket (and |
| * return #MHD_NO). |
| * |
| * @param connection the connection |
| * @param[out] p_finished the pointer to variable that will be set to "true" |
| * when application returned indication of the end |
| * of the stream |
| * @return #MHD_NO if readying the response failed |
| */ |
| static enum MHD_Result |
| try_ready_chunked_body (struct MHD_Connection *connection, |
| bool *p_finished) |
| { |
| ssize_t ret; |
| struct MHD_Response *response; |
| static const size_t max_chunk = 0xFFFFFF; |
| char chunk_hdr[6]; /* 6: max strlen of "FFFFFF" */ |
| /* "FFFFFF" + "\r\n" */ |
| static const size_t max_chunk_hdr_len = sizeof(chunk_hdr) + 2; |
| /* "FFFFFF" + "\r\n" + "\r\n" (chunk termination) */ |
| static const size_t max_chunk_overhead = sizeof(chunk_hdr) + 2 + 2; |
| size_t chunk_hdr_len; |
| uint64_t left_to_send; |
| size_t size_to_fill; |
| |
| response = connection->rp.response; |
| mhd_assert (NULL != response->crc || NULL != response->data); |
| |
| mhd_assert (0 == connection->write_buffer_append_offset); |
| |
| /* The buffer must be reasonably large enough */ |
| if (128 > connection->write_buffer_size) |
| { |
| size_t size; |
| |
| size = connection->write_buffer_size + MHD_pool_get_free (connection->pool); |
| if (128 > size) |
| { |
| #if defined(MHD_USE_POSIX_THREADS) || defined(MHD_USE_W32_THREADS) |
| MHD_mutex_unlock_chk_ (&response->mutex); |
| #endif |
| /* not enough memory */ |
| CONNECTION_CLOSE_ERROR (connection, |
| _ ("Closing connection (out of memory).")); |
| return MHD_NO; |
| } |
| /* Limit the buffer size to the largest usable size for chunks */ |
| if ( (max_chunk + max_chunk_overhead) < size) |
| size = max_chunk + max_chunk_overhead; |
| connection->write_buffer = MHD_pool_reallocate (connection->pool, |
| connection->write_buffer, |
| connection-> |
| write_buffer_size, size); |
| mhd_assert (NULL != connection->write_buffer); |
| connection->write_buffer_size = size; |
| } |
| mhd_assert (max_chunk_overhead < connection->write_buffer_size); |
| |
| if (MHD_SIZE_UNKNOWN == response->total_size) |
| left_to_send = MHD_SIZE_UNKNOWN; |
| else |
| left_to_send = response->total_size |
| - connection->rp.rsp_write_position; |
| |
| size_to_fill = connection->write_buffer_size - max_chunk_overhead; |
| /* Limit size for the callback to the max usable size */ |
| if (max_chunk < size_to_fill) |
| size_to_fill = max_chunk; |
| if (left_to_send < size_to_fill) |
| size_to_fill = (size_t) left_to_send; |
| |
| if (0 == left_to_send) |
| /* nothing to send, don't bother calling crc */ |
| ret = MHD_CONTENT_READER_END_OF_STREAM; |
| else if ( (response->data_start <= |
| connection->rp.rsp_write_position) && |
| (response->data_start + response->data_size > |
| connection->rp.rsp_write_position) ) |
| { |
| /* difference between rsp_write_position and data_start is less |
| than data_size which is size_t type, no need to check for overflow */ |
| const size_t data_write_offset |
| = (size_t) (connection->rp.rsp_write_position |
| - response->data_start); |
| /* buffer already ready, use what is there for the chunk */ |
| mhd_assert (SSIZE_MAX >= (response->data_size - data_write_offset)); |
| mhd_assert (response->data_size >= data_write_offset); |
| ret = (ssize_t) (response->data_size - data_write_offset); |
| if ( ((size_t) ret) > size_to_fill) |
| ret = (ssize_t) size_to_fill; |
| memcpy (&connection->write_buffer[max_chunk_hdr_len], |
| &response->data[data_write_offset], |
| (size_t) ret); |
| } |
| else |
| { |
| if (NULL == response->crc) |
| { /* There is no way to reach this code */ |
| #if defined(MHD_USE_THREADS) |
| MHD_mutex_unlock_chk_ (&response->mutex); |
| #endif |
| CONNECTION_CLOSE_ERROR (connection, |
| _ ("No callback for the chunked data.")); |
| return MHD_NO; |
| } |
| ret = response->crc (response->crc_cls, |
| connection->rp.rsp_write_position, |
| &connection->write_buffer[max_chunk_hdr_len], |
| size_to_fill); |
| } |
| if (MHD_CONTENT_READER_END_WITH_ERROR == ret) |
| { |
| /* error, close socket! */ |
| /* TODO: remove update of the response size */ |
| response->total_size = connection->rp.rsp_write_position; |
| #if defined(MHD_USE_POSIX_THREADS) || defined(MHD_USE_W32_THREADS) |
| MHD_mutex_unlock_chk_ (&response->mutex); |
| #endif |
| CONNECTION_CLOSE_ERROR (connection, |
| _ ("Closing connection (application error " \ |
| "generating response).")); |
| return MHD_NO; |
| } |
| if (MHD_CONTENT_READER_END_OF_STREAM == ret) |
| { |
| *p_finished = true; |
| /* TODO: remove update of the response size */ |
| response->total_size = connection->rp.rsp_write_position; |
| return MHD_YES; |
| } |
| if (0 == ret) |
| { |
| connection->state = MHD_CONNECTION_CHUNKED_BODY_UNREADY; |
| #if defined(MHD_USE_POSIX_THREADS) || defined(MHD_USE_W32_THREADS) |
| MHD_mutex_unlock_chk_ (&response->mutex); |
| #endif |
| return MHD_NO; |
| } |
| if (size_to_fill < (size_t) ret) |
| { |
| #if defined(MHD_USE_THREADS) |
| MHD_mutex_unlock_chk_ (&response->mutex); |
| #endif |
| CONNECTION_CLOSE_ERROR (connection, |
| _ ("Closing connection (application returned " \ |
| "more data than requested).")); |
| return MHD_NO; |
| } |
| chunk_hdr_len = MHD_uint32_to_strx ((uint32_t) ret, chunk_hdr, |
| sizeof(chunk_hdr)); |
| mhd_assert (chunk_hdr_len != 0); |
| mhd_assert (chunk_hdr_len < sizeof(chunk_hdr)); |
| *p_finished = false; |
| connection->write_buffer_send_offset = |
| (max_chunk_hdr_len - (chunk_hdr_len + 2)); |
| memcpy (connection->write_buffer + connection->write_buffer_send_offset, |
| chunk_hdr, |
| chunk_hdr_len); |
| connection->write_buffer[max_chunk_hdr_len - 2] = '\r'; |
| connection->write_buffer[max_chunk_hdr_len - 1] = '\n'; |
| connection->write_buffer[max_chunk_hdr_len + (size_t) ret] = '\r'; |
| connection->write_buffer[max_chunk_hdr_len + (size_t) ret + 1] = '\n'; |
| connection->rp.rsp_write_position += (size_t) ret; |
| connection->write_buffer_append_offset = max_chunk_hdr_len + (size_t) ret + 2; |
| return MHD_YES; |
| } |
| |
| |
| /** |
| * Are we allowed to keep the given connection alive? |
| * We can use the TCP stream for a second request if the connection |
| * is HTTP 1.1 and the "Connection" header either does not exist or |
| * is not set to "close", or if the connection is HTTP 1.0 and the |
| * "Connection" header is explicitly set to "keep-alive". |
| * If no HTTP version is specified (or if it is not 1.0 or 1.1), we |
| * definitively close the connection. If the "Connection" header is |
| * not exactly "close" or "keep-alive", we proceed to use the default |
| * for the respective HTTP version. |
| * If response has HTTP/1.0 flag or has "Connection: close" header |
| * then connection must be closed. |
| * If full request has not been read then connection must be closed |
| * as well. |
| * |
| * @param connection the connection to check for keepalive |
| * @return MHD_CONN_USE_KEEPALIVE if (based on the request and the response), |
| * a keepalive is legal, |
| * MHD_CONN_MUST_CLOSE if connection must be closed after sending |
| * complete reply, |
| * MHD_CONN_MUST_UPGRADE if connection must be upgraded. |
| */ |
| static enum MHD_ConnKeepAlive |
| keepalive_possible (struct MHD_Connection *connection) |
| { |
| struct MHD_Connection *const c = connection; /**< a short alias */ |
| struct MHD_Response *const r = c->rp.response; /**< a short alias */ |
| |
| mhd_assert (NULL != r); |
| if (MHD_CONN_MUST_CLOSE == c->keepalive) |
| return MHD_CONN_MUST_CLOSE; |
| |
| #ifdef UPGRADE_SUPPORT |
| /* TODO: Move below the next check when MHD stops closing connections |
| * when response is queued in first callback */ |
| if (NULL != r->upgrade_handler) |
| { |
| /* No "close" token is enforced by 'add_response_header_connection()' */ |
| mhd_assert (0 == (r->flags_auto & MHD_RAF_HAS_CONNECTION_CLOSE)); |
| /* Valid HTTP version is enforced by 'MHD_queue_response()' */ |
| mhd_assert (MHD_IS_HTTP_VER_SUPPORTED (c->rq.http_ver)); |
| mhd_assert (! c->stop_with_error); |
| return MHD_CONN_MUST_UPGRADE; |
| } |
| #endif /* UPGRADE_SUPPORT */ |
| |
| mhd_assert ( (! c->stop_with_error) || (c->discard_request)); |
| if ((c->read_closed) || (c->discard_request)) |
| return MHD_CONN_MUST_CLOSE; |
| |
| if (0 != (r->flags & MHD_RF_HTTP_1_0_COMPATIBLE_STRICT)) |
| return MHD_CONN_MUST_CLOSE; |
| if (0 != (r->flags_auto & MHD_RAF_HAS_CONNECTION_CLOSE)) |
| return MHD_CONN_MUST_CLOSE; |
| |
| if (! MHD_IS_HTTP_VER_SUPPORTED (c->rq.http_ver)) |
| return MHD_CONN_MUST_CLOSE; |
| |
| if (MHD_lookup_header_s_token_ci (c, |
| MHD_HTTP_HEADER_CONNECTION, |
| "close")) |
| return MHD_CONN_MUST_CLOSE; |
| |
| if ((MHD_HTTP_VER_1_0 == connection->rq.http_ver) || |
| (0 != (connection->rp.response->flags & MHD_RF_HTTP_1_0_SERVER))) |
| { |
| if (MHD_lookup_header_s_token_ci (connection, |
| MHD_HTTP_HEADER_CONNECTION, |
| "Keep-Alive")) |
| return MHD_CONN_USE_KEEPALIVE; |
| |
| return MHD_CONN_MUST_CLOSE; |
| } |
| |
| if (MHD_IS_HTTP_VER_1_1_COMPAT (c->rq.http_ver)) |
| return MHD_CONN_USE_KEEPALIVE; |
| |
| return MHD_CONN_MUST_CLOSE; |
| } |
| |
| |
| /** |
| * Produce time stamp. |
| * |
| * Result is NOT null-terminated. |
| * Result is always 29 bytes long. |
| * |
| * @param[out] date where to write the time stamp, with |
| * at least 29 bytes available space. |
| */ |
| static bool |
| get_date_str (char *date) |
| { |
| static const char *const days[] = { |
| "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" |
| }; |
| static const char *const mons[] = { |
| "Jan", "Feb", "Mar", "Apr", "May", "Jun", |
| "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" |
| }; |
| static const size_t buf_len = 29; |
| struct tm now; |
| time_t t; |
| const char *src; |
| #if ! defined(HAVE_C11_GMTIME_S) && ! defined(HAVE_W32_GMTIME_S) && \ |
| ! defined(HAVE_GMTIME_R) |
| struct tm *pNow; |
| #endif |
| |
| if ((time_t) -1 == time (&t)) |
| return false; |
| #if defined(HAVE_C11_GMTIME_S) |
| if (NULL == gmtime_s (&t, |
| &now)) |
| return false; |
| #elif defined(HAVE_W32_GMTIME_S) |
| if (0 != gmtime_s (&now, |
| &t)) |
| return false; |
| #elif defined(HAVE_GMTIME_R) |
| if (NULL == gmtime_r (&t, |
| &now)) |
| return false; |
| #else |
| pNow = gmtime (&t); |
| if (NULL == pNow) |
| return false; |
| now = *pNow; |
| #endif |
| |
| /* Day of the week */ |
| src = days[now.tm_wday % 7]; |
| date[0] = src[0]; |
| date[1] = src[1]; |
| date[2] = src[2]; |
| date[3] = ','; |
| date[4] = ' '; |
| /* Day of the month */ |
| if (2 != MHD_uint8_to_str_pad ((uint8_t) now.tm_mday, 2, |
| date + 5, buf_len - 5)) |
| return false; |
| date[7] = ' '; |
| /* Month */ |
| src = mons[now.tm_mon % 12]; |
| date[8] = src[0]; |
| date[9] = src[1]; |
| date[10] = src[2]; |
| date[11] = ' '; |
| /* Year */ |
| if (4 != MHD_uint16_to_str ((uint16_t) (1900 + now.tm_year), date + 12, |
| buf_len - 12)) |
| return false; |
| date[16] = ' '; |
| /* Time */ |
| MHD_uint8_to_str_pad ((uint8_t) now.tm_hour, 2, date + 17, buf_len - 17); |
| date[19] = ':'; |
| MHD_uint8_to_str_pad ((uint8_t) now.tm_min, 2, date + 20, buf_len - 20); |
| date[22] = ':'; |
| MHD_uint8_to_str_pad ((uint8_t) now.tm_sec, 2, date + 23, buf_len - 23); |
| date[25] = ' '; |
| date[26] = 'G'; |
| date[27] = 'M'; |
| date[28] = 'T'; |
| |
| return true; |
| } |
| |
| |
| /** |
| * Produce HTTP DATE header. |
| * Result is always 37 bytes long (plus one terminating null). |
| * |
| * @param[out] header where to write the header, with |
| * at least 38 bytes available space. |
| */ |
| static bool |
| get_date_header (char *header) |
| { |
| if (! get_date_str (header + 6)) |
| { |
| header[0] = 0; |
| return false; |
| } |
| header[0] = 'D'; |
| header[1] = 'a'; |
| header[2] = 't'; |
| header[3] = 'e'; |
| header[4] = ':'; |
| header[5] = ' '; |
| header[35] = '\r'; |
| header[36] = '\n'; |
| header[37] = 0; |
| return true; |
| } |
| |
| |
| /** |
| * Try growing the read buffer. We initially claim half the available |
| * buffer space for the read buffer (the other half being left for |
| * management data structures; the write buffer can in the end take |
| * virtually everything as the read buffer can be reduced to the |
| * minimum necessary at that point. |
| * |
| * @param connection the connection |
| * @param required set to 'true' if grow is required, i.e. connection |
| * will fail if no additional space is granted |
| * @return 'true' on success, 'false' on failure |
| */ |
| static bool |
| try_grow_read_buffer (struct MHD_Connection *connection, |
| bool required) |
| { |
| size_t new_size; |
| size_t avail_size; |
| void *rb; |
| |
| avail_size = MHD_pool_get_free (connection->pool); |
| if (0 == avail_size) |
| return false; /* No more space available */ |
| if (0 == connection->read_buffer_size) |
| new_size = avail_size / 2; /* Use half of available buffer for reading */ |
| else |
| { |
| size_t grow_size; |
| |
| grow_size = avail_size / 8; |
| if (MHD_BUF_INC_SIZE > grow_size) |
| { /* Shortage of space */ |
| if (! required) |
| return false; /* Grow is not mandatory, leave some space in pool */ |
| else |
| { |
| /* Shortage of space, but grow is mandatory */ |
| static const size_t small_inc = MHD_BUF_INC_SIZE / 8; |
| if (small_inc < avail_size) |
| grow_size = small_inc; |
| else |
| grow_size = avail_size; |
| } |
| } |
| new_size = connection->read_buffer_size + grow_size; |
| } |
| /* we can actually grow the buffer, do it! */ |
| rb = MHD_pool_reallocate (connection->pool, |
| connection->read_buffer, |
| connection->read_buffer_size, |
| new_size); |
| if (NULL == rb) |
| { |
| /* This should NOT be possible: we just computed 'new_size' so that |
| it should fit. If it happens, somehow our read buffer is not in |
| the right position in the pool, say because someone called |
| MHD_pool_allocate() without 'from_end' set to 'true'? Anyway, |
| should be investigated! (Ideally provide all data from |
| *pool and connection->read_buffer and new_size for debugging). */ |
| mhd_assert (0); |
| return false; |
| } |
| connection->read_buffer = rb; |
| mhd_assert (NULL != connection->read_buffer); |
| connection->read_buffer_size = new_size; |
| return true; |
| } |
| |
| |
| /** |
| * Shrink connection read buffer to the zero size of free space in the buffer |
| * @param connection the connection whose read buffer is being manipulated |
| */ |
| static void |
| connection_shrink_read_buffer (struct MHD_Connection *connection) |
| { |
| struct MHD_Connection *const c = connection; /**< a short alias */ |
| void *new_buf; |
| |
| if ((NULL == c->read_buffer) || (0 == c->read_buffer_size)) |
| { |
| mhd_assert (0 == c->read_buffer_size); |
| mhd_assert (0 == c->read_buffer_offset); |
| return; |
| } |
| |
| mhd_assert (c->read_buffer_offset <= c->read_buffer_size); |
| if (0 == c->read_buffer_offset) |
| { |
| MHD_pool_deallocate (c->pool, c->read_buffer, c->read_buffer_size); |
| c->read_buffer = NULL; |
| c->read_buffer_size = 0; |
| } |
| else |
| { |
| new_buf = MHD_pool_reallocate (c->pool, c->read_buffer, c->read_buffer_size, |
| c->read_buffer_offset); |
| mhd_assert (c->read_buffer == new_buf); |
| c->read_buffer = new_buf; |
| c->read_buffer_size = c->read_buffer_offset; |
| } |
| } |
| |
| |
| /** |
| * Allocate the maximum available amount of memory from MemoryPool |
| * for write buffer. |
| * @param connection the connection whose write buffer is being manipulated |
| * @return the size of free space in write buffer, may be smaller |
| * than requested size. |
| */ |
| static size_t |
| connection_maximize_write_buffer (struct MHD_Connection *connection) |
| { |
| struct MHD_Connection *const c = connection; /**< a short alias */ |
| struct MemoryPool *const pool = connection->pool; |
| void *new_buf; |
| size_t new_size; |
| size_t free_size; |
| |
| mhd_assert ((NULL != c->write_buffer) || (0 == c->write_buffer_size)); |
| mhd_assert (c->write_buffer_append_offset >= c->write_buffer_send_offset); |
| mhd_assert (c->write_buffer_size >= c->write_buffer_append_offset); |
| |
| free_size = MHD_pool_get_free (pool); |
| if (0 != free_size) |
| { |
| new_size = c->write_buffer_size + free_size; |
| /* This function must not move the buffer position. |
| * MHD_pool_reallocate () may return the new position only if buffer was |
| * allocated 'from_end' or is not the last allocation, |
| * which should not happen. */ |
| new_buf = MHD_pool_reallocate (pool, |
| c->write_buffer, |
| c->write_buffer_size, |
| new_size); |
| mhd_assert ((c->write_buffer == new_buf) || (NULL == c->write_buffer)); |
| c->write_buffer = new_buf; |
| c->write_buffer_size = new_size; |
| if (c->write_buffer_send_offset == c->write_buffer_append_offset) |
| { |
| /* All data have been sent, reset offsets to zero. */ |
| c->write_buffer_send_offset = 0; |
| c->write_buffer_append_offset = 0; |
| } |
| } |
| |
| return c->write_buffer_size - c->write_buffer_append_offset; |
| } |
| |
| |
| #if 0 /* disable unused function */ |
| /** |
| * Shrink connection write buffer to the size of unsent data. |
| * |
| * @note: The number of calls of this function should be limited to avoid extra |
| * zeroing of the memory. |
| * @param connection the connection whose write buffer is being manipulated |
| * @param connection the connection to manipulate write buffer |
| */ |
| static void |
| connection_shrink_write_buffer (struct MHD_Connection *connection) |
| { |
| struct MHD_Connection *const c = connection; /**< a short alias */ |
| struct MemoryPool *const pool = connection->pool; |
| void *new_buf; |
| |
| mhd_assert ((NULL != c->write_buffer) || (0 == c->write_buffer_size)); |
| mhd_assert (c->write_buffer_append_offset >= c->write_buffer_send_offset); |
| mhd_assert (c->write_buffer_size >= c->write_buffer_append_offset); |
| |
| if ( (NULL == c->write_buffer) || (0 == c->write_buffer_size)) |
| { |
| mhd_assert (0 == c->write_buffer_append_offset); |
| mhd_assert (0 == c->write_buffer_send_offset); |
| c->write_buffer = NULL; |
| return; |
| } |
| if (c->write_buffer_append_offset == c->write_buffer_size) |
| return; |
| |
| new_buf = MHD_pool_reallocate (pool, c->write_buffer, c->write_buffer_size, |
| c->write_buffer_append_offset); |
| mhd_assert ((c->write_buffer == new_buf) || \ |
| (0 == c->write_buffer_append_offset)); |
| c->write_buffer_size = c->write_buffer_append_offset; |
| if (0 == c->write_buffer_size) |
| c->write_buffer = NULL; |
| else |
| c->write_buffer = new_buf; |
| } |
| |
| |
| #endif /* unused function */ |
| |
| |
| /** |
| * Switch connection from recv mode to send mode. |
| * |
| * Current request header or body will not be read anymore, |
| * response must be assigned to connection. |
| * @param connection the connection to prepare for sending. |
| */ |
| static void |
| connection_switch_from_recv_to_send (struct MHD_Connection *connection) |
| { |
| /* Read buffer is not needed for this request, shrink it.*/ |
| connection_shrink_read_buffer (connection); |
| } |
| |
| |
| /** |
| * This enum type describes requirements for reply body and reply bode-specific |
| * headers (namely Content-Length, Transfer-Encoding). |
| */ |
| enum replyBodyUse |
| { |
| /** |
| * No reply body allowed. |
| * Reply body headers 'Content-Length:' or 'Transfer-Encoding: chunked' are |
| * not allowed as well. |
| */ |
| RP_BODY_NONE = 0, |
| |
| /** |
| * Do not send reply body. |
| * Reply body headers 'Content-Length:' or 'Transfer-Encoding: chunked' are |
| * allowed, but optional. |
| */ |
| RP_BODY_HEADERS_ONLY = 1, |
| |
| /** |
| * Send reply body and |
| * reply body headers 'Content-Length:' or 'Transfer-Encoding: chunked'. |
| * Reply body headers are required. |
| */ |
| RP_BODY_SEND = 2 |
| }; |
| |
| |
| /** |
| * Check whether reply body must be used. |
| * |
| * If reply body is needed, it could be zero-sized. |
| * |
| * @param connection the connection to check |
| * @param rcode the response code |
| * @return enum value indicating whether response body can be used and |
| * whether response body length headers are allowed or required. |
| * @sa is_reply_body_header_needed() |
| */ |
| static enum replyBodyUse |
| is_reply_body_needed (struct MHD_Connection *connection, |
| unsigned int rcode) |
| { |
| struct MHD_Connection *const c = connection; /**< a short alias */ |
| |
| mhd_assert (100 <= rcode); |
| mhd_assert (999 >= rcode); |
| |
| if (199 >= rcode) |
| return RP_BODY_NONE; |
| |
| if (MHD_HTTP_NO_CONTENT == rcode) |
| return RP_BODY_NONE; |
| |
| #if 0 |
| /* This check is not needed as upgrade handler is used only with code 101 */ |
| #ifdef UPGRADE_SUPPORT |
| if (NULL != rp.response->upgrade_handler) |
| return RP_BODY_NONE; |
| #endif /* UPGRADE_SUPPORT */ |
| #endif |
| |
| #if 0 |
| /* CONNECT is not supported by MHD */ |
| /* Successful responses for connect requests are filtered by |
| * MHD_queue_response() */ |
| if ( (MHD_HTTP_MTHD_CONNECT == c->rq.http_mthd) && |
| (2 == rcode / 100) ) |
| return false; /* Actually pass-through CONNECT is not supported by MHD */ |
| #endif |
| |
| /* Reply body headers could be used. |
| * Check whether reply body itself must be used. */ |
| |
| if (MHD_HTTP_MTHD_HEAD == c->rq.http_mthd) |
| return RP_BODY_HEADERS_ONLY; |
| |
| if (MHD_HTTP_NOT_MODIFIED == rcode) |
| return RP_BODY_HEADERS_ONLY; |
| |
| /* Reply body must be sent. The body may have zero length, but body size |
| * must be indicated by headers ('Content-Length:' or |
| * 'Transfer-Encoding: chunked'). */ |
| return RP_BODY_SEND; |
| } |
| |
| |
| /** |
| * Setup connection reply properties. |
| * |
| * Reply properties include presence of reply body, transfer-encoding |
| * type and other. |
| * |
| * @param connection to connection to process |
| */ |
| static void |
| setup_reply_properties (struct MHD_Connection *connection) |
| { |
| struct MHD_Connection *const c = connection; /**< a short alias */ |
| struct MHD_Response *const r = c->rp.response; /**< a short alias */ |
| enum replyBodyUse use_rp_body; |
| bool use_chunked; |
| |
| mhd_assert (NULL != r); |
| |
| /* ** Adjust reply properties ** */ |
| |
| c->keepalive = keepalive_possible (c); |
| use_rp_body = is_reply_body_needed (c, c->rp.responseCode); |
| c->rp.props.send_reply_body = (use_rp_body > RP_BODY_HEADERS_ONLY); |
| c->rp.props.use_reply_body_headers = (use_rp_body >= RP_BODY_HEADERS_ONLY); |
| |
| #ifdef UPGRADE_SUPPORT |
| mhd_assert ((NULL == r->upgrade_handler) || (RP_BODY_NONE == use_rp_body)); |
| #endif /* UPGRADE_SUPPORT */ |
| |
| if (c->rp.props.use_reply_body_headers) |
| { |
| if ((MHD_SIZE_UNKNOWN == r->total_size) || |
| (0 != (r->flags_auto & MHD_RAF_HAS_TRANS_ENC_CHUNKED))) |
| { /* Use chunked reply encoding if possible */ |
| |
| /* Check whether chunked encoding is supported by the client */ |
| if (! MHD_IS_HTTP_VER_1_1_COMPAT (c->rq.http_ver)) |
| use_chunked = false; |
| /* Check whether chunked encoding is allowed for the reply */ |
| else if (0 != (r->flags & (MHD_RF_HTTP_1_0_COMPATIBLE_STRICT |
| | MHD_RF_HTTP_1_0_SERVER))) |
| use_chunked = false; |
| else |
| /* If chunked encoding is supported and allowed, and response size |
| * is unknown, use chunked even for non-Keep-Alive connections. |
| * See https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.3 |
| * Also use chunked if it is enforced by application and supported by |
| * the client. */ |
| use_chunked = true; |
| } |
| else |
| use_chunked = false; |
| |
| if ( (MHD_SIZE_UNKNOWN == r->total_size) && ! use_chunked) |
| { |
| /* End of the stream is indicated by closure */ |
| c->keepalive = MHD_CONN_MUST_CLOSE; |
| } |
| } |
| else |
| use_chunked = false; /* chunked encoding cannot be used without body */ |
| |
| c->rp.props.chunked = use_chunked; |
| #ifdef _DEBUG |
| c->rp.props.set = true; |
| #endif /* _DEBUG */ |
| } |
| |
| |
| /** |
| * Check whether queued response is suitable for @a connection. |
| * @param connection to connection to check |
| */ |
| static void |
| check_connection_reply (struct MHD_Connection *connection) |
| { |
| struct MHD_Connection *const c = connection; /**< a short alias */ |
| struct MHD_Response *const r = c->rp.response; /**< a short alias */ |
| mhd_assert (c->rp.props.set); |
| |
| #ifdef HAVE_MESSAGES |
| if ((! c->rp.props.use_reply_body_headers) && (0 != r->total_size)) |
| { |
| MHD_DLOG (c->daemon, |
| _ ("This reply with response code %u cannot use reply body. " |
| "Non-empty response body is ignored and not used.\n"), |
| (unsigned) (c->rp.responseCode)); |
| } |
| if ( (! c->rp.props.use_reply_body_headers) && |
| (0 != (r->flags_auto & MHD_RAF_HAS_CONTENT_LENGTH)) ) |
| { |
| MHD_DLOG (c->daemon, |
| _ ("This reply with response code %u cannot use reply body. " |
| "Application defined \"Content-Length\" header violates" |
| "HTTP specification.\n"), |
| (unsigned) (c->rp.responseCode)); |
| } |
| #else |
| (void) c; /* Mute compiler warning */ |
| (void) r; /* Mute compiler warning */ |
| #endif |
| } |
| |
| |
| /** |
| * Append data to the buffer if enough space is available, |
| * update position. |
| * @param[out] buf the buffer to append data to |
| * @param[in,out] ppos the pointer to position in the @a buffer |
| * @param buf_size the size of the @a buffer |
| * @param append the data to append |
| * @param append_size the size of the @a append |
| * @return true if data has been added and position has been updated, |
| * false if not enough space is available |
| */ |
| static bool |
| buffer_append (char *buf, |
| size_t *ppos, |
| size_t buf_size, |
| const char *append, |
| size_t append_size) |
| { |
| mhd_assert (NULL != buf); /* Mute static analyzer */ |
| if (buf_size < *ppos + append_size) |
| return false; |
| memcpy (buf + *ppos, append, append_size); |
| *ppos += append_size; |
| return true; |
| } |
| |
| |
| /** |
| * Append static string to the buffer if enough space is available, |
| * update position. |
| * @param[out] buf the buffer to append data to |
| * @param[in,out] ppos the pointer to position in the @a buffer |
| * @param buf_size the size of the @a buffer |
| * @param str the static string to append |
| * @return true if data has been added and position has been updated, |
| * false if not enough space is available |
| */ |
| #define buffer_append_s(buf,ppos,buf_size,str) \ |
| buffer_append (buf,ppos,buf_size,str, MHD_STATICSTR_LEN_ (str)) |
| |
| |
| /** |
| * Add user-defined headers from response object to |
| * the text buffer. |
| * |
| * @param buf the buffer to add headers to |
| * @param ppos the pointer to the position in the @a buf |
| * @param buf_size the size of the @a buf |
| * @param response the response |
| * @param filter_transf_enc skip "Transfer-Encoding" header if any |
| * @param filter_content_len skip "Content-Length" header if any |
| * @param add_close add "close" token to the |
| * "Connection:" header (if any), ignored if no "Connection:" |
| * header was added by user or if "close" token is already |
| * present in "Connection:" header |
| * @param add_keep_alive add "Keep-Alive" token to the |
| * "Connection:" header (if any) |
| * @return true if succeed, |
| * false if buffer is too small |
| */ |
| static bool |
| add_user_headers (char *buf, |
| size_t *ppos, |
| size_t buf_size, |
| struct MHD_Response *response, |
| bool filter_transf_enc, |
| bool filter_content_len, |
| bool add_close, |
| bool add_keep_alive) |
| { |
| struct MHD_Response *const r = response; /**< a short alias */ |
| struct MHD_HTTP_Res_Header *hdr; /**< Iterates through User-specified headers */ |
| size_t el_size; /**< the size of current element to be added to the @a buf */ |
| |
| mhd_assert (! add_close || ! add_keep_alive); |
| |
| if (0 == (r->flags_auto & MHD_RAF_HAS_TRANS_ENC_CHUNKED)) |
| filter_transf_enc = false; /* No such header */ |
| if (0 == (r->flags_auto & MHD_RAF_HAS_CONTENT_LENGTH)) |
| filter_content_len = false; /* No such header */ |
| if (0 == (r->flags_auto & MHD_RAF_HAS_CONNECTION_HDR)) |
| { |
| add_close = false; /* No such header */ |
| add_keep_alive = false; /* No such header */ |
| } |
| else if (0 != (r->flags_auto & MHD_RAF_HAS_CONNECTION_CLOSE)) |
| add_close = false; /* "close" token was already set */ |
| |
| for (hdr = r->first_header; NULL != hdr; hdr = hdr->next) |
| { |
| size_t initial_pos = *ppos; |
| if (MHD_HEADER_KIND != hdr->kind) |
| continue; |
| if (filter_transf_enc) |
| { /* Need to filter-out "Transfer-Encoding" */ |
| if ((MHD_STATICSTR_LEN_ (MHD_HTTP_HEADER_TRANSFER_ENCODING) == |
| hdr->header_size) && |
| (MHD_str_equal_caseless_bin_n_ (MHD_HTTP_HEADER_TRANSFER_ENCODING, |
| hdr->header, hdr->header_size)) ) |
| { |
| filter_transf_enc = false; /* There is the only one such header */ |
| continue; /* Skip "Transfer-Encoding" header */ |
| } |
| } |
| if (filter_content_len) |
| { /* Need to filter-out "Content-Length" */ |
| if ((MHD_STATICSTR_LEN_ (MHD_HTTP_HEADER_CONTENT_LENGTH) == |
| hdr->header_size) && |
| (MHD_str_equal_caseless_bin_n_ (MHD_HTTP_HEADER_CONTENT_LENGTH, |
| hdr->header, hdr->header_size)) ) |
| { |
| /* Reset filter flag if only one header is allowed */ |
| filter_transf_enc = |
| (0 == (r->flags & MHD_RF_INSANITY_HEADER_CONTENT_LENGTH)); |
| continue; /* Skip "Content-Length" header */ |
| } |
| } |
| |
| /* Add user header */ |
| el_size = hdr->header_size + 2 + hdr->value_size + 2; |
| if (buf_size < *ppos + el_size) |
| return false; |
| memcpy (buf + *ppos, hdr->header, hdr->header_size); |
| (*ppos) += hdr->header_size; |
| buf[(*ppos)++] = ':'; |
| buf[(*ppos)++] = ' '; |
| if (add_close || add_keep_alive) |
| { |
| /* "Connection:" header must be always the first one */ |
| mhd_assert (MHD_str_equal_caseless_n_ (hdr->header, \ |
| MHD_HTTP_HEADER_CONNECTION, \ |
| hdr->header_size)); |
| |
| if (add_close) |
| { |
| el_size += MHD_STATICSTR_LEN_ ("close, "); |
| if (buf_size < initial_pos + el_size) |
| return false; |
| memcpy (buf + *ppos, "close, ", |
| MHD_STATICSTR_LEN_ ("close, ")); |
| *ppos += MHD_STATICSTR_LEN_ ("close, "); |
| } |
| else |
| { |
| el_size += MHD_STATICSTR_LEN_ ("Keep-Alive, "); |
| if (buf_size < initial_pos + el_size) |
| return false; |
| memcpy (buf + *ppos, "Keep-Alive, ", |
| MHD_STATICSTR_LEN_ ("Keep-Alive, ")); |
| *ppos += MHD_STATICSTR_LEN_ ("Keep-Alive, "); |
| } |
| add_close = false; |
| add_keep_alive = false; |
| } |
| if (0 != hdr->value_size) |
| memcpy (buf + *ppos, hdr->value, hdr->value_size); |
| *ppos += hdr->value_size; |
| buf[(*ppos)++] = '\r'; |
| buf[(*ppos)++] = '\n'; |
| mhd_assert (initial_pos + el_size == (*ppos)); |
| } |
| return true; |
| } |
| |
| |
| /** |
| * Allocate the connection's write buffer and fill it with all of the |
| * headers from the response. |
| * Required headers are added here. |
| * |
| * @param connection the connection |
| * @return #MHD_YES on success, #MHD_NO on failure (out of memory) |
| */ |
| static enum MHD_Result |
| build_header_response (struct MHD_Connection *connection) |
| { |
| struct MHD_Connection *const c = connection; /**< a short alias */ |
| struct MHD_Response *const r = c->rp.response; /**< a short alias */ |
| char *buf; /**< the output buffer */ |
| size_t pos; /**< append offset in the @a buf */ |
| size_t buf_size; /**< the size of the @a buf */ |
| size_t el_size; /**< the size of current element to be added to the @a buf */ |
| unsigned rcode; /**< the response code */ |
| bool use_conn_close; /**< Use "Connection: close" header */ |
| bool use_conn_k_alive; /**< Use "Connection: Keep-Alive" header */ |
| |
| mhd_assert (NULL != r); |
| |
| /* ** Adjust response properties ** */ |
| |
| setup_reply_properties (c); |
| |
| mhd_assert (c->rp.props.set); |
| mhd_assert ((MHD_CONN_MUST_CLOSE == c->keepalive) || \ |
| (MHD_CONN_USE_KEEPALIVE == c->keepalive) || \ |
| (MHD_CONN_MUST_UPGRADE == c->keepalive)); |
| #ifdef UPGRADE_SUPPORT |
| mhd_assert ((NULL == r->upgrade_handler) || \ |
| (MHD_CONN_MUST_UPGRADE == c->keepalive)); |
| #else /* ! UPGRADE_SUPPORT */ |
| mhd_assert (MHD_CONN_MUST_UPGRADE != c->keepalive); |
| #endif /* ! UPGRADE_SUPPORT */ |
| mhd_assert ((! c->rp.props.chunked) || c->rp.props.use_reply_body_headers); |
| mhd_assert ((! c->rp.props.send_reply_body) || \ |
| c->rp.props.use_reply_body_headers); |
| #ifdef UPGRADE_SUPPORT |
| mhd_assert (NULL == r->upgrade_handler || \ |
| ! c->rp.props.use_reply_body_headers); |
| #endif /* UPGRADE_SUPPORT */ |
| |
| check_connection_reply (c); |
| |
| rcode = (unsigned) c->rp.responseCode; |
| if (MHD_CONN_MUST_CLOSE == c->keepalive) |
| { |
| /* The closure of connection must be always indicated by header |
| * to avoid hung connections */ |
| use_conn_close = true; |
| use_conn_k_alive = false; |
| } |
| else if (MHD_CONN_USE_KEEPALIVE == c->keepalive) |
| { |
| use_conn_close = false; |
| /* Add "Connection: keep-alive" if request is HTTP/1.0 or |
| * if reply is HTTP/1.0 |
| * For HTTP/1.1 add header only if explicitly requested by app |
| * (by response flag), as "Keep-Alive" is default for HTTP/1.1. */ |
| if ((0 != (r->flags & MHD_RF_SEND_KEEP_ALIVE_HEADER)) || |
| (MHD_HTTP_VER_1_0 == c->rq.http_ver) || |
| (0 != (r->flags & MHD_RF_HTTP_1_0_SERVER))) |
| use_conn_k_alive = true; |
| else |
| use_conn_k_alive = false; |
| } |
| else |
| { |
| use_conn_close = false; |
| use_conn_k_alive = false; |
| } |
| |
| /* ** Actually build the response header ** */ |
| |
| /* Get all space available */ |
| connection_maximize_write_buffer (c); |
| buf = c->write_buffer; |
| pos = c->write_buffer_append_offset; |
| buf_size = c->write_buffer_size; |
| if (0 == buf_size) |
| return MHD_NO; |
| mhd_assert (NULL != buf); |
| |
| /* * The status line * */ |
| |
| /* The HTTP version */ |
| if (! c->rp.responseIcy) |
| { /* HTTP reply */ |
| if (0 == (r->flags & MHD_RF_HTTP_1_0_SERVER)) |
| { /* HTTP/1.1 reply */ |
| /* Use HTTP/1.1 responses for HTTP/1.0 clients. |
| * See https://datatracker.ietf.org/doc/html/rfc7230#section-2.6 */ |
| if (! buffer_append_s (buf, &pos, buf_size, MHD_HTTP_VERSION_1_1)) |
| return MHD_NO; |
| } |
| else |
| { /* HTTP/1.0 reply */ |
| if (! buffer_append_s (buf, &pos, buf_size, MHD_HTTP_VERSION_1_0)) |
| return MHD_NO; |
| } |
| } |
| else |
| { /* ICY reply */ |
| if (! buffer_append_s (buf, &pos, buf_size, "ICY")) |
| return MHD_NO; |
| } |
| |
| /* The response code */ |
| if (buf_size < pos + 5) /* space + code + space */ |
| return MHD_NO; |
| buf[pos++] = ' '; |
| pos += MHD_uint16_to_str ((uint16_t) rcode, buf + pos, |
| buf_size - pos); |
| buf[pos++] = ' '; |
| |
| /* The reason phrase */ |
| el_size = MHD_get_reason_phrase_len_for (rcode); |
| if (0 == el_size) |
| { |
| if (! buffer_append_s (buf, &pos, buf_size, "Non-Standard Status")) |
| return MHD_NO; |
| } |
| else if (! buffer_append (buf, &pos, buf_size, |
| MHD_get_reason_phrase_for (rcode), |
| el_size)) |
| return MHD_NO; |
| |
| /* The linefeed */ |
| if (buf_size < pos + 2) |
| return MHD_NO; |
| buf[pos++] = '\r'; |
| buf[pos++] = '\n'; |
| |
| /* * The headers * */ |
| |
| /* Main automatic headers */ |
| |
| /* The "Date:" header */ |
| if ( (0 == (r->flags_auto & MHD_RAF_HAS_DATE_HDR)) && |
| (0 == (c->daemon->options & MHD_USE_SUPPRESS_DATE_NO_CLOCK)) ) |
| { |
| /* Additional byte for unused zero-termination */ |
| if (buf_size < pos + 38) |
| return MHD_NO; |
| if (get_date_header (buf + pos)) |
| pos += 37; |
| } |
| /* The "Connection:" header */ |
| mhd_assert (! use_conn_close || ! use_conn_k_alive); |
| mhd_assert (! use_conn_k_alive || ! use_conn_close); |
| if (0 == (r->flags_auto & MHD_RAF_HAS_CONNECTION_HDR)) |
| { |
| if (use_conn_close) |
| { |
| if (! buffer_append_s (buf, &pos, buf_size, |
| MHD_HTTP_HEADER_CONNECTION ": close\r\n")) |
| return MHD_NO; |
| } |
| else if (use_conn_k_alive) |
| { |
| if (! buffer_append_s (buf, &pos, buf_size, |
| MHD_HTTP_HEADER_CONNECTION ": Keep-Alive\r\n")) |
| return MHD_NO; |
| } |
| } |
| |
| /* User-defined headers */ |
| |
| if (! add_user_headers (buf, &pos, buf_size, r, |
| ! c->rp.props.chunked, |
| (! c->rp.props.use_reply_body_headers) && |
| (0 == |
| (r->flags & MHD_RF_INSANITY_HEADER_CONTENT_LENGTH)), |
| use_conn_close, |
| use_conn_k_alive)) |
| return MHD_NO; |
| |
| /* Other automatic headers */ |
| |
| if ( (c->rp.props.use_reply_body_headers) && |
| (0 == (r->flags & MHD_RF_HEAD_ONLY_RESPONSE)) ) |
| { |
| /* Body-specific headers */ |
| |
| if (c->rp.props.chunked) |
| { /* Chunked encoding is used */ |
| if (0 == (r->flags_auto & MHD_RAF_HAS_TRANS_ENC_CHUNKED)) |
| { /* No chunked encoding header set by user */ |
| if (! buffer_append_s (buf, &pos, buf_size, |
| MHD_HTTP_HEADER_TRANSFER_ENCODING ": " \ |
| "chunked\r\n")) |
| return MHD_NO; |
| } |
| } |
| else /* Chunked encoding is not used */ |
| { |
| if (MHD_SIZE_UNKNOWN != r->total_size) |
| { /* The size is known */ |
| if (0 == (r->flags_auto & MHD_RAF_HAS_CONTENT_LENGTH)) |
| { /* The response does not have "Content-Length" header */ |
| if (! buffer_append_s (buf, &pos, buf_size, |
| MHD_HTTP_HEADER_CONTENT_LENGTH ": ")) |
| return MHD_NO; |
| el_size = MHD_uint64_to_str (r->total_size, buf + pos, |
| buf_size - pos); |
| if (0 == el_size) |
| return MHD_NO; |
| pos += el_size; |
| |
| if (buf_size < pos + 2) |
| return MHD_NO; |
| buf[pos++] = '\r'; |
| buf[pos++] = '\n'; |
| } |
| } |
| } |
| } |
| |
| /* * Header termination * */ |
| if (buf_size < pos + 2) |
| return MHD_NO; |
| buf[pos++] = '\r'; |
| buf[pos++] = '\n'; |
| |
| c->write_buffer_append_offset = pos; |
| return MHD_YES; |
| } |
| |
| |
| /** |
| * Allocate the connection's write buffer (if necessary) and fill it |
| * with response footers. |
| * Works only for chunked responses as other responses do not need |
| * and do not support any kind of footers. |
| * |
| * @param connection the connection |
| * @return #MHD_YES on success, #MHD_NO on failure (out of memory) |
| */ |
| static enum MHD_Result |
| build_connection_chunked_response_footer (struct MHD_Connection *connection) |
| { |
| char *buf; /**< the buffer to write footers to */ |
| size_t buf_size; /**< the size of the @a buf */ |
| size_t used_size; /**< the used size of the @a buf */ |
| struct MHD_Connection *const c = connection; /**< a short alias */ |
| struct MHD_HTTP_Res_Header *pos; |
| |
| mhd_assert (connection->rp.props.chunked); |
| /* TODO: allow combining of the final footer with the last chunk, |
| * modify the next assert. */ |
| mhd_assert (MHD_CONNECTION_CHUNKED_BODY_SENT == connection->state); |
| mhd_assert (NULL != c->rp.response); |
| |
| buf_size = connection_maximize_write_buffer (c); |
| /* '5' is the minimal size of chunked footer ("0\r\n\r\n") */ |
| if (buf_size < 5) |
| return MHD_NO; |
| mhd_assert (NULL != c->write_buffer); |
| buf = c->write_buffer + c->write_buffer_append_offset; |
| mhd_assert (NULL != buf); |
| used_size = 0; |
| buf[used_size++] = '0'; |
| buf[used_size++] = '\r'; |
| buf[used_size++] = '\n'; |
| |
| for (pos = c->rp.response->first_header; NULL != pos; pos = pos->next) |
| { |
| if (MHD_FOOTER_KIND == pos->kind) |
| { |
| size_t new_used_size; /* resulting size with this header */ |
| /* '4' is colon, space, linefeeds */ |
| new_used_size = used_size + pos->header_size + pos->value_size + 4; |
| if (new_used_size > buf_size) |
| return MHD_NO; |
| memcpy (buf + used_size, pos->header, pos->header_size); |
| used_size += pos->header_size; |
| buf[used_size++] = ':'; |
| buf[used_size++] = ' '; |
| memcpy (buf + used_size, pos->value, pos->value_size); |
| used_size += pos->value_size; |
| buf[used_size++] = '\r'; |
| buf[used_size++] = '\n'; |
| mhd_assert (used_size == new_used_size); |
| } |
| } |
| if (used_size + 2 > buf_size) |
| return MHD_NO; |
| buf[used_size++] = '\r'; |
| buf[used_size++] = '\n'; |
| |
| c->write_buffer_append_offset += used_size; |
| mhd_assert (c->write_buffer_append_offset <= c->write_buffer_size); |
| |
| return MHD_YES; |
| } |
| |
| |
| /** |
| * We encountered an error processing the request. |
| * Handle it properly by stopping to read data |
| * and sending the indicated response code and message. |
| * |
| * @param connection the connection |
| * @param status_code the response code to send (400, 413 or 414) |
| * @param message the error message to send |
| * @param message_len the length of the @a message |
| */ |
| static void |
| transmit_error_response_len (struct MHD_Connection *connection, |
| unsigned int status_code, |
| const char *message, |
| size_t message_len) |
| { |
| struct MHD_Response *response; |
| enum MHD_Result iret; |
| |
| mhd_assert (! connection->stop_with_error); /* Do not send error twice */ |
| if (connection->stop_with_error) |
| { /* Should not happen */ |
| if (MHD_CONNECTION_CLOSED > connection->state) |
| connection->state = MHD_CONNECTION_CLOSED; |
| |
| return; |
| } |
| connection->stop_with_error = true; |
| connection->discard_request = true; |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (connection->daemon, |
| _ ("Error processing request (HTTP response code is %u ('%s')). " \ |
| "Closing connection.\n"), |
| status_code, |
| message); |
| #endif |
| if (MHD_CONNECTION_START_REPLY < connection->state) |
| { |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (connection->daemon, |
| _ ("Too late to send an error response, " \ |
| "response is being sent already.\n"), |
| status_code, |
| message); |
| #endif |
| CONNECTION_CLOSE_ERROR (connection, |
| _ ("Too late for error response.")); |
| return; |
| } |
| /* TODO: remove when special error queue function is implemented */ |
| connection->state = MHD_CONNECTION_FULL_REQ_RECEIVED; |
| if (0 != connection->read_buffer_size) |
| { |
| /* Read buffer is not needed anymore, discard it |
| * to free some space for error response. */ |
| MHD_pool_deallocate (connection->pool, |
| connection->read_buffer, |
| connection->read_buffer_size); |
| connection->read_buffer = NULL; |
| connection->read_buffer_size = 0; |
| connection->read_buffer_offset = 0; |
| } |
| if (NULL != connection->rp.response) |
| { |
| MHD_destroy_response (connection->rp.response); |
| connection->rp.response = NULL; |
| } |
| response = MHD_create_response_from_buffer_static (message_len, |
| message); |
| if (NULL == response) |
| { |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (connection->daemon, |
| _ ("Failed to create error response.\n"), |
| status_code, |
| message); |
| #endif |
| /* can't even send a reply, at least close the connection */ |
| connection->state = MHD_CONNECTION_CLOSED; |
| return; |
| } |
| iret = MHD_queue_response (connection, |
| status_code, |
| response); |
| MHD_destroy_response (response); |
| if (MHD_NO == iret) |
| { |
| /* can't even send a reply, at least close the connection */ |
| CONNECTION_CLOSE_ERROR (connection, |
| _ ("Closing connection " \ |
| "(failed to queue error response).")); |
| return; |
| } |
| mhd_assert (NULL != connection->rp.response); |
| /* Do not reuse this connection. */ |
| connection->keepalive = MHD_CONN_MUST_CLOSE; |
| if (MHD_NO == build_header_response (connection)) |
| { |
| /* No memory. Release everything. */ |
| connection->rq.version = NULL; |
| connection->rq.method = NULL; |
| connection->rq.url = NULL; |
| connection->rq.url_len = 0; |
| connection->rq.last = NULL; |
| connection->rq.colon = NULL; |
| connection->rq.headers_received = NULL; |
| connection->rq.headers_received_tail = NULL; |
| connection->write_buffer = NULL; |
| connection->write_buffer_size = 0; |
| connection->write_buffer_send_offset = 0; |
| connection->write_buffer_append_offset = 0; |
| connection->read_buffer |
| = MHD_pool_reset (connection->pool, |
| NULL, |
| 0, |
| 0); |
| connection->read_buffer_size = 0; |
| |
| /* Retry with empty buffer */ |
| if (MHD_NO == build_header_response (connection)) |
| { |
| CONNECTION_CLOSE_ERROR (connection, |
| _ ("Closing connection " \ |
| "(failed to create error response header).")); |
| return; |
| } |
| } |
| connection->state = MHD_CONNECTION_HEADERS_SENDING; |
| } |
| |
| |
| /** |
| * Transmit static string as error response |
| */ |
| #define transmit_error_response_static(c, code, msg) \ |
| transmit_error_response_len (c, code, msg, MHD_STATICSTR_LEN_ (msg)) |
| |
| /** |
| * Update the 'event_loop_info' field of this connection based on the state |
| * that the connection is now in. May also close the connection or |
| * perform other updates to the connection if needed to prepare for |
| * the next round of the event loop. |
| * |
| * @param connection connection to get poll set for |
| */ |
| static void |
| MHD_connection_update_event_loop_info (struct MHD_Connection *connection) |
| { |
| /* Do not update states of suspended connection */ |
| if (connection->suspended) |
| return; /* States will be updated after resume. */ |
| #ifdef HTTPS_SUPPORT |
| if (MHD_TLS_CONN_NO_TLS != connection->tls_state) |
| { /* HTTPS connection. */ |
| switch (connection->tls_state) |
| { |
| case MHD_TLS_CONN_INIT: |
| connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ; |
| return; |
| case MHD_TLS_CONN_HANDSHAKING: |
| case MHD_TLS_CONN_WR_CLOSING: |
| if (0 == gnutls_record_get_direction (connection->tls_session)) |
| connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ; |
| else |
| connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; |
| return; |
| case MHD_TLS_CONN_CONNECTED: |
| break; /* Do normal processing */ |
| case MHD_TLS_CONN_WR_CLOSED: |
| case MHD_TLS_CONN_TLS_FAILED: |
| connection->event_loop_info = MHD_EVENT_LOOP_INFO_CLEANUP; |
| return; |
| case MHD_TLS_CONN_TLS_CLOSING: /* Not implemented yet */ |
| case MHD_TLS_CONN_TLS_CLOSED: /* Not implemented yet */ |
| case MHD_TLS_CONN_INVALID_STATE: |
| case MHD_TLS_CONN_NO_TLS: /* Not possible */ |
| default: |
| MHD_PANIC (_ ("Invalid TLS state value.\n")); |
| } |
| } |
| #endif /* HTTPS_SUPPORT */ |
| while (1) |
| { |
| #if DEBUG_STATES |
| MHD_DLOG (connection->daemon, |
| _ ("In function %s handling connection at state: %s\n"), |
| MHD_FUNC_, |
| MHD_state_to_string (connection->state)); |
| #endif |
| switch (connection->state) |
| { |
| case MHD_CONNECTION_INIT: |
| case MHD_CONNECTION_REQ_LINE_RECEIVING: |
| case MHD_CONNECTION_URL_RECEIVED: |
| case MHD_CONNECTION_HEADER_PART_RECEIVED: |
| /* while reading headers, we always grow the |
| read buffer if needed, no size-check required */ |
| if ( (connection->read_buffer_offset == connection->read_buffer_size) && |
| (! try_grow_read_buffer (connection, true)) ) |
| { |
| if (connection->rq.url != NULL) |
| transmit_error_response_static (connection, |
| MHD_HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE, |
| REQUEST_TOO_BIG); |
| else |
| transmit_error_response_static (connection, |
| MHD_HTTP_URI_TOO_LONG, |
| REQUEST_TOO_BIG); |
| continue; |
| } |
| if (! connection->discard_request) |
| connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ; |
| else |
| connection->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS; |
| break; |
| case MHD_CONNECTION_HEADERS_RECEIVED: |
| mhd_assert (0); |
| break; |
| case MHD_CONNECTION_HEADERS_PROCESSED: |
| mhd_assert (0); |
| break; |
| case MHD_CONNECTION_CONTINUE_SENDING: |
| connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; |
| break; |
| case MHD_CONNECTION_BODY_RECEIVING: |
| if (connection->read_buffer_offset == connection->read_buffer_size) |
| { |
| const bool internal_poll = (0 != (connection->daemon->options |
| & MHD_USE_INTERNAL_POLLING_THREAD)); |
| if ( (! try_grow_read_buffer (connection, true)) && |
| internal_poll) |
| { |
| /* failed to grow the read buffer, and the |
| client which is supposed to handle the |
| received data in a *blocking* fashion |
| (in this mode) did not handle the data as |
| it was supposed to! |
| => we would either have to do busy-waiting |
| (on the client, which would likely fail), |
| or if we do nothing, we would just timeout |
| on the connection (if a timeout is even |
| set!). |
| Solution: we kill the connection with an error */ |
| transmit_error_response_static (connection, |
| MHD_HTTP_INTERNAL_SERVER_ERROR, |
| INTERNAL_ERROR); |
| continue; |
| } |
| } |
| if (connection->discard_request) |
| connection->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS; |
| else if (connection->read_buffer_offset == connection->read_buffer_size) |
| connection->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS; |
| else if (0 == connection->read_buffer_offset) |
| connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ; |
| else if (connection->rq.some_payload_processed) |
| connection->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS_READ; |
| else |
| connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ; |
| break; |
| case MHD_CONNECTION_BODY_RECEIVED: |
| case MHD_CONNECTION_FOOTER_PART_RECEIVED: |
| /* while reading footers, we always grow the |
| read buffer if needed, no size-check required */ |
| if (connection->read_closed) |
| { |
| CONNECTION_CLOSE_ERROR (connection, |
| NULL); |
| continue; |
| } |
| connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ; |
| /* transition to FOOTERS_RECEIVED |
| happens in read handler */ |
| break; |
| case MHD_CONNECTION_FOOTERS_RECEIVED: |
| mhd_assert (0); |
| break; |
| case MHD_CONNECTION_FULL_REQ_RECEIVED: |
| connection->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS; |
| break; |
| case MHD_CONNECTION_START_REPLY: |
| mhd_assert (0); |
| break; |
| case MHD_CONNECTION_HEADERS_SENDING: |
| /* headers in buffer, keep writing */ |
| connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; |
| break; |
| case MHD_CONNECTION_HEADERS_SENT: |
| mhd_assert (0); |
| break; |
| case MHD_CONNECTION_NORMAL_BODY_READY: |
| connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; |
| break; |
| case MHD_CONNECTION_NORMAL_BODY_UNREADY: |
| connection->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS; |
| break; |
| case MHD_CONNECTION_CHUNKED_BODY_READY: |
| connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; |
| break; |
| case MHD_CONNECTION_CHUNKED_BODY_UNREADY: |
| connection->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS; |
| break; |
| case MHD_CONNECTION_CHUNKED_BODY_SENT: |
| mhd_assert (0); |
| break; |
| case MHD_CONNECTION_FOOTERS_SENDING: |
| connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; |
| break; |
| case MHD_CONNECTION_FULL_REPLY_SENT: |
| mhd_assert (0); |
| break; |
| case MHD_CONNECTION_CLOSED: |
| connection->event_loop_info = MHD_EVENT_LOOP_INFO_CLEANUP; |
| return; /* do nothing, not even reading */ |
| #ifdef UPGRADE_SUPPORT |
| case MHD_CONNECTION_UPGRADE: |
| mhd_assert (0); |
| break; |
| #endif /* UPGRADE_SUPPORT */ |
| default: |
| mhd_assert (0); |
| } |
| break; |
| } |
| } |
| |
| |
| /** |
| * Parse a single line of the HTTP header. Advance read_buffer (!) |
| * appropriately. If the current line does not fit, consider growing |
| * the buffer. If the line is far too long, close the connection. If |
| * no line is found (incomplete, buffer too small, line too long), |
| * return NULL. Otherwise return a pointer to the line. |
| * |
| * @param connection connection we're processing |
| * @param[out] line_len pointer to variable that receive |
| * length of line or NULL |
| * @return NULL if no full line is available; note that the returned |
| * string will not be 0-termianted |
| */ |
| static char * |
| get_next_header_line (struct MHD_Connection *connection, |
| size_t *line_len) |
| { |
| char *rbuf; |
| size_t pos; |
| |
| if (0 == connection->read_buffer_offset) |
| return NULL; |
| pos = 0; |
| rbuf = connection->read_buffer; |
| mhd_assert (NULL != rbuf); |
| |
| do |
| { |
| const char c = rbuf[pos]; |
| bool found; |
| found = false; |
| if ( ('\r' == c) && (pos < connection->read_buffer_offset - 1) && |
| ('\n' == rbuf[pos + 1]) ) |
| { /* Found CRLF */ |
| found = true; |
| if (line_len) |
| *line_len = pos; |
| rbuf[pos++] = 0; /* Replace CR with zero */ |
| rbuf[pos++] = 0; /* Replace LF with zero */ |
| } |
| else if ('\n' == c) /* TODO: Add MHD option to disallow */ |
| { /* Found bare LF */ |
| found = true; |
| if (line_len) |
| *line_len = pos; |
| rbuf[pos++] = 0; /* Replace LF with zero */ |
| } |
| if (found) |
| { |
| connection->read_buffer += pos; |
| connection->read_buffer_size -= pos; |
| connection->read_buffer_offset -= pos; |
| return rbuf; |
| } |
| } while (++pos < connection->read_buffer_offset); |
| |
| /* not found, consider growing... */ |
| if ( (connection->read_buffer_offset == connection->read_buffer_size) && |
| (! try_grow_read_buffer (connection, true)) ) |
| { |
| if (NULL != connection->rq.url) |
| transmit_error_response_static (connection, |
| MHD_HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE, |
| REQUEST_TOO_BIG); |
| else |
| transmit_error_response_static (connection, |
| MHD_HTTP_URI_TOO_LONG, |
| REQUEST_TOO_BIG); |
| } |
| if (line_len) |
| *line_len = 0; |
| return NULL; |
| } |
| |
| |
| /** |
| * Add an entry to the HTTP headers of a connection. If this fails, |
| * transmit an error response (request too big). |
| * |
| * @param cls the context (connection) |
| * @param kind kind of the value |
| * @param key key for the value |
| * @param key_size number of bytes in @a key |
| * @param value the value itself |
| * @param value_size number of bytes in @a value |
| * @return #MHD_NO on failure (out of memory), #MHD_YES for success |
| */ |
| static enum MHD_Result |
| connection_add_header (void *cls, |
| const char *key, |
| size_t key_size, |
| const char *value, |
| size_t value_size, |
| enum MHD_ValueKind kind) |
| { |
| struct MHD_Connection *connection = (struct MHD_Connection *) cls; |
| if (MHD_NO == |
| MHD_set_connection_value_n (connection, |
| kind, |
| key, |
| key_size, |
| value, |
| value_size)) |
| { |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (connection->daemon, |
| _ ("Not enough memory in pool to allocate header record!\n")); |
| #endif |
| transmit_error_response_static (connection, |
| MHD_HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE, |
| REQUEST_TOO_BIG); |
| return MHD_NO; |
| } |
| return MHD_YES; |
| } |
| |
| |
| #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 enum _MHD_ParseCookie |
| parse_cookies_string (char *str, |
| const size_t str_len, |
| struct MHD_Connection *connection) |
| { |
| size_t i; |
| bool non_strict; |
| /* Skip extra whitespaces and empty cookies */ |
| const bool allow_wsp_empty = (0 >= connection->daemon->client_discipline); |
| /* Allow whitespaces around '=' character */ |
| const bool wsp_around_eq = (-3 >= connection->daemon->client_discipline); |
| /* Allow whitespaces in quoted cookie value */ |
| const bool wsp_in_quoted = (-2 >= connection->daemon->client_discipline); |
| /* Allow tab as space after semicolon between cookies */ |
| const bool tab_as_sp = (0 >= connection->daemon->client_discipline); |
| /* Allow no space after semicolon between cookies */ |
| const bool allow_no_space = (0 >= connection->daemon->client_discipline); |
| |
| 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; |
| #ifdef _DEBUG |
| 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) |
| { |
| mhd_assert (value_start + value_len <= str_len); |
| str[value_start + value_len] = 0; /* Zero-terminate the value */ |
| if (MHD_NO == |
| MHD_set_connection_value_n_nocheck_ (connection, |
| MHD_COOKIE_KIND, |
| str + name_start, |
| name_len, |
| str + value_start, |
| value_len)) |
| return MHD_PARSE_COOKIE_NO_MEMORY; |
| } |
| else |
| { |
| if (MHD_NO == |
| MHD_set_connection_value_n_nocheck_ (connection, |
| MHD_COOKIE_KIND, |
| str + name_start, |
| name_len, |
| "", |
| 0)) |
| 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) |
| { |
| const char *hdr; |
| size_t hdr_len; |
| char *cpy; |
| size_t i; |
| enum _MHD_ParseCookie parse_res; |
| struct MHD_HTTP_Req_Header *const saved_tail = |
| connection->rq.headers_received_tail; |
| const bool allow_partially_correct_cookie = |
| (1 >= connection->daemon->client_discipline); |
| |
| if (MHD_NO == |
| MHD_lookup_connection_value_n (connection, |
| MHD_HEADER_KIND, |
| MHD_HTTP_HEADER_COOKIE, |
| MHD_STATICSTR_LEN_ ( |
| MHD_HTTP_HEADER_COOKIE), |
| &hdr, |
| &hdr_len)) |
| return MHD_PARSE_COOKIE_OK; |
| if (0 == hdr_len) |
| return MHD_PARSE_COOKIE_OK; |
| |
| cpy = MHD_connection_alloc_memory_ (connection, |
| hdr_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: |
| #ifdef HAVE_MESSAGES |
| if (saved_tail != connection->rq.headers_received_tail) |
| { |
| if (allow_partially_correct_cookie) |
| MHD_DLOG (connection->daemon, |
| _ ("The Cookie header has been only partially parsed as it " |
| "contains malformed data.\n")); |
| else |
| { |
| /* 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; |
| MHD_DLOG (connection->daemon, |
| _ ("The Cookie header has been ignored as it contains " |
| "malformed data.\n")); |
| } |
| } |
| 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 */ |
| |
| |
| /** |
| * The valid length of any HTTP version string |
| */ |
| #define HTTP_VER_LEN (MHD_STATICSTR_LEN_ (MHD_HTTP_VERSION_1_1)) |
| |
| /** |
| * 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 #MHD_YES if HTTP version is correct and supported, |
| * #MHD_NO if HTTP version is not correct or unsupported. |
| */ |
| static enum MHD_Result |
| parse_http_version (struct MHD_Connection *connection, |
| const char *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]) || |
| (('0' > h[5]) || ('9' < h[5])) || |
| (('0' > h[7]) || ('9' < h[7]))) |
| { |
| connection->rq.http_ver = MHD_HTTP_VER_INVALID; |
| transmit_error_response_static (connection, |
| MHD_HTTP_BAD_REQUEST, |
| REQUEST_MALFORMED); |
| return MHD_NO; |
| } |
| if (1 == h[5] - '0') |
| { |
| /* HTTP/1.x */ |
| if (1 == h[7] - '0') |
| connection->rq.http_ver = MHD_HTTP_VER_1_1; |
| else if (0 == h[7] - '0') |
| connection->rq.http_ver = MHD_HTTP_VER_1_0; |
| else |
| connection->rq.http_ver = MHD_HTTP_VER_1_2__1_9; |
| |
| return MHD_YES; |
| } |
| |
| if (0 == h[5] - '0') |
| { |
| /* Too old major version */ |
| connection->rq.http_ver = MHD_HTTP_VER_TOO_OLD; |
| transmit_error_response_static (connection, |
| MHD_HTTP_HTTP_VERSION_NOT_SUPPORTED, |
| REQ_HTTP_VER_IS_TOO_OLD); |
| return MHD_NO; |
| } |
| |
| connection->rq.http_ver = MHD_HTTP_VER_FUTURE; |
| transmit_error_response_static (connection, |
| MHD_HTTP_HTTP_VERSION_NOT_SUPPORTED, |
| REQ_HTTP_VER_IS_NOT_SUPPORTED); |
| return MHD_NO; |
| } |
| |
| |
| /** |
| * Detect standard HTTP request method |
| * |
| * @param connection the connection |
| * @param method the pointer to HTTP request method string |
| * @param len the length of @a method in bytes |
| * @return #MHD_YES if HTTP method is valid string, |
| * #MHD_NO if HTTP method string is not valid. |
| */ |
| static enum MHD_Result |
| parse_http_std_method (struct MHD_Connection *connection, |
| const char *method, |
| size_t len) |
| { |
| const char *const m = method; /**< short alias */ |
| mhd_assert (NULL != m); |
| |
| if (0 == len) |
| return MHD_NO; |
| |
| if ((MHD_STATICSTR_LEN_ (MHD_HTTP_METHOD_GET) == len) && |
| (0 == memcmp (m, MHD_HTTP_METHOD_GET, len))) |
| connection->rq.http_mthd = MHD_HTTP_MTHD_GET; |
| else if ((MHD_STATICSTR_LEN_ (MHD_HTTP_METHOD_HEAD) == len) && |
| (0 == memcmp (m, MHD_HTTP_METHOD_HEAD, len))) |
| connection->rq.http_mthd = MHD_HTTP_MTHD_HEAD; |
| else if ((MHD_STATICSTR_LEN_ (MHD_HTTP_METHOD_POST) == len) && |
| (0 == memcmp (m, MHD_HTTP_METHOD_POST, len))) |
| connection->rq.http_mthd = MHD_HTTP_MTHD_POST; |
| else if ((MHD_STATICSTR_LEN_ (MHD_HTTP_METHOD_PUT) == len) && |
| (0 == memcmp (m, MHD_HTTP_METHOD_PUT, len))) |
| connection->rq.http_mthd = MHD_HTTP_MTHD_PUT; |
| else if ((MHD_STATICSTR_LEN_ (MHD_HTTP_METHOD_DELETE) == len) && |
| (0 == memcmp (m, MHD_HTTP_METHOD_DELETE, len))) |
| connection->rq.http_mthd = MHD_HTTP_MTHD_DELETE; |
| else if ((MHD_STATICSTR_LEN_ (MHD_HTTP_METHOD_CONNECT) == len) && |
| (0 == memcmp (m, MHD_HTTP_METHOD_CONNECT, len))) |
| connection->rq.http_mthd = MHD_HTTP_MTHD_CONNECT; |
| else if ((MHD_STATICSTR_LEN_ (MHD_HTTP_METHOD_OPTIONS) == len) && |
| (0 == memcmp (m, MHD_HTTP_METHOD_OPTIONS, len))) |
| connection->rq.http_mthd = MHD_HTTP_MTHD_OPTIONS; |
| else if ((MHD_STATICSTR_LEN_ (MHD_HTTP_METHOD_TRACE) == len) && |
| (0 == memcmp (m, MHD_HTTP_METHOD_TRACE, len))) |
| connection->rq.http_mthd = MHD_HTTP_MTHD_TRACE; |
| else |
| connection->rq.http_mthd = MHD_HTTP_MTHD_OTHER; |
| |
| /* Any method string with non-zero length is valid */ |
| return MHD_YES; |
| } |
| |
| |
| /** |
| * Parse the first line of the HTTP HEADER. |
| * |
| * @param connection the connection (updated) |
| * @param line the first line, not 0-terminated |
| * @param line_len length of the first @a line |
| * @return #MHD_YES if the line is ok, #MHD_NO if it is malformed |
| */ |
| static enum MHD_Result |
| parse_initial_message_line (struct MHD_Connection *connection, |
| char *line, |
| size_t line_len) |
| { |
| struct MHD_Daemon *daemon = connection->daemon; |
| const char *curi; |
| char *uri; |
| char *http_version; |
| char *args; |
| |
| if (NULL == (uri = memchr (line, |
| ' ', |
| line_len))) |
| return MHD_NO; /* serious error */ |
| uri[0] = '\0'; |
| connection->rq.method = line; |
| if (MHD_NO == parse_http_std_method (connection, connection->rq.method, |
| (size_t) (uri - line))) |
| return MHD_NO; |
| uri++; |
| /* Skip any spaces. Not required by standard but allow |
| to be more tolerant. */ |
| /* TODO: do not skip them in standard mode */ |
| while ( (' ' == uri[0]) && |
| ( (size_t) (uri - line) < line_len) ) |
| uri++; |
| if ((size_t) (uri - line) == line_len) |
| { |
| /* No URI and no http version given */ |
| curi = ""; |
| uri = NULL; |
| connection->rq.version = ""; |
| args = NULL; |
| if (MHD_NO == parse_http_version (connection, connection->rq.version, 0)) |
| return MHD_NO; |
| } |
| else |
| { |
| size_t uri_len; |
| curi = uri; |
| /* Search from back to accept malformed URI with space */ |
| http_version = line + line_len - 1; |
| /* Skip any trailing spaces */ |
| /* TODO: do not skip them in standard mode */ |
| while ( (' ' == http_version[0]) && |
| (http_version > uri) ) |
| http_version--; |
| /* Find first space in reverse direction */ |
| while ( (' ' != http_version[0]) && |
| (http_version > uri) ) |
| http_version--; |
| if (http_version > uri) |
| { |
| /* http_version points to character before HTTP version string */ |
| http_version[0] = '\0'; |
| connection->rq.version = http_version + 1; |
| if (MHD_NO == parse_http_version (connection, connection->rq.version, |
| line_len |
| - (size_t) |
| (connection->rq.version - line))) |
| return MHD_NO; |
| uri_len = (size_t) (http_version - uri); |
| } |
| else |
| { |
| connection->rq.version = ""; |
| if (MHD_NO == parse_http_version (connection, connection->rq.version, 0)) |
| return MHD_NO; |
| uri_len = line_len - (size_t) (uri - line); |
| } |
| /* check for spaces in URI if we are "strict" */ |
| if ( (-2 < daemon->client_discipline) && |
| (NULL != memchr (uri, |
| ' ', |
| uri_len)) ) |
| { |
| /* space exists in URI and we are supposed to be strict, reject */ |
| return MHD_NO; |
| } |
| |
| args = memchr (uri, |
| '?', |
| uri_len); |
| } |
| |
| /* log callback before we modify URI *or* args */ |
| if (NULL != daemon->uri_log_callback) |
| { |
| connection->rq.client_aware = true; |
| connection->rq.client_context |
| = daemon->uri_log_callback (daemon->uri_log_callback_cls, |
| uri, |
| connection); |
| } |
| |
| if (NULL != args) |
| { |
| args[0] = '\0'; |
| args++; |
| /* note that this call clobbers 'args' */ |
| MHD_parse_arguments_ (connection, |
| MHD_GET_ARGUMENT_KIND, |
| args, |
| &connection_add_header, |
| connection); |
| } |
| |
| /* unescape URI *after* searching for arguments and log callback */ |
| if (NULL != uri) |
| { |
| connection->rq.url_len = |
| daemon->unescape_callback (daemon->unescape_callback_cls, |
| connection, |
| uri); |
| } |
| else |
| connection->rq.url_len = 0; |
| |
| connection->rq.url = curi; |
| return MHD_YES; |
| } |
| |
| |
| /** |
| * Call the handler of the application for this |
| * connection. Handles chunking of the upload |
| * as well as normal uploads. |
| * |
| * @param connection connection we're processing |
| */ |
| static void |
| call_connection_handler (struct MHD_Connection *connection) |
| { |
| struct MHD_Daemon *daemon = connection->daemon; |
| size_t processed; |
| |
| if (NULL != connection->rp.response) |
| return; /* already queued a response */ |
| processed = 0; |
| connection->rq.client_aware = true; |
| if (MHD_NO == |
| daemon->default_handler (daemon->default_handler_cls, |
| connection, |
| connection->rq.url, |
| connection->rq.method, |
| connection->rq.version, |
| NULL, |
| &processed, |
| &connection->rq.client_context)) |
| { |
| /* serious internal error, close connection */ |
| CONNECTION_CLOSE_ERROR (connection, |
| _ ("Application reported internal error, " \ |
| "closing connection.")); |
| return; |
| } |
| } |
| |
| |
| /** |
| * Call the handler of the application for this |
| * connection. Handles chunking of the upload |
| * as well as normal uploads. |
| * |
| * @param connection connection we're processing |
| */ |
| static void |
| process_request_body (struct MHD_Connection *connection) |
| { |
| struct MHD_Daemon *daemon = connection->daemon; |
| size_t available; |
| bool instant_retry; |
| char *buffer_head; |
| |
| connection->rq.some_payload_processed = false; |
| |
| if (NULL != connection->rp.response) |
| { |
| /* TODO: discard all read buffer as early response |
| * means that connection have to be closed. */ |
| /* already queued a response, discard remaining upload |
| (but not more, there might be another request after it) */ |
| size_t purge; |
| |
| purge = (size_t) MHD_MIN (connection->rq.remaining_upload_size, |
| (uint64_t) connection->read_buffer_offset); |
| connection->rq.remaining_upload_size -= purge; |
| if (connection->read_buffer_offset > purge) |
| memmove (connection->read_buffer, |
| &connection->read_buffer[purge], |
| connection->read_buffer_offset - purge); |
| connection->read_buffer_offset -= purge; |
| return; |
| } |
| |
| buffer_head = connection->read_buffer; |
| available = connection->read_buffer_offset; |
| do |
| { |
| size_t to_be_processed; |
| size_t left_unprocessed; |
| size_t processed_size; |
| |
| instant_retry = false; |
| if (connection->rq.have_chunked_upload) |
| { |
| mhd_assert (MHD_SIZE_UNKNOWN == connection->rq.remaining_upload_size); |
| if ( (connection->rq.current_chunk_offset == |
| connection->rq.current_chunk_size) && |
| (0 != connection->rq.current_chunk_size) ) |
| { |
| size_t i; |
| mhd_assert (0 != available); |
| /* skip new line at the *end* of a chunk */ |
| i = 0; |
| if ( (2 <= available) && |
| ('\r' == buffer_head[0]) && |
| ('\n' == buffer_head[1]) ) |
| i += 2; /* skip CRLF */ |
| else if ('\n' == buffer_head[0]) /* TODO: Add MHD option to disallow */ |
| i++; /* skip bare LF */ |
| else if (2 > available) |
| break; /* need more upload data */ |
| if (0 == i) |
| { |
| /* malformed encoding */ |
| transmit_error_response_static (connection, |
| MHD_HTTP_BAD_REQUEST, |
| REQUEST_CHUNKED_MALFORMED); |
| return; |
| } |
| available -= i; |
| buffer_head += i; |
| connection->rq.current_chunk_offset = 0; |
| connection->rq.current_chunk_size = 0; |
| if (0 == available) |
| break; |
| } |
| if (0 != connection->rq.current_chunk_size) |
| { |
| uint64_t cur_chunk_left; |
| mhd_assert (connection->rq.current_chunk_offset < \ |
| connection->rq.current_chunk_size); |
| /* we are in the middle of a chunk, give |
| as much as possible to the client (without |
| crossing chunk boundaries) */ |
| cur_chunk_left |
| = connection->rq.current_chunk_size |
| - connection->rq.current_chunk_offset; |
| if (cur_chunk_left > available) |
| to_be_processed = available; |
| else |
| { /* cur_chunk_left <= (size_t)available */ |
| to_be_processed = (size_t) cur_chunk_left; |
| if (available > to_be_processed) |
| instant_retry = true; |
| } |
| } |
| else |
| { |
| size_t i; |
| /** The length of the string with the number of the chunk size */ |
| size_t chunk_size_len; |
| bool found_chunk_size_str; |
| bool malformed; |
| |
| /* we need to read chunk boundaries */ |
| i = 0; |
| found_chunk_size_str = false; |
| chunk_size_len = 0; |
| mhd_assert (0 != available); |
| do |
| { |
| if ('\n' == buffer_head[i]) |
| { |
| if ((0 < i) && ('\r' == buffer_head[i - 1])) |
| { /* CRLF */ |
| if (! found_chunk_size_str) |
| chunk_size_len = i - 1; |
| } |
| else |
| { /* bare LF */ |
| /* TODO: Add an option to disallow bare LF */ |
| if (! found_chunk_size_str) |
| chunk_size_len = i; |
| } |
| found_chunk_size_str = true; |
| break; /* Found the end of the string */ |
| } |
| else if (! found_chunk_size_str && (';' == buffer_head[i])) |
| { /* Found chunk extension */ |
| chunk_size_len = i; |
| found_chunk_size_str = true; |
| } |
| } while (available > ++i); |
| mhd_assert ((i == available) || found_chunk_size_str); |
| mhd_assert ((0 == chunk_size_len) || found_chunk_size_str); |
| malformed = ((0 == chunk_size_len) && found_chunk_size_str); |
| if (! malformed) |
| { |
| /* Check whether size is valid hexadecimal number |
| * even if end of the string is not found yet. */ |
| size_t num_dig; |
| uint64_t chunk_size; |
| mhd_assert (0 < i); |
| if (! found_chunk_size_str) |
| { |
| mhd_assert (i == available); |
| /* Check already available part of the size string for valid |
| * hexadecimal digits. */ |
| chunk_size_len = i; |
| if ('\r' == buffer_head[i - 1]) |
| { |
| chunk_size_len--; |
| malformed = (0 == chunk_size_len); |
| } |
| } |
| num_dig = MHD_strx_to_uint64_n_ (buffer_head, |
| chunk_size_len, |
| &chunk_size); |
| malformed = malformed || (chunk_size_len != num_dig); |
| |
| if ((available != i) && ! malformed) |
| { |
| /* Found end of the string and the size of the chunk is valid */ |
| |
| mhd_assert (found_chunk_size_str); |
| /* Start reading payload data of the chunk */ |
| connection->rq.current_chunk_offset = 0; |
| connection->rq.current_chunk_size = chunk_size; |
| i++; /* Consume the last checked char */ |
| available -= i; |
| buffer_head += i; |
| |
| if (0 == connection->rq.current_chunk_size) |
| { /* The final (termination) chunk */ |
| connection->rq.remaining_upload_size = 0; |
| break; |
| } |
| if (available > 0) |
| instant_retry = true; |
| continue; |
| } |
| |
| if ((0 == num_dig) && (0 != chunk_size_len)) |
| { /* Check whether result is invalid due to uint64_t overflow */ |
| /* At least one byte is always available |
| * in the input buffer here. */ |
| const char d = buffer_head[0]; /**< first digit */ |
| if ((('0' <= d) && ('9' >= d)) || |
| (('A' <= d) && ('F' >= d)) || |
| (('a' <= d) && ('f' >= d))) |
| { /* The first char is a valid hexadecimal digit */ |
| transmit_error_response_static (connection, |
| MHD_HTTP_CONTENT_TOO_LARGE, |
| REQUEST_CHUNK_TOO_LARGE); |
| return; |
| } |
| } |
| } |
| if (malformed) |
| { |
| transmit_error_response_static (connection, |
| MHD_HTTP_BAD_REQUEST, |
| REQUEST_CHUNKED_MALFORMED); |
| return; |
| } |
| mhd_assert (available == i); |
| break; /* The end of the string not found, need more upload data */ |
| } |
| } |
| else |
| { |
| /* no chunked encoding, give all to the client */ |
| mhd_assert (MHD_SIZE_UNKNOWN != connection->rq.remaining_upload_size); |
| mhd_assert (0 != connection->rq.remaining_upload_size); |
| if (connection->rq.remaining_upload_size < available) |
| to_be_processed = (size_t) connection->rq.remaining_upload_size; |
| else |
| to_be_processed = available; |
| } |
| left_unprocessed = to_be_processed; |
| connection->rq.client_aware = true; |
| if (MHD_NO == |
| daemon->default_handler (daemon->default_handler_cls, |
| connection, |
| connection->rq.url, |
| connection->rq.method, |
| connection->rq.version, |
| buffer_head, |
| &left_unprocessed, |
| &connection->rq.client_context)) |
| { |
| /* serious internal error, close connection */ |
| CONNECTION_CLOSE_ERROR (connection, |
| _ ("Application reported internal error, " \ |
| "closing connection.")); |
| return; |
| } |
| if (left_unprocessed > to_be_processed) |
| MHD_PANIC (_ ("libmicrohttpd API violation.\n")); |
| |
| if (left_unprocessed != to_be_processed) |
| /* Something was processed by the application. */ |
| connection->rq.some_payload_processed = true; |
| if (0 != left_unprocessed) |
| { |
| instant_retry = false; /* client did not process everything */ |
| #ifdef HAVE_MESSAGES |
| if ((left_unprocessed == to_be_processed) && |
| (! connection->suspended)) |
| { |
| /* client did not process any upload data, complain if |
| the setup was incorrect, which may prevent us from |
| handling the rest of the request */ |
| if (0 != (daemon->options & MHD_USE_INTERNAL_POLLING_THREAD)) |
| MHD_DLOG (daemon, |
| _ ("WARNING: Access Handler Callback has not processed " \ |
| "any upload data and connection is not suspended. " \ |
| "This may result in hung connection.\n")); |
| } |
| #endif /* HAVE_MESSAGES */ |
| } |
| processed_size = to_be_processed - left_unprocessed; |
| if (connection->rq.have_chunked_upload) |
| connection->rq.current_chunk_offset += processed_size; |
| /* dh left "processed" bytes in buffer for next time... */ |
| buffer_head += processed_size; |
| available -= processed_size; |
| if (! connection->rq.have_chunked_upload) |
| { |
| mhd_assert (MHD_SIZE_UNKNOWN != connection->rq.remaining_upload_size); |
| connection->rq.remaining_upload_size -= processed_size; |
| } |
| else |
| mhd_assert (MHD_SIZE_UNKNOWN == connection->rq.remaining_upload_size); |
| } while (instant_retry); |
| /* TODO: zero out reused memory region */ |
| if ( (available > 0) && |
| (buffer_head != connection->read_buffer) ) |
| memmove (connection->read_buffer, |
| buffer_head, |
| available); |
| else |
| mhd_assert ((0 == available) || \ |
| (connection->read_buffer_offset == available)); |
| connection->read_buffer_offset = available; |
| } |
| |
| |
| /** |
| * 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 enum MHD_Result |
| check_write_done (struct MHD_Connection *connection, |
| enum MHD_CONNECTION_STATE next_state) |
| { |
| if ( (connection->write_buffer_append_offset != |
| connection->write_buffer_send_offset) |
| /* || data_in_tls_buffers == true */ |
| ) |
| return MHD_NO; |
| connection->write_buffer_append_offset = 0; |
| connection->write_buffer_send_offset = 0; |
| connection->state = next_state; |
| return MHD_YES; |
| } |
| |
| |
| /** |
| * We have received (possibly the beginning of) a line in the |
| * header (or footer). Validate (check for ":") and prepare |
| * to process. |
| * |
| * @param connection connection we're processing |
| * @param line line from the header to process |
| * @return #MHD_YES on success, #MHD_NO on error (malformed @a line) |
| */ |
| static enum MHD_Result |
| process_header_line (struct MHD_Connection *connection, |
| char *line) |
| { |
| char *colon; |
| |
| /* line should be normal header line, find colon */ |
| colon = strchr (line, ':'); |
| if (NULL == colon) |
| { |
| /* error in header line, die hard */ |
| return MHD_NO; |
| } |
| if (-3 < connection->daemon->client_discipline) |
| { |
| /* check for whitespace before colon, which is not allowed |
| by RFC 7230 section 3.2.4; we count space ' ' and |
| tab '\t', but not '\r\n' as those would have ended the line. */ |
| const char *white; |
| |
| white = strchr (line, ' '); |
| if ( (NULL != white) && |
| (white < colon) ) |
| return MHD_NO; |
| white = strchr (line, '\t'); |
| if ( (NULL != white) && |
| (white < colon) ) |
| return MHD_NO; |
| } |
| /* zero-terminate header */ |
| colon[0] = '\0'; |
| colon++; /* advance to value */ |
| while ( ('\0' != colon[0]) && |
| ( (' ' == colon[0]) || |
| ('\t' == colon[0]) ) ) |
| colon++; |
| /* we do the actual adding of the connection |
| header at the beginning of the while |
| loop since we need to be able to inspect |
| the *next* header line (in case it starts |
| with a space...) */ |
| connection->rq.last = line; |
| connection->rq.colon = colon; |
| return MHD_YES; |
| } |
| |
| |
| /** |
| * Process a header value that spans multiple lines. |
| * The previous line(s) are in connection->last. |
| * |
| * @param connection connection we're processing |
| * @param line the current input line |
| * @param kind if the line is complete, add a header |
| * of the given kind |
| * @return #MHD_YES if the line was processed successfully |
| */ |
| static enum MHD_Result |
| process_broken_line (struct MHD_Connection *connection, |
| char *line, |
| enum MHD_ValueKind kind) |
| { |
| char *last; |
| char *tmp; |
| size_t last_len; |
| size_t tmp_len; |
| |
| last = connection->rq.last; |
| if ( (' ' == line[0]) || |
| ('\t' == line[0]) ) |
| { |
| /* value was continued on the next line, see |
| http://www.jmarshall.com/easy/http/ */ |
| last_len = strlen (last); |
| /* skip whitespace at start of 2nd line */ |
| tmp = line; |
| while ( (' ' == tmp[0]) || |
| ('\t' == tmp[0]) ) |
| tmp++; |
| tmp_len = strlen (tmp); |
| /* FIXME: we might be able to do this better (faster!), as most |
| likely 'last' and 'line' should already be adjacent in |
| memory; however, doing this right gets tricky if we have a |
| value continued over multiple lines (in which case we need to |
| record how often we have done this so we can check for |
| adjacency); also, in the case where these are not adjacent |
| (not sure how it can happen!), we would want to allocate from |
| the end of the pool, so as to not destroy the read-buffer's |
| ability to grow nicely. */ |
| last = MHD_pool_reallocate (connection->pool, |
| last, |
| last_len + 1, |
| last_len + tmp_len + 1); |
| if (NULL == last) |
| { |
| transmit_error_response_static (connection, |
| MHD_HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE, |
| REQUEST_TOO_BIG); |
| return MHD_NO; |
| } |
| memcpy (&last[last_len], |
| tmp, |
| tmp_len + 1); |
| connection->rq.last = last; |
| return MHD_YES; /* possibly more than 2 lines... */ |
| } |
| mhd_assert ( (NULL != last) && |
| (NULL != connection->rq.colon) ); |
| if (MHD_NO == |
| connection_add_header (connection, |
| last, |
| strlen (last), |
| connection->rq.colon, |
| strlen (connection->rq.colon), |
| kind)) |
| { |
| /* Error has been queued by connection_add_header() */ |
| return MHD_NO; |
| } |
| /* we still have the current line to deal with... */ |
| if (0 != line[0]) |
| { |
| if (MHD_NO == process_header_line (connection, |
| line)) |
| { |
| transmit_error_response_static (connection, |
| MHD_HTTP_BAD_REQUEST, |
| REQUEST_MALFORMED); |
| return MHD_NO; |
| } |
| } |
| return MHD_YES; |
| } |
| |
| |
| /** |
| * 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 connection connection we're processing |
| */ |
| static void |
| parse_connection_headers (struct MHD_Connection *connection) |
| { |
| const char *clen; |
| const char *enc; |
| size_t val_len; |
| |
| #ifdef COOKIE_SUPPORT |
| if (MHD_PARSE_COOKIE_NO_MEMORY == parse_cookie_header (connection)) |
| { |
| transmit_error_response_static (connection, |
| MHD_HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE, |
| REQUEST_TOO_BIG); |
| 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_HEADER_KIND, |
| MHD_HTTP_HEADER_HOST, |
| MHD_STATICSTR_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_BAD_REQUEST, |
| REQUEST_LACKS_HOST); |
| return; |
| } |
| |
| connection->rq.remaining_upload_size = 0; |
| if (MHD_NO != |
| MHD_lookup_connection_value_n (connection, |
| MHD_HEADER_KIND, |
| MHD_HTTP_HEADER_TRANSFER_ENCODING, |
| MHD_STATICSTR_LEN_ ( |
| MHD_HTTP_HEADER_TRANSFER_ENCODING), |
| &enc, |
| NULL)) |
| { |
| if (! MHD_str_equal_caseless_ (enc, |
| "chunked")) |
| { |
| transmit_error_response_static (connection, |
| MHD_HTTP_BAD_REQUEST, |
| REQUEST_UNSUPPORTED_TR_ENCODING); |
| return; |
| } |
| else if (MHD_NO != |
| MHD_lookup_connection_value_n (connection, |
| MHD_HEADER_KIND, |
| MHD_HTTP_HEADER_CONTENT_LENGTH, |
| MHD_STATICSTR_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_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_HEADER_KIND, |
| MHD_HTTP_HEADER_CONTENT_LENGTH, |
| MHD_STATICSTR_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 ( (val_len != num_digits) || |
| (0 == num_digits) ) |
| { |
| connection->rq.remaining_upload_size = 0; |
| if ((0 == num_digits) && |
| (0 != val_len) && |
| ('0' <= clen[0]) && ('9' >= clen[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_CONTENT_TOO_LARGE, |
| REQUEST_CONTENTLENGTH_TOOLARGE); |
| } |
| else |
| { |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (connection->daemon, |
| _ ("Failed to parse `Content-Length' header. " \ |
| "Closing connection.\n")); |
| #endif |
| transmit_error_response_static (connection, |
| MHD_HTTP_BAD_REQUEST, |
| REQUEST_CONTENTLENGTH_MALFORMED); |
| } |
| } |
| } |
| } |
| } |
| |
| |
| /** |
| * Update the 'last_activity' field of the connection to the current time |
| * and move the connection to the head of the 'normal_timeout' list if |
| * the timeout for the connection uses the default value. |
| * |
| * @param connection the connection that saw some activity |
| */ |
| void |
| MHD_update_last_activity_ (struct MHD_Connection *connection) |
| { |
| struct MHD_Daemon *daemon = connection->daemon; |
| #if defined(MHD_USE_THREADS) |
| mhd_assert (NULL == daemon->worker_pool); |
| #endif /* MHD_USE_THREADS */ |
| |
| if (0 == connection->connection_timeout_ms) |
| return; /* Skip update of activity for connections |
| without timeout timer. */ |
| if (connection->suspended) |
| return; /* no activity on suspended connections */ |
| |
| connection->last_activity = MHD_monotonic_msec_counter (); |
| if (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) |
| return; /* each connection has personal timeout */ |
| |
| if (connection->connection_timeout_ms != daemon->connection_timeout_ms) |
| return; /* custom timeout, no need to move it in "normal" DLL */ |
| #if defined(MHD_USE_POSIX_THREADS) || defined(MHD_USE_W32_THREADS) |
| MHD_mutex_lock_chk_ (&daemon->cleanup_connection_mutex); |
| #endif |
| /* move connection to head of timeout list (by remove + add operation) */ |
| XDLL_remove (daemon->normal_timeout_head, |
| daemon->normal_timeout_tail, |
| connection); |
| XDLL_insert (daemon->normal_timeout_head, |
| daemon->normal_timeout_tail, |
| connection); |
| #if defined(MHD_USE_POSIX_THREADS) || defined(MHD_USE_W32_THREADS) |
| MHD_mutex_unlock_chk_ (&daemon->cleanup_connection_mutex); |
| #endif |
| } |
| |
| |
| /** |
| * This function handles a particular connection when it has been |
| * determined that there is data to be read off a socket. All |
| * implementations (multithreaded, external polling, internal polling) |
| * call this function to handle reads. |
| * |
| * @param connection connection to handle |
| * @param socket_error set to true if socket error was detected |
| */ |
| void |
| MHD_connection_handle_read (struct MHD_Connection *connection, |
| bool socket_error) |
| { |
| ssize_t bytes_read; |
| |
| if ( (MHD_CONNECTION_CLOSED == connection->state) || |
| (connection->suspended) ) |
| return; |
| #ifdef HTTPS_SUPPORT |
| if (MHD_TLS_CONN_NO_TLS != connection->tls_state) |
| { /* HTTPS connection. */ |
| if (MHD_TLS_CONN_CONNECTED > connection->tls_state) |
| { |
| if (! MHD_run_tls_handshake_ (connection)) |
| return; |
| } |
| } |
| #endif /* HTTPS_SUPPORT */ |
| |
| /* make sure "read" has a reasonable number of bytes |
| in buffer to use per system call (if possible) */ |
| if (connection->read_buffer_offset + connection->daemon->pool_increment > |
| connection->read_buffer_size) |
| try_grow_read_buffer (connection, |
| (connection->read_buffer_size == |
| connection->read_buffer_offset)); |
| |
| if (connection->read_buffer_size == connection->read_buffer_offset) |
| return; /* No space for receiving data. */ |
| bytes_read = connection->recv_cls (connection, |
| &connection->read_buffer |
| [connection->read_buffer_offset], |
| connection->read_buffer_size |
| - connection->read_buffer_offset); |
| if ((bytes_read < 0) || socket_error) |
| { |
| if ((MHD_ERR_AGAIN_ == bytes_read) && ! socket_error) |
| return; /* No new data to process. */ |
| if ((bytes_read > 0) && connection->sk_nonblck) |
| { /* Try to detect the socket error */ |
| int dummy; |
| bytes_read = connection->recv_cls (connection, &dummy, sizeof (dummy)); |
| } |
| if (MHD_ERR_CONNRESET_ == bytes_read) |
| { |
| if ( (MHD_CONNECTION_INIT < connection->state) && |
| (MHD_CONNECTION_FULL_REQ_RECEIVED > connection->state) ) |
| { |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (connection->daemon, |
| _ ("Socket has been disconnected when reading request.\n")); |
| #endif |
| connection->discard_request = true; |
| } |
| MHD_connection_close_ (connection, |
| MHD_REQUEST_TERMINATED_READ_ERROR); |
| return; |
| } |
| |
| #ifdef HAVE_MESSAGES |
| if (MHD_CONNECTION_INIT != connection->state) |
| MHD_DLOG (connection->daemon, |
| _ ("Connection socket is closed when reading " \ |
| "request due to the error: %s\n"), |
| (bytes_read < 0) ? str_conn_error_ (bytes_read) : |
| "detected connection closure"); |
| #endif |
| CONNECTION_CLOSE_ERROR (connection, |
| NULL); |
| return; |
| } |
| |
| if (0 == bytes_read) |
| { /* Remote side closed connection. */ |
| connection->read_closed = true; |
| if ( (MHD_CONNECTION_INIT < connection->state) && |
| (MHD_CONNECTION_FULL_REQ_RECEIVED > connection->state) ) |
| { |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (connection->daemon, |
| _ ("Connection was closed by remote side with incomplete " |
| "request.\n")); |
| #endif |
| connection->discard_request = true; |
| MHD_connection_close_ (connection, |
| MHD_REQUEST_TERMINATED_CLIENT_ABORT); |
| } |
| else if (MHD_CONNECTION_INIT == connection->state) |
| /* This termination code cannot be reported to the application |
| * because application has not been informed yet about this request */ |
| MHD_connection_close_ (connection, |
| MHD_REQUEST_TERMINATED_COMPLETED_OK); |
| else |
| MHD_connection_close_ (connection, |
| MHD_REQUEST_TERMINATED_WITH_ERROR); |
| return; |
| } |
| connection->read_buffer_offset += (size_t) bytes_read; |
| MHD_update_last_activity_ (connection); |
| #if DEBUG_STATES |
| MHD_DLOG (connection->daemon, |
| _ ("In function %s handling connection at state: %s\n"), |
| MHD_FUNC_, |
| MHD_state_to_string (connection->state)); |
| #endif |
| /* TODO: check whether the next 'switch()' really needed */ |
| switch (connection->state) |
| { |
| case MHD_CONNECTION_INIT: |
| case MHD_CONNECTION_REQ_LINE_RECEIVING: |
| case MHD_CONNECTION_URL_RECEIVED: |
| case MHD_CONNECTION_HEADER_PART_RECEIVED: |
| case MHD_CONNECTION_HEADERS_RECEIVED: |
| case MHD_CONNECTION_HEADERS_PROCESSED: |
| case MHD_CONNECTION_CONTINUE_SENDING: |
| case MHD_CONNECTION_BODY_RECEIVING: |
| case MHD_CONNECTION_BODY_RECEIVED: |
| case MHD_CONNECTION_FOOTER_PART_RECEIVED: |
| case MHD_CONNECTION_FOOTERS_RECEIVED: |
| case MHD_CONNECTION_FULL_REQ_RECEIVED: |
| /* nothing to do but default action */ |
| if (connection->read_closed) |
| { |
| /* TODO: check whether this really needed */ |
| MHD_connection_close_ (connection, |
| MHD_REQUEST_TERMINATED_READ_ERROR); |
| } |
| return; |
| case MHD_CONNECTION_CLOSED: |
| return; |
| #ifdef UPGRADE_SUPPORT |
| case MHD_CONNECTION_UPGRADE: |
| mhd_assert (0); |
| return; |
| #endif /* UPGRADE_SUPPORT */ |
| case MHD_CONNECTION_START_REPLY: |
| /* shrink read buffer to how much is actually used */ |
| /* TODO: remove shrink as it handled in special function */ |
| if ((0 != connection->read_buffer_size) && |
| (connection->read_buffer_size != connection->read_buffer_offset)) |
| { |
| mhd_assert (NULL != connection->read_buffer); |
| connection->read_buffer = |
| MHD_pool_reallocate (connection->pool, |
| connection->read_buffer, |
| connection->read_buffer_size, |
| connection->read_buffer_offset); |
| connection->read_buffer_size = connection->read_buffer_offset; |
| } |
| break; |
| case MHD_CONNECTION_HEADERS_SENDING: |
| case MHD_CONNECTION_HEADERS_SENT: |
| case MHD_CONNECTION_NORMAL_BODY_UNREADY: |
| case MHD_CONNECTION_NORMAL_BODY_READY: |
| case MHD_CONNECTION_CHUNKED_BODY_UNREADY: |
| case MHD_CONNECTION_CHUNKED_BODY_READY: |
| case MHD_CONNECTION_CHUNKED_BODY_SENT: |
| case MHD_CONNECTION_FOOTERS_SENDING: |
| case MHD_CONNECTION_FULL_REPLY_SENT: |
| default: |
| mhd_assert (0); /* Should not be possible */ |
| } |
| return; |
| } |
| |
| |
| /** |
| * This function was created to handle writes to sockets when it has |
| * been determined that the socket can be written to. All |
| * implementations (multithreaded, external select, internal select) |
| * call this function |
| * |
| * @param connection connection to handle |
| */ |
| void |
| MHD_connection_handle_write (struct MHD_Connection *connection) |
| { |
| struct MHD_Response *response; |
| ssize_t ret; |
| if (connection->suspended) |
| return; |
| |
| #ifdef HTTPS_SUPPORT |
| if (MHD_TLS_CONN_NO_TLS != connection->tls_state) |
| { /* HTTPS connection. */ |
| if (MHD_TLS_CONN_CONNECTED > connection->tls_state) |
| { |
| if (! MHD_run_tls_handshake_ (connection)) |
| return; |
| } |
| } |
| #endif /* HTTPS_SUPPORT */ |
| |
| #if DEBUG_STATES |
| MHD_DLOG (connection->daemon, |
| _ ("In function %s handling connection at state: %s\n"), |
| MHD_FUNC_, |
| MHD_state_to_string (connection->state)); |
| #endif |
| switch (connection->state) |
| { |
| case MHD_CONNECTION_INIT: |
| case MHD_CONNECTION_REQ_LINE_RECEIVING: |
| case MHD_CONNECTION_URL_RECEIVED: |
| case MHD_CONNECTION_HEADER_PART_RECEIVED: |
| case MHD_CONNECTION_HEADERS_RECEIVED: |
| mhd_assert (0); |
| return; |
| case MHD_CONNECTION_HEADERS_PROCESSED: |
| return; |
| case MHD_CONNECTION_CONTINUE_SENDING: |
| ret = MHD_send_data_ (connection, |
| &HTTP_100_CONTINUE |
| [connection->continue_message_write_offset], |
| MHD_STATICSTR_LEN_ (HTTP_100_CONTINUE) |
| - connection->continue_message_write_offset, |
| true); |
| if (ret < 0) |
| { |
| if (MHD_ERR_AGAIN_ == ret) |
| return; |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (connection->daemon, |
| _ ("Failed to send data in request for %s.\n"), |
| connection->rq.url); |
| #endif |
| CONNECTION_CLOSE_ERROR (connection, |
| NULL); |
| return; |
| } |
| #if _MHD_DEBUG_SEND_DATA |
| fprintf (stderr, |
| _ ("Sent 100 continue response: `%.*s'\n"), |
| (int) ret, |
| &HTTP_100_CONTINUE[connection->continue_message_write_offset]); |
| #endif |
| connection->continue_message_write_offset += (size_t) ret; |
| MHD_update_last_activity_ (connection); |
| return; |
| case MHD_CONNECTION_BODY_RECEIVING: |
| case MHD_CONNECTION_BODY_RECEIVED: |
| case MHD_CONNECTION_FOOTER_PART_RECEIVED: |
| case MHD_CONNECTION_FOOTERS_RECEIVED: |
| case MHD_CONNECTION_FULL_REQ_RECEIVED: |
| case MHD_CONNECTION_START_REPLY: |
| mhd_assert (0); |
| return; |
| case MHD_CONNECTION_HEADERS_SENDING: |
| { |
| struct MHD_Response *const resp = connection->rp.response; |
| const size_t wb_ready = connection->write_buffer_append_offset |
| - connection->write_buffer_send_offset; |
| mhd_assert (connection->write_buffer_append_offset >= \ |
| connection->write_buffer_send_offset); |
| mhd_assert (NULL != resp); |
| mhd_assert ( (0 == resp->data_size) || \ |
| (0 == resp->data_start) || \ |
| (NULL != resp->crc) ); |
| mhd_assert ( (0 == connection->rp.rsp_write_position) || \ |
| (resp->total_size == |
| connection->rp.rsp_write_position) ); |
| mhd_assert ((MHD_CONN_MUST_UPGRADE != connection->keepalive) || \ |
| (! connection->rp.props.send_reply_body)); |
| |
| if ( (connection->rp.props.send_reply_body) && |
| (NULL == resp->crc) && |
| (NULL == resp->data_iov) && |
| /* TODO: remove the next check as 'send_reply_body' is used */ |
| (0 == connection->rp.rsp_write_position) && |
| (! connection->rp.props.chunked) ) |
| { |
| mhd_assert (resp->total_size >= resp->data_size); |
| mhd_assert (0 == resp->data_start); |
| /* Send response headers alongside the response body, if the body |
| * data is available. */ |
| ret = MHD_send_hdr_and_body_ (connection, |
| &connection->write_buffer |
| [connection->write_buffer_send_offset], |
| wb_ready, |
| false, |
| resp->data, |
| resp->data_size, |
| (resp->total_size == resp->data_size)); |
| } |
| 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. */ |
| ret = MHD_send_hdr_and_body_ (connection, |
| &connection->write_buffer |
| [connection->write_buffer_send_offset], |
| wb_ready, |
| false, |
| NULL, |
| 0, |
| ((0 == resp->total_size) || |
| (! connection->rp.props.send_reply_body) |
| )); |
| } |
| |
| if (ret < 0) |
| { |
| if (MHD_ERR_AGAIN_ == ret) |
| return; |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (connection->daemon, |
| _ ("Failed to send the response headers for the " \ |
| "request for `%s'. Error: %s\n"), |
| connection->rq.url, |
| str_conn_error_ (ret)); |
| #endif |
| CONNECTION_CLOSE_ERROR (connection, |
| NULL); |
| return; |
| } |
| /* 'ret' is not negative, it's safe to cast it to 'size_t'. */ |
| if (((size_t) ret) > wb_ready) |
| { |
| /* The complete header and some response data have been sent, |
| * update both offsets. */ |
| mhd_assert (0 == connection->rp.rsp_write_position); |
| mhd_assert (! connection->rp.props.chunked); |
| mhd_assert (connection->rp.props.send_reply_body); |
| connection->write_buffer_send_offset += wb_ready; |
| connection->rp.rsp_write_position = ((size_t) ret) - wb_ready; |
| } |
| else |
| connection->write_buffer_send_offset += (size_t) ret; |
| MHD_update_last_activity_ (connection); |
| if (MHD_CONNECTION_HEADERS_SENDING != connection->state) |
| return; |
| check_write_done (connection, |
| MHD_CONNECTION_HEADERS_SENT); |
| return; |
| } |
| case MHD_CONNECTION_HEADERS_SENT: |
| return; |
| case MHD_CONNECTION_NORMAL_BODY_READY: |
| response = connection->rp.response; |
| if (connection->rp.rsp_write_position < |
| connection->rp.response->total_size) |
| { |
| uint64_t data_write_offset; |
| |
| #if defined(MHD_USE_POSIX_THREADS) || defined(MHD_USE_W32_THREADS) |
| if (NULL != response->crc) |
| MHD_mutex_lock_chk_ (&response->mutex); |
| #endif |
| if (MHD_NO == try_ready_normal_body (connection)) |
| { |
| /* mutex was already unlocked by try_ready_normal_body */ |
| return; |
| } |
| #if defined(_MHD_HAVE_SENDFILE) |
| if (MHD_resp_sender_sendfile == connection->rp.resp_sender) |
| { |
| mhd_assert (NULL == response->data_iov); |
| ret = MHD_send_sendfile_ (connection); |
| } |
| else /* combined with the next 'if' */ |
| #endif /* _MHD_HAVE_SENDFILE */ |
| if (NULL != response->data_iov) |
| { |
| ret = MHD_send_iovec_ (connection, |
| &connection->rp.resp_iov, |
| true); |
| } |
| else |
| { |
| data_write_offset = connection->rp.rsp_write_position |
| - response->data_start; |
| if (data_write_offset > (uint64_t) SIZE_MAX) |
| MHD_PANIC (_ ("Data offset exceeds limit.\n")); |
| ret = MHD_send_data_ (connection, |
| &response->data |
| [(size_t) data_write_offset], |
| response->data_size |
| - (size_t) data_write_offset, |
| true); |
| #if _MHD_DEBUG_SEND_DATA |
| if (ret > 0) |
| fprintf (stderr, |
| _ ("Sent %d-byte DATA response: `%.*s'\n"), |
| (int) ret, |
| (int) ret, |
| &rp.response->data[connection->rp.rsp_write_position |
| - rp.response->data_start]); |
| #endif |
| } |
| #if defined(MHD_USE_POSIX_THREADS) || defined(MHD_USE_W32_THREADS) |
| if (NULL != response->crc) |
| MHD_mutex_unlock_chk_ (&response->mutex); |
| #endif |
| if (ret < 0) |
| { |
| if (MHD_ERR_AGAIN_ == ret) |
| return; |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (connection->daemon, |
| _ ("Failed to send the response body for the " \ |
| "request for `%s'. Error: %s\n"), |
| connection->rq.url, |
| str_conn_error_ (ret)); |
| #endif |
| CONNECTION_CLOSE_ERROR (connection, |
| NULL); |
| return; |
| } |
| connection->rp.rsp_write_position += (size_t) ret; |
| MHD_update_last_activity_ (connection); |
| } |
| if (connection->rp.rsp_write_position == |
| connection->rp.response->total_size) |
| connection->state = MHD_CONNECTION_FULL_REPLY_SENT; |
| return; |
| case MHD_CONNECTION_NORMAL_BODY_UNREADY: |
| mhd_assert (0); |
| return; |
| case MHD_CONNECTION_CHUNKED_BODY_READY: |
| ret = MHD_send_data_ (connection, |
| &connection->write_buffer |
| [connection->write_buffer_send_offset], |
| connection->write_buffer_append_offset |
| - connection->write_buffer_send_offset, |
| true); |
| if (ret < 0) |
| { |
| if (MHD_ERR_AGAIN_ == ret) |
| return; |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (connection->daemon, |
| _ ("Failed to send the chunked response body for the " \ |
| "request for `%s'. Error: %s\n"), |
| connection->rq.url, |
| str_conn_error_ (ret)); |
| #endif |
| CONNECTION_CLOSE_ERROR (connection, |
| NULL); |
| return; |
| } |
| connection->write_buffer_send_offset += (size_t) ret; |
| MHD_update_last_activity_ (connection); |
| if (MHD_CONNECTION_CHUNKED_BODY_READY != connection->state) |
| return; |
| check_write_done (connection, |
| (connection->rp.response->total_size == |
| connection->rp.rsp_write_position) ? |
| MHD_CONNECTION_CHUNKED_BODY_SENT : |
| MHD_CONNECTION_CHUNKED_BODY_UNREADY); |
| return; |
| case MHD_CONNECTION_CHUNKED_BODY_UNREADY: |
| case MHD_CONNECTION_CHUNKED_BODY_SENT: |
| mhd_assert (0); |
| return; |
| case MHD_CONNECTION_FOOTERS_SENDING: |
| ret = MHD_send_data_ (connection, |
| &connection->write_buffer |
| [connection->write_buffer_send_offset], |
| connection->write_buffer_append_offset |
| - connection->write_buffer_send_offset, |
| true); |
| if (ret < 0) |
| { |
| if (MHD_ERR_AGAIN_ == ret) |
| return; |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (connection->daemon, |
| _ ("Failed to send the footers for the " \ |
| "request for `%s'. Error: %s\n"), |
| connection->rq.url, |
| str_conn_error_ (ret)); |
| #endif |
| CONNECTION_CLOSE_ERROR (connection, |
| NULL); |
| return; |
| } |
| connection->write_buffer_send_offset += (size_t) ret; |
| MHD_update_last_activity_ (connection); |
| if (MHD_CONNECTION_FOOTERS_SENDING != connection->state) |
| return; |
| check_write_done (connection, |
| MHD_CONNECTION_FULL_REPLY_SENT); |
| return; |
| case MHD_CONNECTION_FULL_REPLY_SENT: |
| mhd_assert (0); |
| return; |
| case MHD_CONNECTION_CLOSED: |
| return; |
| #ifdef UPGRADE_SUPPORT |
| case MHD_CONNECTION_UPGRADE: |
| mhd_assert (0); |
| return; |
| #endif /* UPGRADE_SUPPORT */ |
| default: |
| mhd_assert (0); |
| CONNECTION_CLOSE_ERROR (connection, |
| _ ("Internal error.\n")); |
| break; |
| } |
| return; |
| } |
| |
| |
| /** |
| * Check whether connection has timed out. |
| * @param c the connection to check |
| * @return true if connection has timeout and needs to be closed, |
| * false otherwise. |
| */ |
| static bool |
| connection_check_timedout (struct MHD_Connection *c) |
| { |
| const uint64_t timeout = c->connection_timeout_ms; |
| uint64_t now; |
| uint64_t since_actv; |
| |
| if (c->suspended) |
| return false; |
| if (0 == timeout) |
| return false; |
| now = MHD_monotonic_msec_counter (); |
| since_actv = now - c->last_activity; |
| /* Keep the next lines in sync with #connection_get_wait() to avoid |
| * undesired side-effects like busy-waiting. */ |
| if (timeout < since_actv) |
| { |
| if (UINT64_MAX / 2 < since_actv) |
| { |
| const uint64_t jump_back = c->last_activity - now; |
| /* Very unlikely that it is more than quarter-million years pause. |
| * More likely that system clock jumps back. */ |
| if (5000 >= jump_back) |
| { |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (c->daemon, |
| _ ("Detected system clock %u milliseconds jump back.\n"), |
| (unsigned int) jump_back); |
| #endif |
| return false; |
| } |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (c->daemon, |
| _ ("Detected too large system clock %" PRIu64 " milliseconds " |
| "jump back.\n"), |
| jump_back); |
| #endif |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| |
| /** |
| * Clean up the state of the given connection and move it into the |
| * clean up queue for final disposal. |
| * @remark To be called only from thread that process connection's |
| * recv(), send() and response. |
| * |
| * @param connection handle for the connection to clean up |
| */ |
| static void |
| cleanup_connection (struct MHD_Connection *connection) |
| { |
| struct MHD_Daemon *daemon = connection->daemon; |
| #ifdef MHD_USE_THREADS |
| mhd_assert ( (0 == (daemon->options & MHD_USE_INTERNAL_POLLING_THREAD)) || \ |
| MHD_thread_ID_match_current_ (connection->pid) ); |
| mhd_assert (NULL == daemon->worker_pool); |
| #endif /* MHD_USE_THREADS */ |
| |
| if (connection->in_cleanup) |
| return; /* Prevent double cleanup. */ |
| connection->in_cleanup = true; |
| if (NULL != connection->rp.response) |
| { |
| MHD_destroy_response (connection->rp.response); |
| connection->rp.response = NULL; |
| } |
| #if defined(MHD_USE_POSIX_THREADS) || defined(MHD_USE_W32_THREADS) |
| MHD_mutex_lock_chk_ (&daemon->cleanup_connection_mutex); |
| #endif |
| if (connection->suspended) |
| { |
| DLL_remove (daemon->suspended_connections_head, |
| daemon->suspended_connections_tail, |
| connection); |
| connection->suspended = false; |
| } |
| else |
| { |
| if (0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) |
| { |
| if (connection->connection_timeout_ms == daemon->connection_timeout_ms) |
| XDLL_remove (daemon->normal_timeout_head, |
| daemon->normal_timeout_tail, |
| connection); |
| else |
| XDLL_remove (daemon->manual_timeout_head, |
| daemon->manual_timeout_tail, |
| connection); |
| } |
| DLL_remove (daemon->connections_head, |
| daemon->connections_tail, |
| connection); |
| } |
| DLL_insert (daemon->cleanup_head, |
| daemon->cleanup_tail, |
| connection); |
| connection->resuming = false; |
| connection->in_idle = false; |
| #if defined(MHD_USE_POSIX_THREADS) || defined(MHD_USE_W32_THREADS) |
| MHD_mutex_unlock_chk_ (&daemon->cleanup_connection_mutex); |
| #endif |
| if (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) |
| { |
| /* if we were at the connection limit before and are in |
| thread-per-connection mode, signal the main thread |
| to resume accepting connections */ |
| if ( (MHD_ITC_IS_VALID_ (daemon->itc)) && |
| (! MHD_itc_activate_ (daemon->itc, "c")) ) |
| { |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| _ ("Failed to signal end of connection via inter-thread " \ |
| "communication channel.\n")); |
| #endif |
| } |
| } |
| } |
| |
| |
| /** |
| * Reset connection after request-reply cycle. |
| * @param connection the connection to process |
| * @param reuse the flag to choose whether to close connection or |
| * prepare connection for the next request processing |
| */ |
| static void |
| connection_reset (struct MHD_Connection *connection, |
| bool reuse) |
| { |
| struct MHD_Connection *const c = connection; /**< a short alias */ |
| struct MHD_Daemon *const d = connection->daemon; |
| |
| if (! reuse) |
| { |
| /* Next function will destroy response, notify client, |
| * destroy memory pool, and set connection state to "CLOSED" */ |
| MHD_connection_close_ (c, |
| c->stop_with_error ? |
| MHD_REQUEST_TERMINATED_WITH_ERROR : |
| MHD_REQUEST_TERMINATED_COMPLETED_OK); |
| c->read_buffer = NULL; |
| c->read_buffer_size = 0; |
| c->read_buffer_offset = 0; |
| c->write_buffer = NULL; |
| c->write_buffer_size = 0; |
| c->write_buffer_send_offset = 0; |
| c->write_buffer_append_offset = 0; |
| } |
| else |
| { |
| /* Reset connection to process the next request */ |
| size_t new_read_buf_size; |
| mhd_assert (! c->stop_with_error); |
| mhd_assert (! c->discard_request); |
| |
| if ( (NULL != d->notify_completed) && |
| (c->rq.client_aware) ) |
| d->notify_completed (d->notify_completed_cls, |
| c, |
| &c->rq.client_context, |
| MHD_REQUEST_TERMINATED_COMPLETED_OK); |
| c->rq.client_aware = false; |
| |
| if (NULL != c->rp.response) |
| MHD_destroy_response (c->rp.response); |
| c->rp.response = NULL; |
| |
| c->keepalive = MHD_CONN_KEEPALIVE_UNKOWN; |
| c->state = MHD_CONNECTION_INIT; |
| c->event_loop_info = MHD_EVENT_LOOP_INFO_READ; |
| |
| memset (&c->rq, 0, sizeof(c->rq)); |
| |
| /* iov (if any) will be deallocated by MHD_pool_reset */ |
| memset (&c->rp, 0, sizeof(c->rp)); |
| |
| c->write_buffer = NULL; |
| c->write_buffer_size = 0; |
| c->write_buffer_send_offset = 0; |
| c->write_buffer_append_offset = 0; |
| c->continue_message_write_offset = 0; |
| |
| /* Reset the read buffer to the starting size, |
| preserving the bytes we have already read. */ |
| new_read_buf_size = c->daemon->pool_size / 2; |
| if (c->read_buffer_offset > new_read_buf_size) |
| new_read_buf_size = c->read_buffer_offset; |
| |
| c->read_buffer |
| = MHD_pool_reset (c->pool, |
| c->read_buffer, |
| c->read_buffer_offset, |
| new_read_buf_size); |
| c->read_buffer_size = new_read_buf_size; |
| } |
| c->rq.client_context = NULL; |
| } |
| |
| |
| /** |
| * This function was created to handle per-connection processing that |
| * has to happen even if the socket cannot be read or written to. |
| * All implementations (multithreaded, external select, internal select) |
| * call this function. |
| * @remark To be called only from thread that process connection's |
| * recv(), send() and response. |
| * |
| * @param connection connection to handle |
| * @return #MHD_YES if we should continue to process the |
| * connection (not dead yet), #MHD_NO if it died |
| */ |
| enum MHD_Result |
| MHD_connection_handle_idle (struct MHD_Connection *connection) |
| { |
| struct MHD_Daemon *daemon = connection->daemon; |
| char *line; |
| size_t line_len; |
| enum MHD_Result ret; |
| #ifdef MHD_USE_THREADS |
| mhd_assert ( (0 == (daemon->options & MHD_USE_INTERNAL_POLLING_THREAD)) || \ |
| MHD_thread_ID_match_current_ (connection->pid) ); |
| #endif /* MHD_USE_THREADS */ |
| /* 'daemon' is not used if epoll is not available and asserts are disabled */ |
| (void) daemon; /* Mute compiler warning */ |
| |
| connection->in_idle = true; |
| while (! connection->suspended) |
| { |
| #ifdef HTTPS_SUPPORT |
| if (MHD_TLS_CONN_NO_TLS != connection->tls_state) |
| { /* HTTPS connection. */ |
| if ((MHD_TLS_CONN_INIT <= connection->tls_state) && |
| (MHD_TLS_CONN_CONNECTED > connection->tls_state)) |
| break; |
| } |
| #endif /* HTTPS_SUPPORT */ |
| #if DEBUG_STATES |
| MHD_DLOG (daemon, |
| _ ("In function %s handling connection at state: %s\n"), |
| MHD_FUNC_, |
| MHD_state_to_string (connection->state)); |
| #endif |
| switch (connection->state) |
| { |
| case MHD_CONNECTION_INIT: |
| case MHD_CONNECTION_REQ_LINE_RECEIVING: |
| line = get_next_header_line (connection, |
| &line_len); |
| if (MHD_CONNECTION_REQ_LINE_RECEIVING < connection->state) |
| continue; |
| if (NULL != line) |
| { |
| /* Check for empty string, as we might want |
| to tolerate 'spurious' empty lines */ |
| if (0 == line[0]) |
| { |
| /* TODO: Add MHD option to not tolerate it */ |
| connection->state = MHD_CONNECTION_INIT; |
| continue; /* Process the next line */ |
| } |
| if (MHD_NO == parse_initial_message_line (connection, |
| line, |
| line_len)) |
| CONNECTION_CLOSE_ERROR_CHECK (connection, |
| NULL); |
| else |
| { |
| mhd_assert (MHD_IS_HTTP_VER_SUPPORTED (connection->rq.http_ver)); |
| connection->state = MHD_CONNECTION_URL_RECEIVED; |
| } |
| continue; |
| } |
| /* NULL means we didn't get a full line yet */ |
| if (connection->discard_request) |
| { |
| mhd_assert (MHD_CONNECTION_INIT != connection->state); |
| continue; |
| } |
| if (0 < connection->read_buffer_offset) |
| connection->state = MHD_CONNECTION_REQ_LINE_RECEIVING; |
| break; |
| case MHD_CONNECTION_URL_RECEIVED: |
| line = get_next_header_line (connection, |
| NULL); |
| if (MHD_CONNECTION_URL_RECEIVED != connection->state) |
| continue; |
| if (NULL == line) |
| { |
| if (connection->read_closed) |
| { |
| CONNECTION_CLOSE_ERROR (connection, |
| NULL); |
| continue; |
| } |
| break; |
| } |
| if (0 == line[0]) |
| { |
| connection->state = MHD_CONNECTION_HEADERS_RECEIVED; |
| connection->rq.header_size = (size_t) (connection->read_buffer |
| - connection->rq.method); |
| continue; |
| } |
| if (MHD_NO == process_header_line (connection, |
| line)) |
| { |
| transmit_error_response_static (connection, |
| MHD_HTTP_BAD_REQUEST, |
| REQUEST_MALFORMED); |
| break; |
| } |
| connection->state = MHD_CONNECTION_HEADER_PART_RECEIVED; |
| continue; |
| case MHD_CONNECTION_HEADER_PART_RECEIVED: |
| line = get_next_header_line (connection, |
| NULL); |
| if (MHD_CONNECTION_HEADER_PART_RECEIVED != connection->state) |
| continue; |
| if (NULL == line) |
| { |
| if (connection->state != MHD_CONNECTION_HEADER_PART_RECEIVED) |
| continue; |
| if (connection->read_closed) |
| { |
| CONNECTION_CLOSE_ERROR (connection, |
| NULL); |
| continue; |
| } |
| break; |
| } |
| if (MHD_NO == |
| process_broken_line (connection, |
| line, |
| MHD_HEADER_KIND)) |
| continue; |
| if (0 == line[0]) |
| { |
| connection->state = MHD_CONNECTION_HEADERS_RECEIVED; |
| connection->rq.header_size = (size_t) (connection->read_buffer |
| - connection->rq.method); |
| continue; |
| } |
| continue; |
| case MHD_CONNECTION_HEADERS_RECEIVED: |
| parse_connection_headers (connection); |
| if (MHD_CONNECTION_HEADERS_RECEIVED != connection->state) |
| continue; |
| connection->state = MHD_CONNECTION_HEADERS_PROCESSED; |
| if (connection->suspended) |
| break; |
| continue; |
| case MHD_CONNECTION_HEADERS_PROCESSED: |
| call_connection_handler (connection); /* first call */ |
| if (MHD_CONNECTION_HEADERS_PROCESSED != connection->state) |
| continue; |
| if (connection->suspended) |
| continue; |
| |
| if ( (NULL == connection->rp.response) && |
| (need_100_continue (connection)) && |
| /* If the client is already sending the payload (body) |
| there is no need to send "100 Continue" */ |
| (0 == connection->read_buffer_offset) ) |
| { |
| connection->state = MHD_CONNECTION_CONTINUE_SENDING; |
| break; |
| } |
| if ( (NULL != connection->rp.response) && |
| (0 != connection->rq.remaining_upload_size) ) |
| { |
| /* we refused (no upload allowed!) */ |
| connection->rq.remaining_upload_size = 0; |
| /* force close, in case client still tries to upload... */ |
| connection->discard_request = true; |
| } |
| connection->state = (0 == connection->rq.remaining_upload_size) |
| ? MHD_CONNECTION_FULL_REQ_RECEIVED |
| : MHD_CONNECTION_BODY_RECEIVING; |
| if (connection->suspended) |
| break; |
| continue; |
| case MHD_CONNECTION_CONTINUE_SENDING: |
| if (connection->continue_message_write_offset == |
| MHD_STATICSTR_LEN_ (HTTP_100_CONTINUE)) |
| { |
| connection->state = MHD_CONNECTION_BODY_RECEIVING; |
| continue; |
| } |
| break; |
| case MHD_CONNECTION_BODY_RECEIVING: |
| if (0 != connection->read_buffer_offset) |
| { |
| process_request_body (connection); /* loop call */ |
| if (MHD_CONNECTION_BODY_RECEIVING != connection->state) |
| continue; |
| } |
| if ( (0 == connection->rq.remaining_upload_size) || |
| ( (MHD_SIZE_UNKNOWN == connection->rq.remaining_upload_size) && |
| (0 == connection->read_buffer_offset) && |
| (connection->discard_request) ) ) |
| { |
| if ( (connection->rq.have_chunked_upload) && |
| (! connection->discard_request) ) |
| connection->state = MHD_CONNECTION_BODY_RECEIVED; |
| else |
| connection->state = MHD_CONNECTION_FULL_REQ_RECEIVED; |
| if (connection->suspended) |
| break; |
| continue; |
| } |
| break; |
| case MHD_CONNECTION_BODY_RECEIVED: |
| line = get_next_header_line (connection, |
| NULL); |
| if (connection->state != MHD_CONNECTION_BODY_RECEIVED) |
| continue; |
| if (NULL == line) |
| { |
| if (connection->read_closed) |
| { |
| CONNECTION_CLOSE_ERROR (connection, |
| NULL); |
| continue; |
| } |
| if (0 < connection->read_buffer_offset) |
| connection->state = MHD_CONNECTION_FOOTER_PART_RECEIVED; |
| break; |
| } |
| if (0 == line[0]) |
| { |
| connection->state = MHD_CONNECTION_FOOTERS_RECEIVED; |
| if (connection->suspended) |
| break; |
| continue; |
| } |
| if (MHD_NO == process_header_line (connection, |
| line)) |
| { |
| transmit_error_response_static (connection, |
| MHD_HTTP_BAD_REQUEST, |
| REQUEST_MALFORMED); |
| break; |
| } |
| connection->state = MHD_CONNECTION_FOOTER_PART_RECEIVED; |
| continue; |
| case MHD_CONNECTION_FOOTER_PART_RECEIVED: |
| line = get_next_header_line (connection, |
| NULL); |
| if (connection->state != MHD_CONNECTION_FOOTER_PART_RECEIVED) |
| continue; |
| if (NULL == line) |
| { |
| if (connection->read_closed) |
| { |
| CONNECTION_CLOSE_ERROR (connection, |
| NULL); |
| continue; |
| } |
| break; |
| } |
| if (MHD_NO == |
| process_broken_line (connection, |
| line, |
| MHD_FOOTER_KIND)) |
| continue; |
| if (0 == line[0]) |
| { |
| connection->state = MHD_CONNECTION_FOOTERS_RECEIVED; |
| if (connection->suspended) |
| break; |
| continue; |
| } |
| continue; |
| 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. */ |
| connection->state = MHD_CONNECTION_FULL_REQ_RECEIVED; |
| continue; |
| case MHD_CONNECTION_FULL_REQ_RECEIVED: |
| call_connection_handler (connection); /* "final" call */ |
| if (connection->state != MHD_CONNECTION_FULL_REQ_RECEIVED) |
| continue; |
| if (NULL == connection->rp.response) |
| break; /* try again next time */ |
| /* Response is ready, start reply */ |
| connection->state = MHD_CONNECTION_START_REPLY; |
| continue; |
| case MHD_CONNECTION_START_REPLY: |
| mhd_assert (NULL != connection->rp.response); |
| connection_switch_from_recv_to_send (connection); |
| if (MHD_NO == build_header_response (connection)) |
| { |
| /* oops - close! */ |
| CONNECTION_CLOSE_ERROR (connection, |
| _ ("Closing connection (failed to create " |
| "response header).\n")); |
| continue; |
| } |
| connection->state = MHD_CONNECTION_HEADERS_SENDING; |
| break; |
| |
| case MHD_CONNECTION_HEADERS_SENDING: |
| /* no default action */ |
| break; |
| case MHD_CONNECTION_HEADERS_SENT: |
| #ifdef UPGRADE_SUPPORT |
| if (NULL != connection->rp.response->upgrade_handler) |
| { |
| connection->state = MHD_CONNECTION_UPGRADE; |
| /* This connection is "upgraded". Pass socket to application. */ |
| if (MHD_NO == |
| MHD_response_execute_upgrade_ (connection->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 = connection->rp.response; |
| |
| connection->rp.response = NULL; |
| MHD_destroy_response (resp); |
| } |
| continue; |
| } |
| #endif /* UPGRADE_SUPPORT */ |
| |
| if (connection->rp.props.send_reply_body) |
| { |
| if (connection->rp.props.chunked) |
| connection->state = MHD_CONNECTION_CHUNKED_BODY_UNREADY; |
| else |
| connection->state = MHD_CONNECTION_NORMAL_BODY_UNREADY; |
| } |
| else |
| connection->state = MHD_CONNECTION_FULL_REPLY_SENT; |
| continue; |
| case MHD_CONNECTION_NORMAL_BODY_READY: |
| mhd_assert (connection->rp.props.send_reply_body); |
| mhd_assert (! connection->rp.props.chunked); |
| /* nothing to do here */ |
| break; |
| case MHD_CONNECTION_NORMAL_BODY_UNREADY: |
| mhd_assert (connection->rp.props.send_reply_body); |
| mhd_assert (! connection->rp.props.chunked); |
| #if defined(MHD_USE_POSIX_THREADS) || defined(MHD_USE_W32_THREADS) |
| if (NULL != connection->rp.response->crc) |
| MHD_mutex_lock_chk_ (&connection->rp.response->mutex); |
| #endif |
| if (0 == connection->rp.response->total_size) |
| { |
| #if defined(MHD_USE_POSIX_THREADS) || defined(MHD_USE_W32_THREADS) |
| if (NULL != connection->rp.response->crc) |
| MHD_mutex_unlock_chk_ (&connection->rp.response->mutex); |
| #endif |
| if (connection->rp.props.chunked) |
| connection->state = MHD_CONNECTION_CHUNKED_BODY_SENT; |
| else |
| connection->state = MHD_CONNECTION_FULL_REPLY_SENT; |
| continue; |
| } |
| if (MHD_NO != try_ready_normal_body (connection)) |
| { |
| #if defined(MHD_USE_POSIX_THREADS) || defined(MHD_USE_W32_THREADS) |
| if (NULL != connection->rp.response->crc) |
| MHD_mutex_unlock_chk_ (&connection->rp.response->mutex); |
| #endif |
| connection->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 (connection->rp.props.send_reply_body); |
| mhd_assert (connection->rp.props.chunked); |
| /* nothing to do here */ |
| break; |
| case MHD_CONNECTION_CHUNKED_BODY_UNREADY: |
| mhd_assert (connection->rp.props.send_reply_body); |
| mhd_assert (connection->rp.props.chunked); |
| #if defined(MHD_USE_POSIX_THREADS) || defined(MHD_USE_W32_THREADS) |
| if (NULL != connection->rp.response->crc) |
| MHD_mutex_lock_chk_ (&connection->rp.response->mutex); |
| #endif |
| if ( (0 == connection->rp.response->total_size) || |
| (connection->rp.rsp_write_position == |
| connection->rp.response->total_size) ) |
| { |
| #if defined(MHD_USE_POSIX_THREADS) || defined(MHD_USE_W32_THREADS) |
| if (NULL != connection->rp.response->crc) |
| MHD_mutex_unlock_chk_ (&connection->rp.response->mutex); |
| #endif |
| connection->state = MHD_CONNECTION_CHUNKED_BODY_SENT; |
| continue; |
| } |
| if (1) |
| { /* pseudo-branch for local variables scope */ |
| bool finished; |
| if (MHD_NO != try_ready_chunked_body (connection, &finished)) |
| { |
| #if defined(MHD_USE_POSIX_THREADS) || defined(MHD_USE_W32_THREADS) |
| if (NULL != connection->rp.response->crc) |
| MHD_mutex_unlock_chk_ (&connection->rp.response->mutex); |
| #endif |
| connection->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 (connection->rp.props.send_reply_body); |
| mhd_assert (connection->rp.props.chunked); |
| mhd_assert (connection->write_buffer_send_offset <= \ |
| connection->write_buffer_append_offset); |
| |
| if (MHD_NO == build_connection_chunked_response_footer (connection)) |
| { |
| /* oops - close! */ |
| CONNECTION_CLOSE_ERROR (connection, |
| _ ("Closing connection (failed to create " \ |
| "response footer).")); |
| continue; |
| } |
| mhd_assert (connection->write_buffer_send_offset < \ |
| connection->write_buffer_append_offset); |
| connection->state = MHD_CONNECTION_FOOTERS_SENDING; |
| continue; |
| case MHD_CONNECTION_FOOTERS_SENDING: |
| mhd_assert (connection->rp.props.send_reply_body); |
| mhd_assert (connection->rp.props.chunked); |
| /* no default action */ |
| break; |
| case MHD_CONNECTION_FULL_REPLY_SENT: |
| if (MHD_HTTP_PROCESSING == connection->rp.responseCode) |
| { |
| /* After this type of response, we allow sending another! */ |
| connection->state = MHD_CONNECTION_HEADERS_PROCESSED; |
| MHD_destroy_response (connection->rp.response); |
| connection->rp.response = NULL; |
| /* FIXME: maybe partially reset memory pool? */ |
| continue; |
| } |
| /* Reset connection after complete reply */ |
| connection_reset (connection, |
| MHD_CONN_USE_KEEPALIVE == connection->keepalive && |
| ! connection->read_closed && |
| ! connection->discard_request); |
| continue; |
| case MHD_CONNECTION_CLOSED: |
| cleanup_connection (connection); |
| connection->in_idle = false; |
| return MHD_NO; |
| #ifdef UPGRADE_SUPPORT |
| case MHD_CONNECTION_UPGRADE: |
| connection->in_idle = false; |
| return MHD_YES; /* keep open */ |
| #endif /* UPGRADE_SUPPORT */ |
| default: |
| mhd_assert (0); |
| break; |
| } |
| break; |
| } |
| if (connection_check_timedout (connection)) |
| { |
| MHD_connection_close_ (connection, |
| MHD_REQUEST_TERMINATED_TIMEOUT_REACHED); |
| connection->in_idle = false; |
| return MHD_YES; |
| } |
| MHD_connection_update_event_loop_info (connection); |
| ret = MHD_YES; |
| #ifdef EPOLL_SUPPORT |
| if ( (! connection->suspended) && |
| (0 != (daemon->options & MHD_USE_EPOLL)) ) |
| { |
| ret = MHD_connection_epoll_update_ (connection); |
| } |
| #endif /* EPOLL_SUPPORT */ |
| connection->in_idle = false; |
| return ret; |
| } |
| |
| |
| #ifdef EPOLL_SUPPORT |
| /** |
| * Perform epoll() processing, possibly moving the connection back into |
| * the epoll() set if needed. |
| * |
| * @param connection connection to process |
| * @return #MHD_YES if we should continue to process the |
| * connection (not dead yet), #MHD_NO if it died |
| */ |
| enum MHD_Result |
| MHD_connection_epoll_update_ (struct MHD_Connection *connection) |
| { |
| struct MHD_Daemon *const daemon = connection->daemon; |
| |
| mhd_assert (0 != (daemon->options & MHD_USE_EPOLL)); |
| |
| if ((0 != (MHD_EVENT_LOOP_INFO_PROCESS & connection->event_loop_info)) && |
| (0 == (connection->epoll_state & MHD_EPOLL_STATE_IN_EREADY_EDLL))) |
| { |
| /* Make sure that connection waiting for processing will be processed */ |
| EDLL_insert (daemon->eready_head, |
| daemon->eready_tail, |
| connection); |
| connection->epoll_state |= MHD_EPOLL_STATE_IN_EREADY_EDLL; |
| } |
| |
| if ( (0 == (connection->epoll_state & MHD_EPOLL_STATE_IN_EPOLL_SET)) && |
| (0 == (connection->epoll_state & MHD_EPOLL_STATE_SUSPENDED)) && |
| ( ( (MHD_EVENT_LOOP_INFO_WRITE == connection->event_loop_info) && |
| (0 == (connection->epoll_state & MHD_EPOLL_STATE_WRITE_READY))) || |
| ( (0 != (MHD_EVENT_LOOP_INFO_READ & connection->event_loop_info)) && |
| (0 == (connection->epoll_state & MHD_EPOLL_STATE_READ_READY)) ) ) ) |
| { |
| /* add to epoll set */ |
| struct epoll_event event; |
| |
| event.events = EPOLLIN | EPOLLOUT | EPOLLPRI | EPOLLET; |
| event.data.ptr = connection; |
| if (0 != epoll_ctl (daemon->epoll_fd, |
| EPOLL_CTL_ADD, |
| connection->socket_fd, |
| &event)) |
| { |
| #ifdef HAVE_MESSAGES |
| if (0 != (daemon->options & MHD_USE_ERROR_LOG)) |
| MHD_DLOG (daemon, |
| _ ("Call to epoll_ctl failed: %s\n"), |
| MHD_socket_last_strerr_ ()); |
| #endif |
| connection->state = MHD_CONNECTION_CLOSED; |
| cleanup_connection (connection); |
| return MHD_NO; |
| } |
| connection->epoll_state |= MHD_EPOLL_STATE_IN_EPOLL_SET; |
| } |
| return MHD_YES; |
| } |
| |
| |
| #endif |
| |
| |
| /** |
| * Set callbacks for this connection to those for HTTP. |
| * |
| * @param connection connection to initialize |
| */ |
| void |
| MHD_set_http_callbacks_ (struct MHD_Connection *connection) |
| { |
| connection->recv_cls = &recv_param_adapter; |
| } |
| |
| |
| /** |
| * Obtain information about the given connection. |
| * The returned pointer is invalidated with the next call of this function or |
| * when the connection is closed. |
| * |
| * @param connection what connection to get information about |
| * @param info_type what information is desired? |
| * @param ... depends on @a info_type |
| * @return NULL if this information is not available |
| * (or if the @a info_type is unknown) |
| * @ingroup specialized |
| */ |
| _MHD_EXTERN const union MHD_ConnectionInfo * |
| MHD_get_connection_info (struct MHD_Connection *connection, |
| enum MHD_ConnectionInfoType info_type, |
| ...) |
| { |
| switch (info_type) |
| { |
| #ifdef HTTPS_SUPPORT |
| case MHD_CONNECTION_INFO_CIPHER_ALGO: |
| if (NULL == connection->tls_session) |
| return NULL; |
| if (1) |
| { /* Workaround to mute compiler warning */ |
| gnutls_cipher_algorithm_t res; |
| res = gnutls_cipher_get (connection->tls_session); |
| connection->connection_info_dummy.cipher_algorithm = (int) res; |
| } |
| return &connection->connection_info_dummy; |
| case MHD_CONNECTION_INFO_PROTOCOL: |
| if (NULL == connection->tls_session) |
| return NULL; |
| if (1) |
| { /* Workaround to mute compiler warning */ |
| gnutls_protocol_t res; |
| res = gnutls_protocol_get_version (connection->tls_session); |
| connection->connection_info_dummy.protocol = (int) res; |
| } |
| return &connection->connection_info_dummy; |
| case MHD_CONNECTION_INFO_GNUTLS_SESSION: |
| if (NULL == connection->tls_session) |
| return NULL; |
| connection->connection_info_dummy.tls_session = connection->tls_session; |
| return &connection->connection_info_dummy; |
| #else /* ! HTTPS_SUPPORT */ |
| case MHD_CONNECTION_INFO_CIPHER_ALGO: |
| case MHD_CONNECTION_INFO_PROTOCOL: |
| case MHD_CONNECTION_INFO_GNUTLS_SESSION: |
| #endif /* ! HTTPS_SUPPORT */ |
| case MHD_CONNECTION_INFO_GNUTLS_CLIENT_CERT: |
| return NULL; /* Not implemented */ |
| case MHD_CONNECTION_INFO_CLIENT_ADDRESS: |
| if (0 < connection->addr_len) |
| { |
| mhd_assert (sizeof (connection->addr) == \ |
| sizeof (connection->connection_info_dummy.client_addr)); |
| memcpy (&connection->connection_info_dummy.client_addr, |
| &connection->addr, |
| sizeof(connection->addr)); |
| return &connection->connection_info_dummy; |
| } |
| return NULL; |
| case MHD_CONNECTION_INFO_DAEMON: |
| connection->connection_info_dummy.daemon = connection->daemon; |
| return &connection->connection_info_dummy; |
| case MHD_CONNECTION_INFO_CONNECTION_FD: |
| connection->connection_info_dummy.connect_fd = connection->socket_fd; |
| return &connection->connection_info_dummy; |
| case MHD_CONNECTION_INFO_SOCKET_CONTEXT: |
| connection->connection_info_dummy.socket_context = |
| connection->socket_context; |
| return &connection->connection_info_dummy; |
| case MHD_CONNECTION_INFO_CONNECTION_SUSPENDED: |
| connection->connection_info_dummy.suspended = |
| connection->suspended ? MHD_YES : MHD_NO; |
| return &connection->connection_info_dummy; |
| case MHD_CONNECTION_INFO_CONNECTION_TIMEOUT: |
| #if SIZEOF_UNSIGNED_INT <= (SIZEOF_UINT64_T - 2) |
| if (UINT_MAX < connection->connection_timeout_ms / 1000) |
| connection->connection_info_dummy.connection_timeout = UINT_MAX; |
| else |
| #endif /* SIZEOF_UNSIGNED_INT <=(SIZEOF_UINT64_T - 2) */ |
| connection->connection_info_dummy.connection_timeout = |
| (unsigned int) (connection->connection_timeout_ms / 1000); |
| return &connection->connection_info_dummy; |
| case MHD_CONNECTION_INFO_REQUEST_HEADER_SIZE: |
| if ( (MHD_CONNECTION_HEADERS_RECEIVED > connection->state) || |
| (MHD_CONNECTION_CLOSED == connection->state) ) |
| return NULL; /* invalid, too early! */ |
| connection->connection_info_dummy.header_size = connection->rq.header_size; |
| return &connection->connection_info_dummy; |
| case MHD_CONNECTION_INFO_HTTP_STATUS: |
| if (NULL == connection->rp.response) |
| return NULL; |
| connection->connection_info_dummy.http_status = connection->rp.responseCode; |
| return &connection->connection_info_dummy; |
| default: |
| return NULL; |
| } |
| } |
| |
| |
| /** |
| * Set a custom option for the given connection, overriding defaults. |
| * |
| * @param connection connection to modify |
| * @param option option to set |
| * @param ... arguments to the option, depending on the option type |
| * @return #MHD_YES on success, #MHD_NO if setting the option failed |
| * @ingroup specialized |
| */ |
| _MHD_EXTERN enum MHD_Result |
| MHD_set_connection_option (struct MHD_Connection *connection, |
| enum MHD_CONNECTION_OPTION option, |
| ...) |
| { |
| va_list ap; |
| struct MHD_Daemon *daemon; |
| unsigned int ui_val; |
| |
| daemon = connection->daemon; |
| switch (option) |
| { |
| case MHD_CONNECTION_OPTION_TIMEOUT: |
| if (0 == connection->connection_timeout_ms) |
| connection->last_activity = MHD_monotonic_msec_counter (); |
| va_start (ap, option); |
| ui_val = va_arg (ap, unsigned int); |
| va_end (ap); |
| #if (SIZEOF_UINT64_T - 2) <= SIZEOF_UNSIGNED_INT |
| if ((UINT64_MAX / 4000 - 1) < ui_val) |
| { |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (connection->daemon, |
| _ ("The specified connection timeout (%u) is too " \ |
| "large. Maximum allowed value (%" PRIu64 ") will be used " \ |
| "instead.\n"), |
| ui_val, |
| (UINT64_MAX / 4000 - 1)); |
| #endif |
| ui_val = UINT64_MAX / 4000 - 1; |
| } |
| #endif /* (SIZEOF_UINT64_T - 2) <= SIZEOF_UNSIGNED_INT */ |
| if (0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) |
| { |
| #if defined(MHD_USE_THREADS) |
| MHD_mutex_lock_chk_ (&daemon->cleanup_connection_mutex); |
| #endif |
| if (! connection->suspended) |
| { |
| if (connection->connection_timeout_ms == daemon->connection_timeout_ms) |
| XDLL_remove (daemon->normal_timeout_head, |
| daemon->normal_timeout_tail, |
| connection); |
| else |
| XDLL_remove (daemon->manual_timeout_head, |
| daemon->manual_timeout_tail, |
| connection); |
| connection->connection_timeout_ms = ui_val * 1000; |
| if (connection->connection_timeout_ms == daemon->connection_timeout_ms) |
| XDLL_insert (daemon->normal_timeout_head, |
| daemon->normal_timeout_tail, |
| connection); |
| else |
| XDLL_insert (daemon->manual_timeout_head, |
| daemon->manual_timeout_tail, |
| connection); |
| } |
| #if defined(MHD_USE_THREADS) |
| MHD_mutex_unlock_chk_ (&daemon->cleanup_connection_mutex); |
| #endif |
| } |
| return MHD_YES; |
| default: |
| return MHD_NO; |
| } |
| } |
| |
| |
| /** |
| * Queue a response to be transmitted to the client (as soon as |
| * possible but after #MHD_AccessHandlerCallback returns). |
| * |
| * For any active connection this function must be called |
| * only by #MHD_AccessHandlerCallback callback. |
| * For suspended connection this function can be called at any moment. Response |
| * will be sent as soon as connection is resumed. |
| * |
| * If HTTP specifications require use no body in reply, like @a status_code with |
| * value 1xx, the response body is automatically not sent even if it is present |
| * in the response. No "Content-Length" or "Transfer-Encoding" headers are |
| * generated and added. |
| * |
| * When the response is used to respond HEAD request or used with @a status_code |
| * #MHD_HTTP_NOT_MODIFIED, then response body is not sent, but "Content-Length" |
| * header is added automatically based the size of the body in the response. |
| * If body size it set to #MHD_SIZE_UNKNOWN or chunked encoding is enforced |
| * then "Transfer-Encoding: chunked" header (for HTTP/1.1 only) is added instead |
| * of "Content-Length" header. |
| * |
| * In situations, where reply body is required, like answer for the GET request |
| * with @a status_code #MHD_HTTP_OK, headers "Content-Length" (for known body |
| * size) or "Transfer-Encoding: chunked" (for #MHD_SIZE_UNKNOWN with HTTP/1.1) |
| * are added automatically. |
| * In practice, the same response object can be used to respond to both HEAD and |
| * GET requests. |
| * |
| * @param connection the connection identifying the client |
| * @param status_code HTTP status code (i.e. #MHD_HTTP_OK) |
| * @param response response to transmit, the NULL is tolerated |
| * @return #MHD_NO on error (reply already sent, response is NULL), |
| * #MHD_YES on success or if message has been queued |
| * @ingroup response |
| * @sa #MHD_AccessHandlerCallback |
| */ |
| _MHD_EXTERN enum MHD_Result |
| MHD_queue_response (struct MHD_Connection *connection, |
| unsigned int status_code, |
| struct MHD_Response *response) |
| { |
| struct MHD_Daemon *daemon; |
| bool reply_icy; |
| |
| reply_icy = (0 != (status_code & MHD_ICY_FLAG)); |
| status_code &= ~MHD_ICY_FLAG; |
| if ((NULL == connection) || (NULL == response)) |
| return MHD_NO; |
| |
| daemon = connection->daemon; |
| |
| #if defined(MHD_USE_POSIX_THREADS) || defined(MHD_USE_W32_THREADS) |
| if ( (! connection->suspended) && |
| (0 != (daemon->options & MHD_USE_INTERNAL_POLLING_THREAD)) && |
| (! MHD_thread_ID_match_current_ (connection->pid)) ) |
| { |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| _ ("Attempted to queue response on wrong thread!\n")); |
| #endif |
| return MHD_NO; |
| } |
| #endif |
| |
| if (daemon->shutdown) |
| return MHD_YES; /* If daemon was shut down in parallel, |
| * response will be aborted now or on later stage. */ |
| |
| if ( (NULL != connection->rp.response) || |
| ( (MHD_CONNECTION_HEADERS_PROCESSED != connection->state) && |
| (MHD_CONNECTION_FULL_REQ_RECEIVED != connection->state) ) ) |
| return MHD_NO; |
| |
| #ifdef UPGRADE_SUPPORT |
| if (NULL != response->upgrade_handler) |
| { |
| struct MHD_HTTP_Res_Header *conn_header; |
| if (0 == (daemon->options & MHD_ALLOW_UPGRADE)) |
| { |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| _ ("Attempted 'upgrade' connection on daemon without" \ |
| " MHD_ALLOW_UPGRADE option!\n")); |
| #endif |
| return MHD_NO; |
| } |
| if (MHD_HTTP_SWITCHING_PROTOCOLS != status_code) |
| { |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| _ ("Application used invalid status code for" \ |
| " 'upgrade' response!\n")); |
| #endif |
| return MHD_NO; |
| } |
| if (0 == (response->flags_auto & MHD_RAF_HAS_CONNECTION_HDR)) |
| { |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| _ ("Application used invalid response" \ |
| " without \"Connection\" header!\n")); |
| #endif |
| return MHD_NO; |
| } |
| conn_header = response->first_header; |
| mhd_assert (NULL != conn_header); |
| mhd_assert (MHD_str_equal_caseless_ (conn_header->header, |
| MHD_HTTP_HEADER_CONNECTION)); |
| if (! MHD_str_has_s_token_caseless_ (conn_header->value, |
| "upgrade")) |
| { |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| _ ("Application used invalid response" \ |
| " without \"upgrade\" token in" \ |
| " \"Connection\" header!\n")); |
| #endif |
| return MHD_NO; |
| } |
| if (! MHD_IS_HTTP_VER_1_1_COMPAT (connection->rq.http_ver)) |
| { |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| _ ("Connection \"Upgrade\" can be used only " \ |
| "with HTTP/1.1 connections!\n")); |
| #endif |
| return MHD_NO; |
| } |
| } |
| #endif /* UPGRADE_SUPPORT */ |
| if (MHD_HTTP_SWITCHING_PROTOCOLS == status_code) |
| { |
| #ifdef UPGRADE_SUPPORT |
| if (NULL == response->upgrade_handler) |
| { |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| _ ("Application used status code 101 \"Switching Protocols\" " \ |
| "with non-'upgrade' response!\n")); |
| #endif /* HAVE_MESSAGES */ |
| return MHD_NO; |
| } |
| #else /* ! UPGRADE_SUPPORT */ |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| _ ("Application used status code 101 \"Switching Protocols\", " \ |
| "but this MHD was built without \"Upgrade\" support!\n")); |
| #endif /* HAVE_MESSAGES */ |
| return MHD_NO; |
| #endif /* ! UPGRADE_SUPPORT */ |
| } |
| if ( (100 > status_code) || |
| (999 < status_code) ) |
| { |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| _ ("Refused wrong status code (%u). " \ |
| "HTTP requires three digits status code!\n"), |
| status_code); |
| #endif |
| return MHD_NO; |
| } |
| if (200 > status_code) |
| { |
| if (MHD_HTTP_VER_1_0 == connection->rq.http_ver) |
| { |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| _ ("Wrong status code (%u) refused. " \ |
| "HTTP/1.0 clients do not support 1xx status codes!\n"), |
| (status_code)); |
| #endif |
| return MHD_NO; |
| } |
| if (0 != (response->flags & (MHD_RF_HTTP_1_0_COMPATIBLE_STRICT |
| | MHD_RF_HTTP_1_0_SERVER))) |
| { |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| _ ("Wrong status code (%u) refused. " \ |
| "HTTP/1.0 reply mode does not support 1xx status codes!\n"), |
| (status_code)); |
| #endif |
| return MHD_NO; |
| } |
| } |
| if ( (MHD_HTTP_MTHD_CONNECT == connection->rq.http_mthd) && |
| (2 == status_code / 100) ) |
| { |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| _ ("Successful (%u) response code cannot be used to answer " \ |
| "\"CONNECT\" request!\n"), |
| (status_code)); |
| #endif |
| return MHD_NO; |
| } |
| |
| if ( (0 != (MHD_RF_HEAD_ONLY_RESPONSE & response->flags)) && |
| (RP_BODY_HEADERS_ONLY < is_reply_body_needed (connection, status_code)) ) |
| { |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| _ ("HEAD-only response cannot be used when the request requires " |
| "reply body to be sent!\n")); |
| #endif |
| return MHD_NO; |
| } |
| |
| #ifdef HAVE_MESSAGES |
| if ( (0 != (MHD_RF_INSANITY_HEADER_CONTENT_LENGTH & response->flags)) && |
| (0 != (MHD_RAF_HAS_CONTENT_LENGTH & response->flags_auto)) ) |
| { |
| MHD_DLOG (daemon, |
| _ ("The response has application-defined \"Content-Length\" " \ |
| "header. The reply to the request will be not " \ |
| "HTTP-compliant and may result in hung connection or " \ |
| "other problems!\n")); |
| } |
| #endif |
| |
| MHD_increment_response_rc (response); |
| connection->rp.response = response; |
| connection->rp.responseCode = status_code; |
| connection->rp.responseIcy = reply_icy; |
| #if defined(_MHD_HAVE_SENDFILE) |
| if ( (response->fd == -1) || |
| (response->is_pipe) || |
| (0 != (connection->daemon->options & MHD_USE_TLS)) |
| #if defined(MHD_SEND_SPIPE_SUPPRESS_NEEDED) && \ |
| defined(MHD_SEND_SPIPE_SUPPRESS_POSSIBLE) |
| || (! daemon->sigpipe_blocked && ! connection->sk_spipe_suppress) |
| #endif /* MHD_SEND_SPIPE_SUPPRESS_NEEDED && |
| MHD_SEND_SPIPE_SUPPRESS_POSSIBLE */ |
| ) |
| connection->rp.resp_sender = MHD_resp_sender_std; |
| else |
| connection->rp.resp_sender = MHD_resp_sender_sendfile; |
| #endif /* _MHD_HAVE_SENDFILE */ |
| /* FIXME: if 'is_pipe' is set, TLS is off, and we have *splice*, we could use splice() |
| to avoid two user-space copies... */ |
| |
| if ( (MHD_HTTP_MTHD_HEAD == connection->rq.http_mthd) || |
| (MHD_HTTP_OK > status_code) || |
| (MHD_HTTP_NO_CONTENT == status_code) || |
| (MHD_HTTP_NOT_MODIFIED == status_code) ) |
| { |
| /* if this is a "HEAD" request, or a status code for |
| which a body is not allowed, pretend that we |
| have already sent the full message body. */ |
| /* TODO: remove the next assignment, use 'rp_props.send_reply_body' in |
| * checks */ |
| connection->rp.rsp_write_position = response->total_size; |
| } |
| if (MHD_CONNECTION_HEADERS_PROCESSED == connection->state) |
| { |
| /* response was queued "early", refuse to read body / footers or |
| further requests! */ |
| connection->discard_request = true; |
| connection->state = MHD_CONNECTION_START_REPLY; |
| connection->rq.remaining_upload_size = 0; |
| } |
| if (! connection->in_idle) |
| (void) MHD_connection_handle_idle (connection); |
| MHD_update_last_activity_ (connection); |
| return MHD_YES; |
| } |
| |
| |
| /* end of connection.c */ |