| /* |
| This file is part of libmicrohttpd |
| Copyright (C) 2007-2018 Daniel Pittman and Christian Grothoff |
| |
| 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 lib/connection_call_handlers.c |
| * @brief call the connection's handlers based on the event trigger |
| * @author Christian Grothoff |
| */ |
| #include "internal.h" |
| #include "connection_call_handlers.h" |
| #include "connection_update_last_activity.h" |
| #include "connection_close.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 */ |
| |
| |
| /** |
| * sendfile() chuck size |
| */ |
| #define MHD_SENFILE_CHUNK_ (0x20000) |
| |
| /** |
| * sendfile() chuck size for thread-per-connection |
| */ |
| #define MHD_SENFILE_CHUNK_THR_P_C_ (0x200000) |
| |
| |
| /** |
| * 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 (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 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 |
| |
| |
| #ifdef HAVE_FREEBSD_SENDFILE |
| #ifdef SF_FLAGS |
| /** |
| * FreeBSD sendfile() flags |
| */ |
| static int freebsd_sendfile_flags_; |
| |
| /** |
| * FreeBSD sendfile() flags for thread-per-connection |
| */ |
| static int freebsd_sendfile_flags_thd_p_c_; |
| #endif /* SF_FLAGS */ |
| |
| |
| /** |
| * Initialises static variables. |
| * |
| * FIXME: make sure its actually called! |
| */ |
| void |
| MHD_conn_init_static_ (void) |
| { |
| /* FreeBSD 11 and later allow to specify read-ahead size |
| * and handles SF_NODISKIO differently. |
| * SF_FLAGS defined only on FreeBSD 11 and later. */ |
| #ifdef SF_FLAGS |
| long sys_page_size = sysconf (_SC_PAGESIZE); |
| if (0 > sys_page_size) |
| { /* Failed to get page size. */ |
| freebsd_sendfile_flags_ = SF_NODISKIO; |
| freebsd_sendfile_flags_thd_p_c_ = SF_NODISKIO; |
| } |
| else |
| { |
| freebsd_sendfile_flags_ = |
| SF_FLAGS ((uint16_t) (MHD_SENFILE_CHUNK_ / sys_page_size), SF_NODISKIO); |
| freebsd_sendfile_flags_thd_p_c_ = |
| SF_FLAGS ((uint16_t) (MHD_SENFILE_CHUNK_THR_P_C_ / sys_page_size), |
| SF_NODISKIO); |
| } |
| #endif /* SF_FLAGS */ |
| } |
| |
| |
| #endif /* HAVE_FREEBSD_SENDFILE */ |
| |
| |
| /** |
| * Message to transmit when http 1.1 request is received |
| */ |
| #define HTTP_100_CONTINUE "HTTP/1.1 100 Continue\r\n\r\n" |
| |
| |
| /** |
| * A serious error occurred, close the |
| * connection (and notify the application). |
| * |
| * @param connection connection to close with error |
| * @param sc the reason for closing the connection |
| * @param emsg error message (can be NULL) |
| */ |
| static void |
| connection_close_error (struct MHD_Connection *connection, |
| enum MHD_StatusCode sc, |
| const char *emsg) |
| { |
| #ifdef HAVE_MESSAGES |
| if (NULL != emsg) |
| MHD_DLOG (connection->daemon, |
| sc, |
| emsg); |
| #else /* ! HAVE_MESSAGES */ |
| (void) emsg; /* Mute compiler warning. */ |
| (void) sc; |
| #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, sc, emsg) connection_close_error (c, sc, emsg) |
| #else |
| #define CONNECTION_CLOSE_ERROR(c, sc, emsg) connection_close_error (c, sc, NULL) |
| #endif |
| |
| |
| /** |
| * 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 request the request for which to grow the buffer |
| * @return true on success, false on failure |
| */ |
| static bool |
| try_grow_read_buffer (struct MHD_Request *request) |
| { |
| struct MHD_Daemon *daemon = request->daemon; |
| void *buf; |
| size_t new_size; |
| |
| if (0 == request->read_buffer_size) |
| new_size = daemon->connection_memory_limit_b / 2; |
| else |
| new_size = request->read_buffer_size |
| + daemon->connection_memory_increment_b; |
| buf = MHD_pool_reallocate (request->connection->pool, |
| request->read_buffer, |
| request->read_buffer_size, |
| new_size); |
| if (NULL == buf) |
| return false; |
| /* we can actually grow the buffer, do it! */ |
| request->read_buffer = buf; |
| request->read_buffer_size = new_size; |
| return true; |
| } |
| |
| |
| /** |
| * This function handles a particular request when it has been |
| * determined that there is data to be read off a socket. |
| * |
| * @param request request to handle |
| */ |
| static void |
| MHD_request_handle_read_ (struct MHD_Request *request) |
| { |
| struct MHD_Daemon *daemon = request->daemon; |
| struct MHD_Connection *connection = request->connection; |
| ssize_t bytes_read; |
| |
| if ( (MHD_REQUEST_CLOSED == request->state) || |
| (connection->suspended) ) |
| return; |
| #ifdef HTTPS_SUPPORT |
| { |
| struct MHD_TLS_Plugin *tls; |
| |
| if ( (NULL != (tls = daemon->tls_api)) && |
| (! tls->handshake (tls->cls, |
| connection->tls_cs)) ) |
| return; |
| } |
| #endif /* HTTPS_SUPPORT */ |
| |
| /* make sure "read" has a reasonable number of bytes |
| in buffer to use per system call (if possible) */ |
| if (request->read_buffer_offset |
| + daemon->connection_memory_increment_b > |
| request->read_buffer_size) |
| try_grow_read_buffer (request); |
| |
| if (request->read_buffer_size == request->read_buffer_offset) |
| return; /* No space for receiving data. */ |
| bytes_read = connection->recv_cls (connection, |
| &request->read_buffer |
| [request->read_buffer_offset], |
| request->read_buffer_size |
| - request->read_buffer_offset); |
| if (bytes_read < 0) |
| { |
| if (MHD_ERR_AGAIN_ == bytes_read) |
| return; /* No new data to process. */ |
| if (MHD_ERR_CONNRESET_ == bytes_read) |
| { |
| CONNECTION_CLOSE_ERROR (connection, |
| (MHD_REQUEST_INIT == request->state) |
| ? MHD_SC_CONNECTION_CLOSED |
| : MHD_SC_CONNECTION_RESET_CLOSED, |
| (MHD_REQUEST_INIT == request->state) |
| ? NULL |
| : _ ( |
| "Socket disconnected while reading request.\n")); |
| return; |
| } |
| CONNECTION_CLOSE_ERROR (connection, |
| (MHD_REQUEST_INIT == request->state) |
| ? MHD_SC_CONNECTION_CLOSED |
| : MHD_SC_CONNECTION_READ_FAIL_CLOSED, |
| (MHD_REQUEST_INIT == request->state) |
| ? NULL |
| : _ ( |
| "Connection socket is closed due to error when reading request.\n")); |
| return; |
| } |
| |
| if (0 == bytes_read) |
| { /* Remote side closed connection. */ |
| connection->read_closed = true; |
| MHD_connection_close_ (connection, |
| MHD_REQUEST_TERMINATED_CLIENT_ABORT); |
| return; |
| } |
| request->read_buffer_offset += bytes_read; |
| MHD_connection_update_last_activity_ (connection); |
| #if DEBUG_STATES |
| MHD_DLOG (daemon, |
| MHD_SC_STATE_MACHINE_STATUS_REPORT, |
| _ ("In function %s handling connection at state: %s\n"), |
| __FUNCTION__, |
| MHD_state_to_string (request->state)); |
| #endif |
| switch (request->state) |
| { |
| case MHD_REQUEST_INIT: |
| case MHD_REQUEST_URL_RECEIVED: |
| case MHD_REQUEST_HEADER_PART_RECEIVED: |
| case MHD_REQUEST_HEADERS_RECEIVED: |
| case MHD_REQUEST_HEADERS_PROCESSED: |
| case MHD_REQUEST_CONTINUE_SENDING: |
| case MHD_REQUEST_CONTINUE_SENT: |
| case MHD_REQUEST_BODY_RECEIVED: |
| case MHD_REQUEST_FOOTER_PART_RECEIVED: |
| /* nothing to do but default action */ |
| if (connection->read_closed) |
| { |
| MHD_connection_close_ (connection, |
| MHD_REQUEST_TERMINATED_READ_ERROR); |
| } |
| return; |
| case MHD_REQUEST_CLOSED: |
| return; |
| #ifdef UPGRADE_SUPPORT |
| case MHD_REQUEST_UPGRADE: |
| mhd_assert (0); |
| return; |
| #endif /* UPGRADE_SUPPORT */ |
| default: |
| /* shrink read buffer to how much is actually used */ |
| MHD_pool_reallocate (connection->pool, |
| request->read_buffer, |
| request->read_buffer_size + 1, |
| request->read_buffer_offset); |
| break; |
| } |
| return; |
| } |
| |
| |
| #if defined(_MHD_HAVE_SENDFILE) |
| /** |
| * Function for sending responses backed by file FD. |
| * |
| * @param connection the MHD connection structure |
| * @return actual number of bytes sent |
| */ |
| static ssize_t |
| sendfile_adapter (struct MHD_Connection *connection) |
| { |
| struct MHD_Daemon *daemon = connection->daemon; |
| struct MHD_Request *request = &connection->request; |
| struct MHD_Response *response = request->response; |
| ssize_t ret; |
| const int file_fd = response->fd; |
| uint64_t left; |
| uint64_t offsetu64; |
| #ifndef HAVE_SENDFILE64 |
| const uint64_t max_off_t = (uint64_t) OFF_T_MAX; |
| #else /* HAVE_SENDFILE64 */ |
| const uint64_t max_off_t = (uint64_t) OFF64_T_MAX; |
| #endif /* HAVE_SENDFILE64 */ |
| #ifdef MHD_LINUX_SOLARIS_SENDFILE |
| #ifndef HAVE_SENDFILE64 |
| off_t offset; |
| #else /* HAVE_SENDFILE64 */ |
| off64_t offset; |
| #endif /* HAVE_SENDFILE64 */ |
| #endif /* MHD_LINUX_SOLARIS_SENDFILE */ |
| #ifdef HAVE_FREEBSD_SENDFILE |
| off_t sent_bytes; |
| int flags = 0; |
| #endif |
| #ifdef HAVE_DARWIN_SENDFILE |
| off_t len; |
| #endif /* HAVE_DARWIN_SENDFILE */ |
| const bool used_thr_p_c = (MHD_TM_THREAD_PER_CONNECTION == |
| daemon->threading_mode); |
| const size_t chunk_size = used_thr_p_c ? MHD_SENFILE_CHUNK_THR_P_C_ : |
| MHD_SENFILE_CHUNK_; |
| size_t send_size = 0; |
| |
| mhd_assert (MHD_resp_sender_sendfile == request->resp_sender); |
| offsetu64 = request->response_write_position + response->fd_off; |
| left = response->total_size - request->response_write_position; |
| /* Do not allow system to stick sending on single fast connection: |
| * use 128KiB chunks (2MiB for thread-per-connection). */ |
| send_size = (left > chunk_size) ? chunk_size : (size_t) left; |
| if (max_off_t < offsetu64) |
| { /* Retry to send with standard 'send()'. */ |
| request->resp_sender = MHD_resp_sender_std; |
| return MHD_ERR_AGAIN_; |
| } |
| #ifdef MHD_LINUX_SOLARIS_SENDFILE |
| #ifndef HAVE_SENDFILE64 |
| offset = (off_t) offsetu64; |
| ret = sendfile (connection->socket_fd, |
| file_fd, |
| &offset, |
| send_size); |
| #else /* HAVE_SENDFILE64 */ |
| offset = (off64_t) offsetu64; |
| ret = sendfile64 (connection->socket_fd, |
| file_fd, |
| &offset, |
| send_size); |
| #endif /* HAVE_SENDFILE64 */ |
| if (0 > ret) |
| { |
| const int err = MHD_socket_get_error_ (); |
| |
| if (MHD_SCKT_ERR_IS_EAGAIN_ (err)) |
| { |
| #ifdef EPOLL_SUPPORT |
| /* EAGAIN --- no longer write-ready */ |
| connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY; |
| #endif /* EPOLL_SUPPORT */ |
| return MHD_ERR_AGAIN_; |
| } |
| if (MHD_SCKT_ERR_IS_EINTR_ (err)) |
| return MHD_ERR_AGAIN_; |
| #ifdef HAVE_LINUX_SENDFILE |
| if (MHD_SCKT_ERR_IS_ (err, |
| MHD_SCKT_EBADF_)) |
| return MHD_ERR_BADF_; |
| /* sendfile() failed with EINVAL if mmap()-like operations are not |
| supported for FD or other 'unusual' errors occurred, so we should try |
| to fall back to 'SEND'; see also this thread for info on |
| odd libc/Linux behavior with sendfile: |
| http://lists.gnu.org/archive/html/libmicrohttpd/2011-02/msg00015.html */request->resp_sender = MHD_resp_sender_std; |
| return MHD_ERR_AGAIN_; |
| #else /* HAVE_SOLARIS_SENDFILE */ |
| if ( (EAFNOSUPPORT == err) || |
| (EINVAL == err) || |
| (EOPNOTSUPP == err) ) |
| { /* Retry with standard file reader. */ |
| request->resp_sender = MHD_resp_sender_std; |
| return MHD_ERR_AGAIN_; |
| } |
| if ( (ENOTCONN == err) || |
| (EPIPE == err) ) |
| { |
| return MHD_ERR_CONNRESET_; |
| } |
| return MHD_ERR_BADF_; /* Fail hard */ |
| #endif /* HAVE_SOLARIS_SENDFILE */ |
| } |
| #ifdef EPOLL_SUPPORT |
| else if (send_size > (size_t) ret) |
| connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY; |
| #endif /* EPOLL_SUPPORT */ |
| #elif defined(HAVE_FREEBSD_SENDFILE) |
| #ifdef SF_FLAGS |
| flags = used_thr_p_c ? |
| freebsd_sendfile_flags_thd_p_c_ : freebsd_sendfile_flags_; |
| #endif /* SF_FLAGS */ |
| if (0 != sendfile (file_fd, |
| connection->socket_fd, |
| (off_t) offsetu64, |
| send_size, |
| NULL, |
| &sent_bytes, |
| flags)) |
| { |
| const int err = MHD_socket_get_error_ (); |
| if (MHD_SCKT_ERR_IS_EAGAIN_ (err) || |
| MHD_SCKT_ERR_IS_EINTR_ (err) || |
| (EBUSY == err) ) |
| { |
| mhd_assert (SSIZE_MAX >= sent_bytes); |
| if (0 != sent_bytes) |
| return (ssize_t) sent_bytes; |
| |
| return MHD_ERR_AGAIN_; |
| } |
| /* Some unrecoverable error. Possibly file FD is not suitable |
| * for sendfile(). Retry with standard send(). */ |
| request->resp_sender = MHD_resp_sender_std; |
| return MHD_ERR_AGAIN_; |
| } |
| mhd_assert (0 < sent_bytes); |
| mhd_assert (SSIZE_MAX >= sent_bytes); |
| ret = (ssize_t) sent_bytes; |
| #elif defined(HAVE_DARWIN_SENDFILE) |
| len = (off_t) send_size; /* chunk always fit */ |
| if (0 != sendfile (file_fd, |
| connection->socket_fd, |
| (off_t) offsetu64, |
| &len, |
| NULL, |
| 0)) |
| { |
| const int err = MHD_socket_get_error_ (); |
| if (MHD_SCKT_ERR_IS_EAGAIN_ (err) || |
| MHD_SCKT_ERR_IS_EINTR_ (err)) |
| { |
| mhd_assert (0 <= len); |
| mhd_assert (SSIZE_MAX >= len); |
| mhd_assert (send_size >= (size_t) len); |
| if (0 != len) |
| return (ssize_t) len; |
| |
| return MHD_ERR_AGAIN_; |
| } |
| if ((ENOTCONN == err) || |
| (EPIPE == err) ) |
| return MHD_ERR_CONNRESET_; |
| if ((ENOTSUP == err) || |
| (EOPNOTSUPP == err) ) |
| { /* This file FD is not suitable for sendfile(). |
| * Retry with standard send(). */ |
| request->resp_sender = MHD_resp_sender_std; |
| return MHD_ERR_AGAIN_; |
| } |
| return MHD_ERR_BADF_; /* Return hard error. */ |
| } |
| mhd_assert (0 <= len); |
| mhd_assert (SSIZE_MAX >= len); |
| mhd_assert (send_size >= (size_t) len); |
| ret = (ssize_t) len; |
| #endif /* HAVE_FREEBSD_SENDFILE */ |
| return ret; |
| } |
| |
| |
| #endif /* _MHD_HAVE_SENDFILE */ |
| |
| |
| /** |
| * 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 false if we are not done, true if we are |
| */ |
| static bool |
| check_write_done (struct MHD_Request *request, |
| enum MHD_REQUEST_STATE next_state) |
| { |
| if (request->write_buffer_append_offset != |
| request->write_buffer_send_offset) |
| return false; |
| request->write_buffer_append_offset = 0; |
| request->write_buffer_send_offset = 0; |
| request->state = next_state; |
| MHD_pool_reallocate (request->connection->pool, |
| request->write_buffer, |
| request->write_buffer_size, |
| 0); |
| request->write_buffer = NULL; |
| request->write_buffer_size = 0; |
| return true; |
| } |
| |
| |
| /** |
| * Prepare the response buffer of this request for sending. Assumes |
| * that the response mutex is already held. If the transmission is |
| * complete, this function may close the socket (and return false). |
| * |
| * @param request the request handle |
| * @return false if readying the response failed (the |
| * lock on the response will have been released already |
| * in this case). |
| */ |
| static bool |
| try_ready_normal_body (struct MHD_Request *request) |
| { |
| struct MHD_Response *response = request->response; |
| struct MHD_Connection *connection = request->connection; |
| ssize_t ret; |
| |
| if (NULL == response->crc) |
| return true; |
| if ( (0 == response->total_size) || |
| (request->response_write_position == response->total_size) ) |
| return true; /* 0-byte response is always ready */ |
| if ( (response->data_start <= |
| request->response_write_position) && |
| (response->data_size + response->data_start > |
| request->response_write_position) ) |
| return true; /* response already ready */ |
| #if defined(_MHD_HAVE_SENDFILE) |
| if (MHD_resp_sender_sendfile == request->resp_sender) |
| { |
| /* will use sendfile, no need to bother response crc */ |
| return true; |
| } |
| #endif /* _MHD_HAVE_SENDFILE */ |
| |
| ret = response->crc (response->crc_cls, |
| request->response_write_position, |
| response->data, |
| (size_t) MHD_MIN ((uint64_t) response->data_buffer_size, |
| response->total_size |
| - request->response_write_position)); |
| if ( (((ssize_t) MHD_CONTENT_READER_END_OF_STREAM) == ret) || |
| (((ssize_t) MHD_CONTENT_READER_END_WITH_ERROR) == ret) ) |
| { |
| /* either error or http 1.0 transfer, close socket! */ |
| response->total_size = request->response_write_position; |
| MHD_mutex_unlock_chk_ (&response->mutex); |
| if ( ((ssize_t) MHD_CONTENT_READER_END_OF_STREAM) == ret) |
| MHD_connection_close_ (connection, |
| MHD_REQUEST_TERMINATED_COMPLETED_OK); |
| else |
| CONNECTION_CLOSE_ERROR (connection, |
| MHD_SC_APPLICATION_DATA_GENERATION_FAILURE_CLOSED, |
| _ ( |
| "Closing connection (application reported error generating data).\n")); |
| return false; |
| } |
| response->data_start = request->response_write_position; |
| response->data_size = ret; |
| if (0 == ret) |
| { |
| request->state = MHD_REQUEST_NORMAL_BODY_UNREADY; |
| MHD_mutex_unlock_chk_ (&response->mutex); |
| return false; |
| } |
| return true; |
| } |
| |
| |
| /** |
| * Prepare the response buffer of this request for sending. Assumes |
| * that the response mutex is already held. If the transmission is |
| * complete, this function may close the socket (and return false). |
| * |
| * @param connection the connection |
| * @return false if readying the response failed |
| */ |
| static bool |
| try_ready_chunked_body (struct MHD_Request *request) |
| { |
| struct MHD_Connection *connection = request->connection; |
| struct MHD_Response *response = request->response; |
| struct MHD_Daemon *daemon = request->daemon; |
| ssize_t ret; |
| char *buf; |
| size_t size; |
| char cbuf[10]; /* 10: max strlen of "%x\r\n" */ |
| int cblen; |
| |
| if (NULL == response->crc) |
| return true; |
| if (0 == request->write_buffer_size) |
| { |
| size = MHD_MIN (daemon->connection_memory_limit_b, |
| 2 * (0xFFFFFF + sizeof(cbuf) + 2)); |
| do |
| { |
| size /= 2; |
| if (size < 128) |
| { |
| MHD_mutex_unlock_chk_ (&response->mutex); |
| /* not enough memory */ |
| CONNECTION_CLOSE_ERROR (connection, |
| MHD_SC_CONNECTION_POOL_MALLOC_FAILURE, |
| _ ("Closing connection (out of memory).\n")); |
| return false; |
| } |
| buf = MHD_pool_allocate (connection->pool, |
| size, |
| MHD_NO); |
| } |
| while (NULL == buf); |
| request->write_buffer_size = size; |
| request->write_buffer = buf; |
| } |
| |
| if (0 == response->total_size) |
| ret = 0; /* response must be empty, don't bother calling crc */ |
| else if ( (response->data_start <= |
| request->response_write_position) && |
| (response->data_start + response->data_size > |
| request->response_write_position) ) |
| { |
| /* difference between response_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) (request->response_write_position - response->data_start); |
| /* buffer already ready, use what is there for the chunk */ |
| ret = response->data_size - data_write_offset; |
| if ( ((size_t) ret) > request->write_buffer_size - sizeof (cbuf) - 2) |
| ret = request->write_buffer_size - sizeof (cbuf) - 2; |
| memcpy (&request->write_buffer[sizeof (cbuf)], |
| &response->data[data_write_offset], |
| ret); |
| } |
| else |
| { |
| /* buffer not in range, try to fill it */ |
| ret = response->crc (response->crc_cls, |
| request->response_write_position, |
| &request->write_buffer[sizeof (cbuf)], |
| request->write_buffer_size - sizeof (cbuf) - 2); |
| } |
| if ( ((ssize_t) MHD_CONTENT_READER_END_WITH_ERROR) == ret) |
| { |
| /* error, close socket! */ |
| response->total_size = request->response_write_position; |
| MHD_mutex_unlock_chk_ (&response->mutex); |
| CONNECTION_CLOSE_ERROR (connection, |
| MHD_SC_APPLICATION_DATA_GENERATION_FAILURE_CLOSED, |
| _ ( |
| "Closing connection (application error generating response).\n")); |
| return false; |
| } |
| if ( (((ssize_t) MHD_CONTENT_READER_END_OF_STREAM) == ret) || |
| (0 == response->total_size) ) |
| { |
| /* end of message, signal other side! */ |
| memcpy (request->write_buffer, |
| "0\r\n", |
| 3); |
| request->write_buffer_append_offset = 3; |
| request->write_buffer_send_offset = 0; |
| response->total_size = request->response_write_position; |
| return true; |
| } |
| if (0 == ret) |
| { |
| request->state = MHD_REQUEST_CHUNKED_BODY_UNREADY; |
| MHD_mutex_unlock_chk_ (&response->mutex); |
| return false; |
| } |
| if (ret > 0xFFFFFF) |
| ret = 0xFFFFFF; |
| cblen = MHD_snprintf_ (cbuf, |
| sizeof (cbuf), |
| "%X\r\n", |
| (unsigned int) ret); |
| mhd_assert (cblen > 0); |
| mhd_assert ((size_t) cblen < sizeof(cbuf)); |
| memcpy (&request->write_buffer[sizeof (cbuf) - cblen], |
| cbuf, |
| cblen); |
| memcpy (&request->write_buffer[sizeof (cbuf) + ret], |
| "\r\n", |
| 2); |
| request->response_write_position += ret; |
| request->write_buffer_send_offset = sizeof (cbuf) - cblen; |
| request->write_buffer_append_offset = sizeof (cbuf) + ret + 2; |
| return true; |
| } |
| |
| |
| /** |
| * This function was created to handle writes to sockets when it has |
| * been determined that the socket can be written to. |
| * |
| * @param request the request to handle |
| */ |
| static void |
| MHD_request_handle_write_ (struct MHD_Request *request) |
| { |
| struct MHD_Daemon *daemon = request->daemon; |
| struct MHD_Connection *connection = request->connection; |
| struct MHD_Response *response; |
| ssize_t ret; |
| |
| if (connection->suspended) |
| return; |
| #ifdef HTTPS_SUPPORT |
| { |
| struct MHD_TLS_Plugin *tls; |
| |
| if ( (NULL != (tls = daemon->tls_api)) && |
| (! tls->handshake (tls->cls, |
| connection->tls_cs)) ) |
| return; |
| } |
| #endif /* HTTPS_SUPPORT */ |
| |
| #if DEBUG_STATES |
| MHD_DLOG (daemon, |
| MHD_SC_STATE_MACHINE_STATUS_REPORT, |
| _ ("In function %s handling connection at state: %s\n"), |
| __FUNCTION__, |
| MHD_state_to_string (request->state)); |
| #endif |
| switch (request->state) |
| { |
| case MHD_REQUEST_INIT: |
| case MHD_REQUEST_URL_RECEIVED: |
| case MHD_REQUEST_HEADER_PART_RECEIVED: |
| case MHD_REQUEST_HEADERS_RECEIVED: |
| mhd_assert (0); |
| return; |
| case MHD_REQUEST_HEADERS_PROCESSED: |
| return; |
| case MHD_REQUEST_CONTINUE_SENDING: |
| ret = connection->send_cls (connection, |
| &HTTP_100_CONTINUE |
| [request->continue_message_write_offset], |
| MHD_STATICSTR_LEN_ (HTTP_100_CONTINUE) |
| - request->continue_message_write_offset); |
| if (ret < 0) |
| { |
| if (MHD_ERR_AGAIN_ == ret) |
| return; |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| MHD_SC_CONNECTION_WRITE_FAIL_CLOSED, |
| _ ("Failed to send data in request for %s.\n"), |
| request->url); |
| #endif |
| CONNECTION_CLOSE_ERROR (connection, |
| MHD_SC_CONNECTION_WRITE_FAIL_CLOSED, |
| NULL); |
| return; |
| } |
| request->continue_message_write_offset += ret; |
| MHD_connection_update_last_activity_ (connection); |
| return; |
| case MHD_REQUEST_CONTINUE_SENT: |
| case MHD_REQUEST_BODY_RECEIVED: |
| case MHD_REQUEST_FOOTER_PART_RECEIVED: |
| case MHD_REQUEST_FOOTERS_RECEIVED: |
| mhd_assert (0); |
| return; |
| case MHD_REQUEST_HEADERS_SENDING: |
| ret = connection->send_cls (connection, |
| &request->write_buffer |
| [request->write_buffer_send_offset], |
| request->write_buffer_append_offset |
| - request->write_buffer_send_offset); |
| if (ret < 0) |
| { |
| if (MHD_ERR_AGAIN_ == ret) |
| return; |
| CONNECTION_CLOSE_ERROR (connection, |
| MHD_SC_CONNECTION_WRITE_FAIL_CLOSED, |
| _ ( |
| "Connection was closed while sending response headers.\n")); |
| return; |
| } |
| request->write_buffer_send_offset += ret; |
| MHD_connection_update_last_activity_ (connection); |
| if (MHD_REQUEST_HEADERS_SENDING != request->state) |
| return; |
| check_write_done (request, |
| MHD_REQUEST_HEADERS_SENT); |
| return; |
| case MHD_REQUEST_HEADERS_SENT: |
| return; |
| case MHD_REQUEST_NORMAL_BODY_READY: |
| response = request->response; |
| if (request->response_write_position < |
| request->response->total_size) |
| { |
| uint64_t data_write_offset; |
| |
| if (NULL != response->crc) |
| MHD_mutex_lock_chk_ (&response->mutex); |
| if (! try_ready_normal_body (request)) |
| { |
| /* mutex was already unlocked by try_ready_normal_body */ |
| return; |
| } |
| #if defined(_MHD_HAVE_SENDFILE) |
| if (MHD_resp_sender_sendfile == request->resp_sender) |
| { |
| ret = sendfile_adapter (connection); |
| } |
| else |
| #else /* ! _MHD_HAVE_SENDFILE */ |
| if (1) |
| #endif /* ! _MHD_HAVE_SENDFILE */ |
| { |
| data_write_offset = request->response_write_position |
| - response->data_start; |
| if (data_write_offset > (uint64_t) SIZE_MAX) |
| MHD_PANIC (_ ("Data offset exceeds limit.\n")); |
| ret = connection->send_cls (connection, |
| &response->data |
| [(size_t) data_write_offset], |
| response->data_size |
| - (size_t) data_write_offset); |
| #if DEBUG_SEND_DATA |
| if (ret > 0) |
| fprintf (stderr, |
| _ ("Sent %d-byte DATA response: `%.*s'\n"), |
| (int) ret, |
| (int) ret, |
| &response->data[request->response_write_position |
| - response->data_start]); |
| #endif |
| } |
| if (NULL != response->crc) |
| MHD_mutex_unlock_chk_ (&response->mutex); |
| if (ret < 0) |
| { |
| if (MHD_ERR_AGAIN_ == ret) |
| return; |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| MHD_SC_CONNECTION_WRITE_FAIL_CLOSED, |
| _ ("Failed to send data in request for `%s'.\n"), |
| request->url); |
| #endif |
| CONNECTION_CLOSE_ERROR (connection, |
| MHD_SC_CONNECTION_WRITE_FAIL_CLOSED, |
| NULL); |
| return; |
| } |
| request->response_write_position += ret; |
| MHD_connection_update_last_activity_ (connection); |
| } |
| if (request->response_write_position == |
| request->response->total_size) |
| request->state = MHD_REQUEST_FOOTERS_SENT; /* have no footers */ |
| return; |
| case MHD_REQUEST_NORMAL_BODY_UNREADY: |
| mhd_assert (0); |
| return; |
| case MHD_REQUEST_CHUNKED_BODY_READY: |
| ret = connection->send_cls (connection, |
| &request->write_buffer |
| [request->write_buffer_send_offset], |
| request->write_buffer_append_offset |
| - request->write_buffer_send_offset); |
| if (ret < 0) |
| { |
| if (MHD_ERR_AGAIN_ == ret) |
| return; |
| CONNECTION_CLOSE_ERROR (connection, |
| MHD_SC_CONNECTION_WRITE_FAIL_CLOSED, |
| _ ( |
| "Connection was closed while sending response body.\n")); |
| return; |
| } |
| request->write_buffer_send_offset += ret; |
| MHD_connection_update_last_activity_ (connection); |
| if (MHD_REQUEST_CHUNKED_BODY_READY != request->state) |
| return; |
| check_write_done (request, |
| (request->response->total_size == |
| request->response_write_position) ? |
| MHD_REQUEST_BODY_SENT : |
| MHD_REQUEST_CHUNKED_BODY_UNREADY); |
| return; |
| case MHD_REQUEST_CHUNKED_BODY_UNREADY: |
| case MHD_REQUEST_BODY_SENT: |
| mhd_assert (0); |
| return; |
| case MHD_REQUEST_FOOTERS_SENDING: |
| ret = connection->send_cls (connection, |
| &request->write_buffer |
| [request->write_buffer_send_offset], |
| request->write_buffer_append_offset |
| - request->write_buffer_send_offset); |
| if (ret < 0) |
| { |
| if (MHD_ERR_AGAIN_ == ret) |
| return; |
| CONNECTION_CLOSE_ERROR (connection, |
| MHD_SC_CONNECTION_WRITE_FAIL_CLOSED, |
| _ ( |
| "Connection was closed while sending response body.\n")); |
| return; |
| } |
| request->write_buffer_send_offset += ret; |
| MHD_connection_update_last_activity_ (connection); |
| if (MHD_REQUEST_FOOTERS_SENDING != request->state) |
| return; |
| check_write_done (request, |
| MHD_REQUEST_FOOTERS_SENT); |
| return; |
| case MHD_REQUEST_FOOTERS_SENT: |
| mhd_assert (0); |
| return; |
| case MHD_REQUEST_CLOSED: |
| return; |
| #ifdef UPGRADE_SUPPORT |
| case MHD_REQUEST_UPGRADE: |
| mhd_assert (0); |
| return; |
| #endif /* UPGRADE_SUPPORT */ |
| default: |
| mhd_assert (0); |
| CONNECTION_CLOSE_ERROR (connection, |
| MHD_SC_STATEMACHINE_FAILURE_CONNECTION_CLOSED, |
| _ ("Internal error.\n")); |
| break; |
| } |
| } |
| |
| |
| /** |
| * 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 request the request to get values from |
| * @param header the header name |
| * @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_Request *request, |
| const char *header, |
| const char *token, |
| size_t token_len) |
| { |
| struct MHD_HTTP_Header *pos; |
| |
| if ( (NULL == request) || /* FIXME: require non-null? */ |
| (NULL == header) || /* FIXME: require non-null? */ |
| (0 == header[0]) || |
| (NULL == token) || |
| (0 == token[0]) ) |
| return false; |
| for (pos = request->headers_received; NULL != pos; pos = pos->next) |
| { |
| if ( (0 != (pos->kind & MHD_HEADER_KIND)) && |
| ( (header == pos->header) || |
| (MHD_str_equal_caseless_ (header, |
| pos->header)) ) && |
| (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 r the request to get values from |
| * @param h the 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(r,h,tkn) \ |
| MHD_lookup_header_token_ci ((r),(h),(tkn),MHD_STATICSTR_LEN_ (tkn)) |
| |
| |
| /** |
| * 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 (which is conservative for HTTP 1.0, but might be a bit |
| * optimistic for HTTP 1.1). |
| * |
| * @param request the request to check for keepalive |
| * @return #MHD_YES if (based on the request), a keepalive is |
| * legal |
| */ |
| static bool |
| keepalive_possible (struct MHD_Request *request) |
| { |
| if (MHD_CONN_MUST_CLOSE == request->keepalive) |
| return false; |
| if (NULL == request->version_s) |
| return false; |
| if ( (NULL != request->response) && |
| (request->response->v10_only) ) |
| return false; |
| |
| if (MHD_str_equal_caseless_ (request->version_s, |
| MHD_HTTP_VERSION_1_1)) |
| { |
| if (MHD_lookup_header_s_token_ci (request, |
| MHD_HTTP_HEADER_CONNECTION, |
| "upgrade")) |
| return false; |
| if (MHD_lookup_header_s_token_ci (request, |
| MHD_HTTP_HEADER_CONNECTION, |
| "close")) |
| return false; |
| return true; |
| } |
| if (MHD_str_equal_caseless_ (request->version_s, |
| MHD_HTTP_VERSION_1_0)) |
| { |
| if (MHD_lookup_header_s_token_ci (request, |
| MHD_HTTP_HEADER_CONNECTION, |
| "Keep-Alive")) |
| return true; |
| return false; |
| } |
| return false; |
| } |
| |
| |
| /** |
| * Produce HTTP time stamp. |
| * |
| * @param date where to write the header, with |
| * at least 128 bytes available space. |
| * @param date_len number of bytes in @a date |
| */ |
| static void |
| get_date_string (char *date, |
| size_t date_len) |
| { |
| 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" |
| }; |
| struct tm now; |
| time_t t; |
| #if ! defined(HAVE_C11_GMTIME_S) && ! defined(HAVE_W32_GMTIME_S) && \ |
| ! defined(HAVE_GMTIME_R) |
| struct tm *pNow; |
| #endif |
| |
| date[0] = 0; |
| time (&t); |
| #if defined(HAVE_C11_GMTIME_S) |
| if (NULL == gmtime_s (&t, |
| &now)) |
| return; |
| #elif defined(HAVE_W32_GMTIME_S) |
| if (0 != gmtime_s (&now, |
| &t)) |
| return; |
| #elif defined(HAVE_GMTIME_R) |
| if (NULL == gmtime_r (&t, |
| &now)) |
| return; |
| #else |
| pNow = gmtime (&t); |
| if (NULL == pNow) |
| return; |
| now = *pNow; |
| #endif |
| MHD_snprintf_ (date, |
| date_len, |
| "Date: %3s, %02u %3s %04u %02u:%02u:%02u GMT\r\n", |
| days[now.tm_wday % 7], |
| (unsigned int) now.tm_mday, |
| mons[now.tm_mon % 12], |
| (unsigned int) (1900 + now.tm_year), |
| (unsigned int) now.tm_hour, |
| (unsigned int) now.tm_min, |
| (unsigned int) now.tm_sec); |
| } |
| |
| |
| /** |
| * Check whether response header contains particular @a token. |
| * |
| * Token could be surrounded by spaces and tabs and delimited by comma. |
| * Case-insensitive match used for header names and tokens. |
| * @param response the response to query |
| * @param key header name |
| * @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 |
| check_response_header_token_ci (const struct MHD_Response *response, |
| const char *key, |
| const char *token, |
| size_t token_len) |
| { |
| struct MHD_HTTP_Header *pos; |
| |
| if ( (NULL == key) || |
| ('\0' == key[0]) || |
| (NULL == token) || |
| ('\0' == token[0]) ) |
| return false; |
| |
| for (pos = response->first_header; |
| NULL != pos; |
| pos = pos->next) |
| { |
| if ( (pos->kind == MHD_HEADER_KIND) && |
| MHD_str_equal_caseless_ (pos->header, |
| key) && |
| MHD_str_has_token_caseless_ (pos->value, |
| token, |
| token_len) ) |
| return true; |
| } |
| return false; |
| } |
| |
| |
| /** |
| * Check whether response 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 r the response to query |
| * @param k header name |
| * @param tkn the static string of token to find |
| * @return true if token is found in specified header, |
| * false otherwise |
| */ |
| #define check_response_header_s_token_ci(r,k,tkn) \ |
| check_response_header_token_ci ((r),(k),(tkn),MHD_STATICSTR_LEN_ (tkn)) |
| |
| |
| /** |
| * Allocate the connection's write buffer and fill it with all of the |
| * headers (or footers, if we have already sent the body) from the |
| * HTTPd's response. If headers are missing in the response supplied |
| * by the application, additional headers may be added here. |
| * |
| * @param request the request for which to build the response header |
| * @return true on success, false on failure (out of memory) |
| */ |
| static bool |
| build_header_response (struct MHD_Request *request) |
| { |
| struct MHD_Connection *connection = request->connection; |
| struct MHD_Daemon *daemon = request->daemon; |
| struct MHD_Response *response = request->response; |
| size_t size; |
| size_t off; |
| struct MHD_HTTP_Header *pos; |
| char code[256]; |
| char date[128]; |
| size_t datelen; |
| char content_length_buf[128]; |
| size_t content_length_len; |
| char *data; |
| enum MHD_ValueKind kind; |
| bool client_requested_close; |
| bool response_has_close; |
| bool response_has_keepalive; |
| const char *have_encoding; |
| const char *have_content_length; |
| bool must_add_close; |
| bool must_add_chunked_encoding; |
| bool must_add_keep_alive; |
| bool must_add_content_length; |
| |
| mhd_assert (NULL != request->version_s); |
| if (0 == request->version_s[0]) |
| { |
| data = MHD_pool_allocate (connection->pool, |
| 0, |
| MHD_YES); |
| request->write_buffer = data; |
| request->write_buffer_append_offset = 0; |
| request->write_buffer_send_offset = 0; |
| request->write_buffer_size = 0; |
| return true; |
| } |
| if (MHD_REQUEST_FOOTERS_RECEIVED == request->state) |
| { |
| const char *reason_phrase; |
| const char *version; |
| |
| reason_phrase |
| = MHD_get_reason_phrase_for (response->status_code); |
| version |
| = (response->icy) |
| ? "ICY" |
| : ( (MHD_str_equal_caseless_ (MHD_HTTP_VERSION_1_0, |
| request->version_s)) |
| ? MHD_HTTP_VERSION_1_0 |
| : MHD_HTTP_VERSION_1_1); |
| MHD_snprintf_ (code, |
| sizeof (code), |
| "%s %u %s\r\n", |
| version, |
| response->status_code, |
| reason_phrase); |
| off = strlen (code); |
| /* estimate size */ |
| size = off + 2; /* +2 for extra "\r\n" at the end */ |
| kind = MHD_HEADER_KIND; |
| if ( (! daemon->suppress_date) && |
| (NULL == MHD_response_get_header (response, |
| MHD_HTTP_HEADER_DATE)) ) |
| get_date_string (date, |
| sizeof (date)); |
| else |
| date[0] = '\0'; |
| datelen = strlen (date); |
| size += datelen; |
| } |
| else |
| { |
| /* 2 bytes for final CRLF of a Chunked-Body */ |
| size = 2; |
| kind = MHD_FOOTER_KIND; |
| off = 0; |
| datelen = 0; |
| } |
| |
| /* calculate extra headers we need to add, such as 'Connection: close', |
| first see what was explicitly requested by the application */ |
| must_add_close = false; |
| must_add_chunked_encoding = false; |
| must_add_keep_alive = false; |
| must_add_content_length = false; |
| response_has_close = false; |
| switch (request->state) |
| { |
| case MHD_REQUEST_FOOTERS_RECEIVED: |
| response_has_close |
| = check_response_header_s_token_ci (response, |
| MHD_HTTP_HEADER_CONNECTION, |
| "close"); |
| response_has_keepalive |
| = check_response_header_s_token_ci (response, |
| MHD_HTTP_HEADER_CONNECTION, |
| "Keep-Alive"); |
| client_requested_close |
| = MHD_lookup_header_s_token_ci (request, |
| MHD_HTTP_HEADER_CONNECTION, |
| "close"); |
| |
| if (response->v10_only) |
| request->keepalive = MHD_CONN_MUST_CLOSE; |
| #ifdef UPGRADE_SUPPORT |
| else if (NULL != response->upgrade_handler) |
| /* If this connection will not be "upgraded", it must be closed. */ |
| request->keepalive = MHD_CONN_MUST_CLOSE; |
| #endif /* UPGRADE_SUPPORT */ |
| |
| /* now analyze chunked encoding situation */ |
| request->have_chunked_upload = false; |
| |
| if ( (MHD_SIZE_UNKNOWN == response->total_size) && |
| #ifdef UPGRADE_SUPPORT |
| (NULL == response->upgrade_handler) && |
| #endif /* UPGRADE_SUPPORT */ |
| (! response_has_close) && |
| (! client_requested_close) ) |
| { |
| /* size is unknown, and close was not explicitly requested; |
| need to either to HTTP 1.1 chunked encoding or |
| close the connection */ |
| /* 'close' header doesn't exist yet, see if we need to add one; |
| if the client asked for a close, no need to start chunk'ing */ |
| if ( (keepalive_possible (request)) && |
| (MHD_str_equal_caseless_ (MHD_HTTP_VERSION_1_1, |
| request->version_s)) ) |
| { |
| have_encoding |
| = MHD_response_get_header (response, |
| MHD_HTTP_HEADER_TRANSFER_ENCODING); |
| if (NULL == have_encoding) |
| { |
| must_add_chunked_encoding = true; |
| request->have_chunked_upload = true; |
| } |
| else if (MHD_str_equal_caseless_ (have_encoding, |
| "identity")) |
| { |
| /* application forced identity encoding, can't do 'chunked' */ |
| must_add_close = true; |
| } |
| else |
| { |
| request->have_chunked_upload = true; |
| } |
| } |
| else |
| { |
| /* Keep alive or chunking not possible |
| => set close header if not present */ |
| if (! response_has_close) |
| must_add_close = true; |
| } |
| } |
| |
| /* check for other reasons to add 'close' header */ |
| if ( ( (client_requested_close) || |
| (connection->read_closed) || |
| (MHD_CONN_MUST_CLOSE == request->keepalive)) && |
| (! response_has_close) && |
| #ifdef UPGRADE_SUPPORT |
| (NULL == response->upgrade_handler) && |
| #endif /* UPGRADE_SUPPORT */ |
| (! response->v10_only) ) |
| must_add_close = true; |
| |
| /* check if we should add a 'content length' header */ |
| have_content_length |
| = MHD_response_get_header (response, |
| MHD_HTTP_HEADER_CONTENT_LENGTH); |
| |
| /* MHD_HTTP_NO_CONTENT, MHD_HTTP_NOT_MODIFIED and 1xx-status |
| codes SHOULD NOT have a Content-Length according to spec; |
| also chunked encoding / unknown length or CONNECT... */ |
| if ( (MHD_SIZE_UNKNOWN != response->total_size) && |
| (MHD_HTTP_NO_CONTENT != response->status_code) && |
| (MHD_HTTP_NOT_MODIFIED != response->status_code) && |
| (MHD_HTTP_OK <= response->status_code) && |
| (NULL == have_content_length) && |
| (request->method != MHD_METHOD_CONNECT) ) |
| { |
| /* |
| Here we add a content-length if one is missing; however, |
| for 'connect' methods, the responses MUST NOT include a |
| content-length header *if* the response code is 2xx (in |
| which case we expect there to be no body). Still, |
| as we don't know the response code here in some cases, we |
| simply only force adding a content-length header if this |
| is not a 'connect' or if the response is not empty |
| (which is kind of more sane, because if some crazy |
| application did return content with a 2xx status code, |
| then having a content-length might again be a good idea). |
| |
| Note that the change from 'SHOULD NOT' to 'MUST NOT' is |
| a recent development of the HTTP 1.1 specification. |
| */content_length_len |
| = MHD_snprintf_ (content_length_buf, |
| sizeof (content_length_buf), |
| MHD_HTTP_HEADER_CONTENT_LENGTH ": " |
| MHD_UNSIGNED_LONG_LONG_PRINTF "\r\n", |
| (MHD_UNSIGNED_LONG_LONG) response->total_size); |
| must_add_content_length = true; |
| } |
| |
| /* check for adding keep alive */ |
| if ( (! response_has_keepalive) && |
| (! response_has_close) && |
| (! must_add_close) && |
| (MHD_CONN_MUST_CLOSE != request->keepalive) && |
| #ifdef UPGRADE_SUPPORT |
| (NULL == response->upgrade_handler) && |
| #endif /* UPGRADE_SUPPORT */ |
| (keepalive_possible (request)) ) |
| must_add_keep_alive = true; |
| break; |
| case MHD_REQUEST_BODY_SENT: |
| response_has_keepalive = false; |
| break; |
| default: |
| mhd_assert (0); |
| return MHD_NO; |
| } |
| |
| if (MHD_CONN_MUST_CLOSE != request->keepalive) |
| { |
| if ( (must_add_close) || |
| (response_has_close) ) |
| request->keepalive = MHD_CONN_MUST_CLOSE; |
| else if ( (must_add_keep_alive) || |
| (response_has_keepalive) ) |
| request->keepalive = MHD_CONN_USE_KEEPALIVE; |
| } |
| |
| if (must_add_close) |
| size += MHD_STATICSTR_LEN_ ("Connection: close\r\n"); |
| if (must_add_keep_alive) |
| size += MHD_STATICSTR_LEN_ ("Connection: Keep-Alive\r\n"); |
| if (must_add_chunked_encoding) |
| size += MHD_STATICSTR_LEN_ ("Transfer-Encoding: chunked\r\n"); |
| if (must_add_content_length) |
| size += content_length_len; |
| mhd_assert (! (must_add_close && must_add_keep_alive) ); |
| mhd_assert (! (must_add_chunked_encoding && must_add_content_length) ); |
| |
| for (pos = response->first_header; NULL != pos; pos = pos->next) |
| { |
| /* TODO: add proper support for excluding "Keep-Alive" token. */ |
| if ( (pos->kind == kind) && |
| (! ( (must_add_close) && |
| (response_has_keepalive) && |
| (MHD_str_equal_caseless_ (pos->header, |
| MHD_HTTP_HEADER_CONNECTION)) && |
| (MHD_str_equal_caseless_ (pos->value, |
| "Keep-Alive")) ) ) ) |
| size += strlen (pos->header) + strlen (pos->value) + 4; /* colon, space, linefeeds */ |
| } |
| /* produce data */ |
| data = MHD_pool_allocate (connection->pool, |
| size + 1, |
| MHD_NO); |
| if (NULL == data) |
| { |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| MHD_SC_CONNECTION_POOL_MALLOC_FAILURE, |
| "Not enough memory for write!\n"); |
| #endif |
| return false; |
| } |
| if (MHD_REQUEST_FOOTERS_RECEIVED == request->state) |
| { |
| memcpy (data, |
| code, |
| off); |
| } |
| if (must_add_close) |
| { |
| /* we must add the 'Connection: close' header */ |
| memcpy (&data[off], |
| "Connection: close\r\n", |
| MHD_STATICSTR_LEN_ ("Connection: close\r\n")); |
| off += MHD_STATICSTR_LEN_ ("Connection: close\r\n"); |
| } |
| if (must_add_keep_alive) |
| { |
| /* we must add the 'Connection: Keep-Alive' header */ |
| memcpy (&data[off], |
| "Connection: Keep-Alive\r\n", |
| MHD_STATICSTR_LEN_ ("Connection: Keep-Alive\r\n")); |
| off += MHD_STATICSTR_LEN_ ("Connection: Keep-Alive\r\n"); |
| } |
| if (must_add_chunked_encoding) |
| { |
| /* we must add the 'Transfer-Encoding: chunked' header */ |
| memcpy (&data[off], |
| "Transfer-Encoding: chunked\r\n", |
| MHD_STATICSTR_LEN_ ("Transfer-Encoding: chunked\r\n")); |
| off += MHD_STATICSTR_LEN_ ("Transfer-Encoding: chunked\r\n"); |
| } |
| if (must_add_content_length) |
| { |
| /* we must add the 'Content-Length' header */ |
| memcpy (&data[off], |
| content_length_buf, |
| content_length_len); |
| off += content_length_len; |
| } |
| for (pos = response->first_header; NULL != pos; pos = pos->next) |
| { |
| /* TODO: add proper support for excluding "Keep-Alive" token. */ |
| if ( (pos->kind == kind) && |
| (! ( (must_add_close) && |
| (response_has_keepalive) && |
| (MHD_str_equal_caseless_ (pos->header, |
| MHD_HTTP_HEADER_CONNECTION)) && |
| (MHD_str_equal_caseless_ (pos->value, |
| "Keep-Alive")) ) ) ) |
| off += MHD_snprintf_ (&data[off], |
| size - off, |
| "%s: %s\r\n", |
| pos->header, |
| pos->value); |
| } |
| if (MHD_REQUEST_FOOTERS_RECEIVED == request->state) |
| { |
| memcpy (&data[off], |
| date, |
| datelen); |
| off += datelen; |
| } |
| memcpy (&data[off], |
| "\r\n", |
| 2); |
| off += 2; |
| |
| if (off != size) |
| mhd_panic (mhd_panic_cls, |
| __FILE__, |
| __LINE__, |
| NULL); |
| request->write_buffer = data; |
| request->write_buffer_append_offset = size; |
| request->write_buffer_send_offset = 0; |
| request->write_buffer_size = size + 1; |
| return true; |
| } |
| |
| |
| /** |
| * We encountered an error processing the request. Handle it properly |
| * by stopping to read data and sending the indicated response code |
| * and message. |
| * |
| * @param request the request |
| * @param ec error code for MHD |
| * @param status_code the response code to send (400, 413 or 414) |
| * @param message the error message to send |
| */ |
| static void |
| transmit_error_response (struct MHD_Request *request, |
| enum MHD_StatusCode ec, |
| enum MHD_HTTP_StatusCode status_code, |
| const char *message) |
| { |
| struct MHD_Response *response; |
| |
| if (NULL == request->version_s) |
| { |
| /* we were unable to process the full header line, so we don't |
| really know what version the client speaks; assume 1.0 */ |
| request->version_s = MHD_HTTP_VERSION_1_0; |
| } |
| request->state = MHD_REQUEST_FOOTERS_RECEIVED; |
| request->connection->read_closed = true; |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (request->daemon, |
| ec, |
| _ ( |
| "Error processing request (HTTP response code is %u (`%s')). Closing connection.\n"), |
| status_code, |
| message); |
| #endif |
| if (NULL != request->response) |
| { |
| MHD_response_queue_for_destroy (request->response); |
| request->response = NULL; |
| } |
| response = MHD_response_from_buffer (status_code, |
| strlen (message), |
| (void *) message, |
| MHD_RESPMEM_PERSISTENT); |
| request->response = response; |
| /* Do not reuse this connection. */ |
| request->keepalive = MHD_CONN_MUST_CLOSE; |
| if (! build_header_response (request)) |
| { |
| /* oops - close! */ |
| CONNECTION_CLOSE_ERROR (request->connection, |
| ec, |
| _ ( |
| "Closing connection (failed to create response header).\n")); |
| } |
| else |
| { |
| request->state = MHD_REQUEST_HEADERS_SENDING; |
| } |
| } |
| |
| |
| /** |
| * Convert @a method to the respective enum value. |
| * |
| * @param method the method string to look up enum value for |
| * @return resulting enum, or generic value for "unknown" |
| */ |
| static enum MHD_Method |
| method_string_to_enum (const char *method) |
| { |
| static const struct |
| { |
| const char *key; |
| enum MHD_Method value; |
| } methods[] = { |
| { "OPTIONS", MHD_METHOD_OPTIONS }, |
| { "GET", MHD_METHOD_GET }, |
| { "HEAD", MHD_METHOD_HEAD }, |
| { "POST", MHD_METHOD_POST }, |
| { "PUT", MHD_METHOD_PUT }, |
| { "DELETE", MHD_METHOD_DELETE }, |
| { "TRACE", MHD_METHOD_TRACE }, |
| { "CONNECT", MHD_METHOD_CONNECT }, |
| { "ACL", MHD_METHOD_ACL }, |
| { "BASELINE_CONTROL", MHD_METHOD_BASELINE_CONTROL }, |
| { "BIND", MHD_METHOD_BIND }, |
| { "CHECKIN", MHD_METHOD_CHECKIN }, |
| { "CHECKOUT", MHD_METHOD_CHECKOUT }, |
| { "COPY", MHD_METHOD_COPY }, |
| { "LABEL", MHD_METHOD_LABEL }, |
| { "LINK", MHD_METHOD_LINK }, |
| { "LOCK", MHD_METHOD_LOCK }, |
| { "MERGE", MHD_METHOD_MERGE }, |
| { "MKACTIVITY", MHD_METHOD_MKACTIVITY }, |
| { "MKCOL", MHD_METHOD_MKCOL }, |
| { "MKREDIRECTREF", MHD_METHOD_MKREDIRECTREF }, |
| { "MKWORKSPACE", MHD_METHOD_MKWORKSPACE }, |
| { "MOVE", MHD_METHOD_MOVE }, |
| { "ORDERPATCH", MHD_METHOD_ORDERPATCH }, |
| { "PRI", MHD_METHOD_PRI }, |
| { "PROPFIND", MHD_METHOD_PROPFIND }, |
| { "PROPPATCH", MHD_METHOD_PROPPATCH }, |
| { "REBIND", MHD_METHOD_REBIND }, |
| { "REPORT", MHD_METHOD_REPORT }, |
| { "SEARCH", MHD_METHOD_SEARCH }, |
| { "UNBIND", MHD_METHOD_UNBIND }, |
| { "UNCHECKOUT", MHD_METHOD_UNCHECKOUT }, |
| { "UNLINK", MHD_METHOD_UNLINK }, |
| { "UNLOCK", MHD_METHOD_UNLOCK }, |
| { "UPDATE", MHD_METHOD_UPDATE }, |
| { "UPDATEDIRECTREF", MHD_METHOD_UPDATEDIRECTREF }, |
| { "VERSION-CONTROL", MHD_METHOD_VERSION_CONTROL }, |
| { NULL, MHD_METHOD_UNKNOWN } /* must be last! */ |
| }; |
| unsigned int i; |
| |
| for (i = 0; NULL != methods[i].key; i++) |
| if (0 == |
| MHD_str_equal_caseless_ (method, |
| methods[i].key)) |
| return methods[i].value; |
| return MHD_METHOD_UNKNOWN; |
| } |
| |
| |
| /** |
| * Add an entry to the HTTP headers of a request. If this fails, |
| * transmit an error response (request too big). |
| * |
| * @param request the request 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 false on failure (out of memory), true for success |
| */ |
| static bool |
| request_add_header (struct MHD_Request *request, |
| const char *key, |
| const char *value, |
| enum MHD_ValueKind kind) |
| { |
| if (MHD_NO == |
| MHD_request_set_value (request, |
| kind, |
| key, |
| value)) |
| { |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (request->daemon, |
| MHD_SC_CONNECTION_POOL_MALLOC_FAILURE, |
| _ ("Not enough memory in pool to allocate header record!\n")); |
| #endif |
| transmit_error_response (request, |
| MHD_SC_CLIENT_HEADER_TOO_BIG, |
| MHD_HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE, |
| REQUEST_TOO_BIG); |
| return false; |
| } |
| return true; |
| } |
| |
| |
| /** |
| * 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 true if the line is ok, false if it is malformed |
| */ |
| static bool |
| parse_initial_message_line (struct MHD_Request *request, |
| char *line, |
| size_t line_len) |
| { |
| struct MHD_Daemon *daemon = request->daemon; |
| const char *curi; |
| char *uri; |
| char *http_version; |
| char *args; |
| unsigned int unused_num_headers; |
| size_t url_end; |
| |
| if (NULL == (uri = memchr (line, |
| ' ', |
| line_len))) |
| return false; /* serious error */ |
| uri[0] = '\0'; |
| request->method_s = line; |
| request->method = method_string_to_enum (line); |
| uri++; |
| /* Skip any spaces. Not required by standard but allow |
| to be more tolerant. */ |
| while ( (' ' == uri[0]) && |
| ( (size_t) (uri - line) < line_len) ) |
| uri++; |
| if ((size_t) (uri - line) == line_len) |
| { |
| curi = ""; |
| uri = NULL; |
| request->version_s = ""; |
| args = NULL; |
| url_end = line_len - (line - uri); // EH, this is garbage. FIXME! |
| } |
| else |
| { |
| curi = uri; |
| /* Search from back to accept malformed URI with space */ |
| http_version = line + line_len - 1; |
| /* Skip any trailing spaces */ |
| 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[0] = '\0'; |
| request->version_s = http_version + 1; |
| args = memchr (uri, |
| '?', |
| http_version - uri); |
| } |
| else |
| { |
| request->version_s = ""; |
| args = memchr (uri, |
| '?', |
| line_len - (uri - line)); |
| } |
| url_end = http_version - uri; |
| } |
| if ( (MHD_PSL_STRICT == daemon->protocol_strict_level) && |
| (NULL != memchr (curi, |
| ' ', |
| url_end)) ) |
| { |
| /* space exists in URI and we are supposed to be strict, reject */ |
| return MHD_NO; |
| } |
| if (NULL != daemon->early_uri_logger_cb) |
| { |
| request->client_context |
| = daemon->early_uri_logger_cb (daemon->early_uri_logger_cb_cls, |
| curi, |
| request); |
| } |
| if (NULL != args) |
| { |
| args[0] = '\0'; |
| args++; |
| /* note that this call clobbers 'args' */ |
| MHD_parse_arguments_ (request, |
| MHD_GET_ARGUMENT_KIND, |
| args, |
| &request_add_header, |
| &unused_num_headers); |
| } |
| if (NULL != uri) |
| daemon->unescape_cb (daemon->unescape_cb_cls, |
| request, |
| uri); |
| request->url = curi; |
| return true; |
| } |
| |
| |
| /** |
| * We have received (possibly the beginning of) a line in the |
| * header (or footer). Validate (check for ":") and prepare |
| * to process. |
| * |
| * @param request the request we're processing |
| * @param line line from the header to process |
| * @return true on success, false on error (malformed @a line) |
| */ |
| static bool |
| process_header_line (struct MHD_Request *request, |
| char *line) |
| { |
| struct MHD_Connection *connection = request->connection; |
| char *colon; |
| |
| /* line should be normal header line, find colon */ |
| colon = strchr (line, |
| ':'); |
| if (NULL == colon) |
| { |
| /* error in header line, die hard */ |
| CONNECTION_CLOSE_ERROR (connection, |
| MHD_SC_CONNECTION_PARSE_FAIL_CLOSED, |
| _ ( |
| "Received malformed line (no colon). Closing connection.\n")); |
| return false; |
| } |
| if (MHD_PSL_PERMISSIVE != request->daemon->protocol_strict_level) |
| { |
| /* 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, |
| (unsigned char) ' '); |
| if ( (NULL != white) && |
| (white < colon) ) |
| { |
| CONNECTION_CLOSE_ERROR (connection, |
| MHD_SC_CONNECTION_PARSE_FAIL_CLOSED, |
| _ ( |
| "Whitespace before colon forbidden by RFC 7230. Closing connection.\n")); |
| return false; |
| } |
| white = strchr (line, |
| (unsigned char) '\t'); |
| if ( (NULL != white) && |
| (white < colon) ) |
| { |
| CONNECTION_CLOSE_ERROR (connection, |
| MHD_SC_CONNECTION_PARSE_FAIL_CLOSED, |
| _ ( |
| "Tab before colon forbidden by RFC 7230. Closing connection.\n")); |
| return false; |
| } |
| } |
| /* 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...) */request->last = line; |
| request->colon = colon; |
| return true; |
| } |
| |
| |
| /** |
| * Process a header value that spans multiple lines. |
| * The previous line(s) are in connection->last. |
| * |
| * @param request the request we're processing |
| * @param line the current input line |
| * @param kind if the line is complete, add a header |
| * of the given kind |
| * @return true if the line was processed successfully |
| */ |
| static bool |
| process_broken_line (struct MHD_Request *request, |
| char *line, |
| enum MHD_ValueKind kind) |
| { |
| struct MHD_Connection *connection = request->connection; |
| char *last; |
| char *tmp; |
| size_t last_len; |
| size_t tmp_len; |
| |
| last = request->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 (request, |
| MHD_SC_CLIENT_HEADER_TOO_BIG, |
| MHD_HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE, |
| REQUEST_TOO_BIG); |
| return MHD_NO; |
| } |
| memcpy (&last[last_len], |
| tmp, |
| tmp_len + 1); |
| request->last = last; |
| return MHD_YES; /* possibly more than 2 lines... */ |
| } |
| mhd_assert ( (NULL != last) && |
| (NULL != request->colon) ); |
| if (! request_add_header (request, |
| last, |
| request->colon, |
| kind)) |
| { |
| transmit_error_response (request, |
| MHD_SC_CLIENT_HEADER_TOO_BIG, |
| MHD_HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE, |
| REQUEST_TOO_BIG); |
| return false; |
| } |
| /* we still have the current line to deal with... */ |
| if ('\0' != line[0]) |
| { |
| if (! process_header_line (request, |
| line)) |
| { |
| transmit_error_response (request, |
| MHD_SC_CONNECTION_PARSE_FAIL_CLOSED, |
| MHD_HTTP_BAD_REQUEST, |
| REQUEST_MALFORMED); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| |
| /** |
| * 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 request request 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_Request *request, |
| size_t *line_len) |
| { |
| char *rbuf; |
| size_t pos; |
| |
| if (0 == request->read_buffer_offset) |
| return NULL; |
| pos = 0; |
| rbuf = request->read_buffer; |
| while ( (pos < request->read_buffer_offset - 1) && |
| ('\r' != rbuf[pos]) && |
| ('\n' != rbuf[pos]) ) |
| pos++; |
| if ( (pos == request->read_buffer_offset - 1) && |
| ('\n' != rbuf[pos]) ) |
| { |
| /* not found, consider growing... */ |
| if ( (request->read_buffer_offset == request->read_buffer_size) && |
| (! try_grow_read_buffer (request)) ) |
| { |
| transmit_error_response (request, |
| MHD_SC_CLIENT_HEADER_TOO_BIG, |
| (NULL != request->url) |
| ? MHD_HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE |
| : MHD_HTTP_URI_TOO_LONG, |
| REQUEST_TOO_BIG); |
| } |
| if (line_len) |
| *line_len = 0; |
| return NULL; |
| } |
| |
| if (line_len) |
| *line_len = pos; |
| /* found, check if we have proper LFCR */ |
| if ( ('\r' == rbuf[pos]) && |
| ('\n' == rbuf[pos + 1]) ) |
| rbuf[pos++] = '\0'; /* skip both r and n */ |
| rbuf[pos++] = '\0'; |
| request->read_buffer += pos; |
| request->read_buffer_size -= pos; |
| request->read_buffer_offset -= pos; |
| return rbuf; |
| } |
| |
| |
| /** |
| * Check whether is possible to force push socket buffer content as |
| * partial packet. |
| * MHD use different buffering logic depending on whether flushing of |
| * socket buffer is possible or not. |
| * If flushing IS possible than MHD activates extra buffering before |
| * sending data to prevent sending partial packets and flush pending |
| * data in socket buffer to push last partial packet to client after |
| * sending logical completed part of data (for example: after sending |
| * full response header or full response message). |
| * If flushing IS NOT possible than MHD activates no buffering (no |
| * delay sending) when it going to send formed fully completed logical |
| * part of data and activate normal buffering after sending. |
| * For idled keep-alive connection MHD always activate normal |
| * buffering. |
| * |
| * @param connection connection to check |
| * @return true if force push is possible, false otherwise |
| */ |
| static bool |
| socket_flush_possible (struct MHD_Connection *connection) |
| { |
| (void) connection; /* Mute compiler warning. */ |
| #if defined(TCP_CORK) || defined(TCP_PUSH) |
| return true; |
| #else /* !TCP_CORK && !TCP_PUSH */ |
| return false; |
| #endif /* !TCP_CORK && !TCP_PUSH */ |
| } |
| |
| |
| /** |
| * Activate extra buffering mode on connection socket to prevent |
| * sending of partial packets. |
| * |
| * @param connection connection to be processed |
| * @return true on success, false otherwise |
| */ |
| static bool |
| socket_start_extra_buffering (struct MHD_Connection *connection) |
| { |
| bool res = false; |
| (void) connection; /* Mute compiler warning. */ |
| #if defined(TCP_CORK) || defined(TCP_NOPUSH) |
| const MHD_SCKT_OPT_BOOL_ on_val = 1; |
| #if defined(TCP_NODELAY) |
| const MHD_SCKT_OPT_BOOL_ off_val = 0; |
| #endif /* TCP_NODELAY */ |
| mhd_assert (NULL != connection); |
| #if defined(TCP_NOPUSH) && ! defined(TCP_CORK) |
| /* Buffer data before sending */ |
| res = (0 == setsockopt (connection->socket_fd, |
| IPPROTO_TCP, |
| TCP_NOPUSH, |
| (const void *) &on_val, |
| sizeof (on_val))) |
| ? true : false; |
| #if defined(TCP_NODELAY) |
| /* Enable Nagle's algorithm */ |
| /* TCP_NODELAY may interfere with TCP_NOPUSH */ |
| res &= (0 == setsockopt (connection->socket_fd, |
| IPPROTO_TCP, |
| TCP_NODELAY, |
| (const void *) &off_val, |
| sizeof (off_val))) |
| ? true : false; |
| #endif /* TCP_NODELAY */ |
| #else /* TCP_CORK */ |
| #if defined(TCP_NODELAY) |
| /* Enable Nagle's algorithm */ |
| /* TCP_NODELAY may prevent enabling TCP_CORK. Resulting buffering mode depends |
| solely on TCP_CORK result, so ignoring return code here. */ |
| (void) setsockopt (connection->socket_fd, |
| IPPROTO_TCP, |
| TCP_NODELAY, |
| (const void *) &off_val, |
| sizeof (off_val)); |
| #endif /* TCP_NODELAY */ |
| /* Send only full packets */ |
| res = (0 == setsockopt (connection->socket_fd, |
| IPPROTO_TCP, |
| TCP_CORK, |
| (const void *) &on_val, |
| sizeof (on_val))) |
| ? true : false; |
| #endif /* TCP_CORK */ |
| #endif /* TCP_CORK || TCP_NOPUSH */ |
| return res; |
| } |
| |
| |
| /** |
| * Activate no buffering mode (no delay sending) on connection socket. |
| * |
| * @param connection connection to be processed |
| * @return true on success, false otherwise |
| */ |
| static bool |
| socket_start_no_buffering (struct MHD_Connection *connection) |
| { |
| #if defined(TCP_NODELAY) |
| bool res = true; |
| const MHD_SCKT_OPT_BOOL_ on_val = 1; |
| #if defined(TCP_CORK) || defined(TCP_NOPUSH) |
| const MHD_SCKT_OPT_BOOL_ off_val = 0; |
| #endif /* TCP_CORK || TCP_NOPUSH */ |
| |
| (void) connection; /* Mute compiler warning. */ |
| mhd_assert (NULL != connection); |
| #if defined(TCP_CORK) |
| /* Allow partial packets */ |
| res &= (0 == setsockopt (connection->socket_fd, |
| IPPROTO_TCP, |
| TCP_CORK, |
| (const void *) &off_val, |
| sizeof (off_val))) |
| ? true : false; |
| #endif /* TCP_CORK */ |
| #if defined(TCP_NODELAY) |
| /* Disable Nagle's algorithm for sending packets without delay */ |
| res &= (0 == setsockopt (connection->socket_fd, |
| IPPROTO_TCP, |
| TCP_NODELAY, |
| (const void *) &on_val, |
| sizeof (on_val))) |
| ? true : false; |
| #endif /* TCP_NODELAY */ |
| #if defined(TCP_NOPUSH) && ! defined(TCP_CORK) |
| /* Disable extra buffering */ |
| res &= (0 == setsockopt (connection->socket_fd, |
| IPPROTO_TCP, |
| TCP_NOPUSH, |
| (const void *) &off_val, |
| sizeof (off_val))) |
| ? true : false; |
| #endif /* TCP_NOPUSH && !TCP_CORK */ |
| return res; |
| #else /* !TCP_NODELAY */ |
| return false; |
| #endif /* !TCP_NODELAY */ |
| } |
| |
| |
| /** |
| * Activate no buffering mode (no delay sending) on connection socket |
| * and push to client data pending in socket buffer. |
| * |
| * @param connection connection to be processed |
| * @return true on success, false otherwise |
| */ |
| static bool |
| socket_start_no_buffering_flush (struct MHD_Connection *connection) |
| { |
| bool res = true; |
| #if defined(TCP_NOPUSH) && ! defined(TCP_CORK) |
| const int dummy = 0; |
| #endif /* !TCP_CORK */ |
| |
| if (NULL == connection) |
| return false; /* FIXME: use MHD_NONNULL? */ |
| res = socket_start_no_buffering (connection); |
| #if defined(TCP_NOPUSH) && ! defined(TCP_CORK) |
| /* Force flush data with zero send otherwise Darwin and some BSD systems |
| will add 5 seconds delay. Not required with TCP_CORK as switching off |
| TCP_CORK always flushes socket buffer. */ |
| res &= (0 <= MHD_send_ (connection->socket_fd, |
| &dummy, |
| 0)) |
| ? true : false; |
| #endif /* TCP_NOPUSH && !TCP_CORK*/ |
| return res; |
| } |
| |
| |
| /** |
| * Activate normal buffering mode on connection socket. |
| * |
| * @param connection connection to be processed |
| * @return true on success, false otherwise |
| */ |
| static bool |
| socket_start_normal_buffering (struct MHD_Connection *connection) |
| { |
| #if defined(TCP_NODELAY) |
| bool res = true; |
| const MHD_SCKT_OPT_BOOL_ off_val = 0; |
| #if defined(TCP_CORK) |
| MHD_SCKT_OPT_BOOL_ cork_val = 0; |
| socklen_t param_size = sizeof (cork_val); |
| #endif /* TCP_CORK */ |
| |
| mhd_assert (NULL != connection); |
| #if defined(TCP_CORK) |
| /* Allow partial packets */ |
| /* Disabling TCP_CORK will flush partial packet even if TCP_CORK wasn't enabled before |
| so try to check current value of TCP_CORK to prevent unrequested flushing */ |
| if ( (0 != getsockopt (connection->socket_fd, |
| IPPROTO_TCP, |
| TCP_CORK, |
| (void *) &cork_val, |
| ¶m_size)) || |
| (0 != cork_val)) |
| res &= (0 == setsockopt (connection->socket_fd, |
| IPPROTO_TCP, |
| TCP_CORK, |
| (const void *) &off_val, |
| sizeof (off_val))) |
| ? true : false; |
| #elif defined(TCP_NOPUSH) |
| /* Disable extra buffering */ |
| /* No need to check current value as disabling TCP_NOPUSH will not flush partial |
| packet if TCP_NOPUSH wasn't enabled before */ |
| res &= (0 == setsockopt (connection->socket_fd, |
| IPPROTO_TCP, |
| TCP_NOPUSH, |
| (const void *) &off_val, |
| sizeof (off_val))) |
| ? true : false; |
| #endif /* TCP_NOPUSH && !TCP_CORK */ |
| /* Enable Nagle's algorithm for normal buffering */ |
| res &= (0 == setsockopt (connection->socket_fd, |
| IPPROTO_TCP, |
| TCP_NODELAY, |
| (const void *) &off_val, |
| sizeof (off_val))) |
| ? true : false; |
| return res; |
| #else /* !TCP_NODELAY */ |
| return false; |
| #endif /* !TCP_NODELAY */ |
| } |
| |
| |
| /** |
| * Do we (still) need to send a 100 continue |
| * message for this request? |
| * |
| * @param request the request to test |
| * @return false if we don't need 100 CONTINUE, true if we do |
| */ |
| static bool |
| need_100_continue (struct MHD_Request *request) |
| { |
| const char *expect; |
| |
| return ( (NULL == request->response) && |
| (NULL != request->version_s) && |
| (MHD_str_equal_caseless_ (request->version_s, |
| MHD_HTTP_VERSION_1_1)) && |
| (NULL != (expect = MHD_request_lookup_value (request, |
| MHD_HEADER_KIND, |
| MHD_HTTP_HEADER_EXPECT))) |
| && |
| (MHD_str_equal_caseless_ (expect, |
| "100-continue")) && |
| (request->continue_message_write_offset < |
| MHD_STATICSTR_LEN_ (HTTP_100_CONTINUE)) ); |
| } |
| |
| |
| /** |
| * Parse the cookie header (see RFC 2109). |
| * |
| * @param request request to parse header of |
| * @return true for success, false for failure (malformed, out of memory) |
| */ |
| static int |
| parse_cookie_header (struct MHD_Request *request) |
| { |
| const char *hdr; |
| char *cpy; |
| char *pos; |
| char *sce; |
| char *semicolon; |
| char *equals; |
| char *ekill; |
| char old; |
| int quotes; |
| |
| hdr = MHD_request_lookup_value (request, |
| MHD_HEADER_KIND, |
| MHD_HTTP_HEADER_COOKIE); |
| if (NULL == hdr) |
| return true; |
| cpy = MHD_pool_allocate (request->connection->pool, |
| strlen (hdr) + 1, |
| MHD_YES); |
| if (NULL == cpy) |
| { |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (request->daemon, |
| MHD_SC_COOKIE_POOL_ALLOCATION_FAILURE, |
| _ ("Not enough memory in pool to parse cookies!\n")); |
| #endif |
| transmit_error_response (request, |
| MHD_SC_COOKIE_POOL_ALLOCATION_FAILURE, |
| MHD_HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE, |
| REQUEST_TOO_BIG); |
| return false; |
| } |
| memcpy (cpy, |
| hdr, |
| strlen (hdr) + 1); |
| pos = cpy; |
| while (NULL != pos) |
| { |
| while (' ' == *pos) |
| pos++; /* skip spaces */ |
| |
| sce = pos; |
| while ( ((*sce) != '\0') && |
| ((*sce) != ',') && |
| ((*sce) != ';') && |
| ((*sce) != '=') ) |
| sce++; |
| /* remove tailing whitespace (if any) from key */ |
| ekill = sce - 1; |
| while ( (*ekill == ' ') && |
| (ekill >= pos) ) |
| *(ekill--) = '\0'; |
| old = *sce; |
| *sce = '\0'; |
| if (old != '=') |
| { |
| /* value part omitted, use empty string... */ |
| if (! request_add_header (request, |
| pos, |
| "", |
| MHD_COOKIE_KIND)) |
| return false; |
| if (old == '\0') |
| break; |
| pos = sce + 1; |
| continue; |
| } |
| equals = sce + 1; |
| quotes = 0; |
| semicolon = equals; |
| while ( ('\0' != semicolon[0]) && |
| ( (0 != quotes) || |
| ( (';' != semicolon[0]) && |
| (',' != semicolon[0]) ) ) ) |
| { |
| if ('"' == semicolon[0]) |
| quotes = (quotes + 1) & 1; |
| semicolon++; |
| } |
| if ('\0' == semicolon[0]) |
| semicolon = NULL; |
| if (NULL != semicolon) |
| { |
| semicolon[0] = '\0'; |
| semicolon++; |
| } |
| /* remove quotes */ |
| if ( ('"' == equals[0]) && |
| ('"' == equals[strlen (equals) - 1]) ) |
| { |
| equals[strlen (equals) - 1] = '\0'; |
| equals++; |
| } |
| if (! request_add_header (request, |
| pos, |
| equals, |
| MHD_COOKIE_KIND)) |
| return false; |
| pos = semicolon; |
| } |
| return true; |
| } |
| |
| |
| /** |
| * 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 request request we're processing |
| */ |
| static void |
| parse_request_headers (struct MHD_Request *request) |
| { |
| struct MHD_Daemon *daemon = request->daemon; |
| struct MHD_Connection *connection = request->connection; |
| const char *clen; |
| struct MHD_Response *response; |
| const char *enc; |
| const char *end; |
| |
| parse_cookie_header (request); /* FIXME: return value ignored! */ |
| if ( (MHD_PSL_STRICT == daemon->protocol_strict_level) && |
| (NULL != request->version_s) && |
| (MHD_str_equal_caseless_ (MHD_HTTP_VERSION_1_1, |
| request->version_s)) && |
| (NULL == |
| MHD_request_lookup_value (request, |
| MHD_HEADER_KIND, |
| MHD_HTTP_HEADER_HOST)) ) |
| { |
| /* die, http 1.1 request without host and we are pedantic */ |
| request->state = MHD_REQUEST_FOOTERS_RECEIVED; |
| connection->read_closed = true; |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| MHD_SC_HOST_HEADER_MISSING, |
| _ ("Received HTTP 1.1 request without `Host' header.\n")); |
| #endif |
| mhd_assert (NULL == request->response); |
| response = |
| MHD_response_from_buffer (MHD_HTTP_BAD_REQUEST, |
| MHD_STATICSTR_LEN_ (REQUEST_LACKS_HOST), |
| REQUEST_LACKS_HOST, |
| MHD_RESPMEM_PERSISTENT); |
| request->response = response; |
| // FIXME: state machine advance? |
| return; |
| } |
| |
| request->remaining_upload_size = 0; |
| enc = MHD_request_lookup_value (request, |
| MHD_HEADER_KIND, |
| MHD_HTTP_HEADER_TRANSFER_ENCODING); |
| if (NULL != enc) |
| { |
| request->remaining_upload_size = MHD_SIZE_UNKNOWN; |
| if (MHD_str_equal_caseless_ (enc, |
| "chunked")) |
| request->have_chunked_upload = true; |
| return; |
| } |
| clen = MHD_request_lookup_value (request, |
| MHD_HEADER_KIND, |
| MHD_HTTP_HEADER_CONTENT_LENGTH); |
| if (NULL == clen) |
| return; |
| end = clen + MHD_str_to_uint64_ (clen, |
| &request->remaining_upload_size); |
| if ( (clen == end) || |
| ('\0' != *end) ) |
| { |
| request->remaining_upload_size = 0; |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (request->daemon, |
| MHD_SC_CONTENT_LENGTH_MALFORMED, |
| "Failed to parse `Content-Length' header. Closing connection.\n"); |
| #endif |
| CONNECTION_CLOSE_ERROR (connection, |
| MHD_SC_CONTENT_LENGTH_MALFORMED, |
| NULL); |
| } |
| } |
| |
| |
| /** |
| * Call the handler of the application for this |
| * request. |
| * |
| * @param request request we're processing |
| */ |
| static void |
| call_request_handler (struct MHD_Request *request) |
| { |
| struct MHD_Daemon *daemon = request->daemon; |
| struct MHD_Connection *connection = request->connection; |
| const struct MHD_Action *action; |
| |
| if (NULL != request->response) |
| return; /* already queued a response */ |
| if (NULL == (action = |
| daemon->rc (daemon->rc_cls, |
| request, |
| request->url, |
| request->method))) |
| { |
| /* serious internal error, close connection */ |
| CONNECTION_CLOSE_ERROR (connection, |
| MHD_SC_APPLICATION_CALLBACK_FAILURE_CLOSED, |
| _ ( |
| "Application reported internal error, closing connection.\n")); |
| return; |
| } |
| action->action (action->action_cls, |
| request); |
| } |
| |
| |
| /** |
| * Call the handler of the application for this request. Handles |
| * chunking of the upload as well as normal uploads. |
| * |
| * @param request request we're processing |
| */ |
| static void |
| process_request_body (struct MHD_Request *request) |
| { |
| struct MHD_Daemon *daemon = request->daemon; |
| struct MHD_Connection *connection = request->connection; |
| size_t available; |
| bool instant_retry; |
| char *buffer_head; |
| |
| if (NULL != request->response) |
| return; /* already queued a response */ |
| |
| buffer_head = request->read_buffer; |
| available = request->read_buffer_offset; |
| do |
| { |
| size_t to_be_processed; |
| size_t left_unprocessed; |
| size_t processed_size; |
| |
| instant_retry = false; |
| if ( (request->have_chunked_upload) && |
| (MHD_SIZE_UNKNOWN == request->remaining_upload_size) ) |
| { |
| if ( (request->current_chunk_offset == request->current_chunk_size) && |
| (0LLU != request->current_chunk_offset) && |
| (available >= 2) ) |
| { |
| size_t i; |
| |
| /* skip new line at the *end* of a chunk */ |
| i = 0; |
| if ( ('\r' == buffer_head[i]) || |
| ('\n' == buffer_head[i]) ) |
| i++; /* skip 1st part of line feed */ |
| if ( ('\r' == buffer_head[i]) || |
| ('\n' == buffer_head[i]) ) |
| i++; /* skip 2nd part of line feed */ |
| if (0 == i) |
| { |
| /* malformed encoding */ |
| CONNECTION_CLOSE_ERROR (connection, |
| MHD_SC_CHUNKED_ENCODING_MALFORMED, |
| _ ( |
| "Received malformed HTTP request (bad chunked encoding). Closing connection.\n")); |
| return; |
| } |
| available -= i; |
| buffer_head += i; |
| request->current_chunk_offset = 0; |
| request->current_chunk_size = 0; |
| } |
| if (request->current_chunk_offset < |
| request->current_chunk_size) |
| { |
| uint64_t cur_chunk_left; |
| |
| /* we are in the middle of a chunk, give |
| as much as possible to the client (without |
| crossing chunk boundaries) */ |
| cur_chunk_left |
| = request->current_chunk_size - request->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; |
| size_t end_size; |
| bool malformed; |
| |
| /* we need to read chunk boundaries */ |
| i = 0; |
| while (i < available) |
| { |
| if ( ('\r' == buffer_head[i]) || |
| ('\n' == buffer_head[i]) || |
| (';' == buffer_head[i]) ) |
| break; |
| i++; |
| if (i >= 16) |
| break; |
| } |
| end_size = i; |
| /* find beginning of CRLF (skip over chunk extensions) */ |
| if (';' == buffer_head[i]) |
| { |
| while (i < available) |
| { |
| if ( ('\r' == buffer_head[i]) || |
| ('\n' == buffer_head[i]) ) |
| break; |
| i++; |
| } |
| } |
| /* take '\n' into account; if '\n' is the unavailable |
| character, we will need to wait until we have it |
| before going further */ |
| if ( (i + 1 >= available) && |
| ! ( (1 == i) && |
| (2 == available) && |
| ('0' == buffer_head[0]) ) ) |
| break; /* need more data... */ |
| i++; |
| malformed = (end_size >= 16); |
| if (! malformed) |
| { |
| size_t num_dig = MHD_strx_to_uint64_n_ (buffer_head, |
| end_size, |
| &request->current_chunk_size); |
| malformed = (end_size != num_dig); |
| } |
| if (malformed) |
| { |
| /* malformed encoding */ |
| CONNECTION_CLOSE_ERROR (connection, |
| MHD_SC_CHUNKED_ENCODING_MALFORMED, |
| _ ( |
| "Received malformed HTTP request (bad chunked encoding). Closing connection.\n")); |
| return; |
| } |
| /* skip 2nd part of line feed */ |
| if ( (i < available) && |
| ( ('\r' == buffer_head[i]) || |
| ('\n' == buffer_head[i]) ) ) |
| i++; |
| |
| buffer_head += i; |
| available -= i; |
| request->current_chunk_offset = 0; |
| |
| if (available > 0) |
| instant_retry = true; |
| if (0LLU == request->current_chunk_size) |
| { |
| request->remaining_upload_size = 0; |
| break; |
| } |
| continue; |
| } |
| } |
| else |
| { |
| /* no chunked encoding, give all to the client */ |
| if ( (0 != request->remaining_upload_size) && |
| (MHD_SIZE_UNKNOWN != request->remaining_upload_size) && |
| (request->remaining_upload_size < available) ) |
| { |
| to_be_processed = (size_t) request->remaining_upload_size; |
| } |
| else |
| { |
| /** |
| * 1. no chunked encoding, give all to the client |
| * 2. client may send large chunked data, but only a smaller part is available at one time. |
| */ |
| to_be_processed = available; |
| } |
| } |
| left_unprocessed = to_be_processed; |
| #if FIXME_OLD_STYLE |
| if (MHD_NO == |
| daemon->rc (daemon->rc_cls, |
| request, |
| request->url, |
| request->method, |
| request->version, |
| buffer_head, |
| &left_unprocessed, |
| &request->client_context)) |
| { |
| /* serious internal error, close connection */ |
| CONNECTION_CLOSE_ERROR (connection, |
| MHD_SC_APPLICATION_CALLBACK_FAILURE_CLOSED, |
| _ ( |
| "Application reported internal error, closing connection.\n")); |
| return; |
| } |
| #endif |
| if (left_unprocessed > to_be_processed) |
| mhd_panic (mhd_panic_cls, |
| __FILE__, |
| __LINE__ |
| #ifdef HAVE_MESSAGES |
| , _ ("libmicrohttpd API violation.\n") |
| #else |
| , NULL |
| #endif |
| ); |
| if (0 != left_unprocessed) |
| { |
| instant_retry = false; /* client did not process everything */ |
| #ifdef HAVE_MESSAGES |
| /* client did not process all upload data, complain if |
| the setup was incorrect, which may prevent us from |
| handling the rest of the request */ |
| if ( (MHD_TM_EXTERNAL_EVENT_LOOP == daemon->threading_mode) && |
| (! connection->suspended) ) |
| MHD_DLOG (daemon, |
| MHD_SC_APPLICATION_HUNG_CONNECTION, |
| _ ( |
| "WARNING: incomplete upload processing and connection not suspended may result in hung connection.\n")); |
| #endif |
| } |
| processed_size = to_be_processed - left_unprocessed; |
| if (request->have_chunked_upload) |
| request->current_chunk_offset += processed_size; |
| /* dh left "processed" bytes in buffer for next time... */ |
| buffer_head += processed_size; |
| available -= processed_size; |
| if (MHD_SIZE_UNKNOWN != request->remaining_upload_size) |
| request->remaining_upload_size -= processed_size; |
| } |
| while (instant_retry); |
| if (available > 0) |
| memmove (request->read_buffer, |
| buffer_head, |
| available); |
| request->read_buffer_offset = available; |
| } |
| |
| |
| /** |
| * 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; |
| |
| if (connection->request.in_cleanup) |
| return; /* Prevent double cleanup. */ |
| connection->request.in_cleanup = true; |
| if (NULL != connection->request.response) |
| { |
| MHD_response_queue_for_destroy (connection->request.response); |
| connection->request.response = NULL; |
| } |
| MHD_mutex_lock_chk_ (&daemon->cleanup_connection_mutex); |
| if (connection->suspended) |
| { |
| DLL_remove (daemon->suspended_connections_head, |
| daemon->suspended_connections_tail, |
| connection); |
| connection->suspended = false; |
| } |
| else |
| { |
| if (MHD_TM_THREAD_PER_CONNECTION != daemon->threading_mode) |
| { |
| if (connection->connection_timeout == |
| daemon->connection_default_timeout) |
| 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->request.in_idle = false; |
| MHD_mutex_unlock_chk_ (&daemon->cleanup_connection_mutex); |
| if (MHD_TM_THREAD_PER_CONNECTION == daemon->threading_mode) |
| { |
| /* 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, |
| MHD_SC_ITC_USE_FAILED, |
| _ ( |
| "Failed to signal end of connection via inter-thread communication channel.\n")); |
| #endif |
| } |
| } |
| } |
| |
| |
| #ifdef EPOLL_SUPPORT |
| /** |
| * Perform epoll() processing, possibly moving the connection back into |
| * the epoll() set if needed. |
| * |
| * @param connection connection to process |
| * @return true if we should continue to process the |
| * connection (not dead yet), false if it died |
| */ |
| static bool |
| connection_epoll_update_ (struct MHD_Connection *connection) |
| { |
| struct MHD_Daemon *daemon = connection->daemon; |
| |
| if ( (MHD_ELS_EPOLL == daemon->event_loop_syscall) && |
| (0 == (connection->epoll_state & MHD_EPOLL_STATE_IN_EPOLL_SET)) && |
| (0 == (connection->epoll_state & MHD_EPOLL_STATE_SUSPENDED)) && |
| ( ( (MHD_EVENT_LOOP_INFO_WRITE == connection->request.event_loop_info) && |
| (0 == (connection->epoll_state & MHD_EPOLL_STATE_WRITE_READY))) || |
| ( (MHD_EVENT_LOOP_INFO_READ == connection->request.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 |
| MHD_DLOG (daemon, |
| MHD_SC_EPOLL_CTL_ADD_FAILED, |
| _ ("Call to epoll_ctl failed: %s\n"), |
| MHD_socket_last_strerr_ ()); |
| #endif |
| connection->request.state = MHD_REQUEST_CLOSED; |
| cleanup_connection (connection); |
| return false; |
| } |
| connection->epoll_state |= MHD_EPOLL_STATE_IN_EPOLL_SET; |
| } |
| return true; |
| } |
| |
| |
| #endif |
| |
| |
| /** |
| * 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 |
| connection_update_event_loop_info (struct MHD_Connection *connection) |
| { |
| struct MHD_Daemon *daemon = connection->daemon; |
| struct MHD_Request *request = &connection->request; |
| |
| /* Do not update states of suspended connection */ |
| if (connection->suspended) |
| return; /* States will be updated after resume. */ |
| #ifdef HTTPS_SUPPORT |
| { |
| struct MHD_TLS_Plugin *tls; |
| |
| if ( (NULL != (tls = daemon->tls_api)) && |
| (tls->update_event_loop_info (tls->cls, |
| connection->tls_cs, |
| &request->event_loop_info)) ) |
| return; /* TLS has decided what to do */ |
| } |
| #endif /* HTTPS_SUPPORT */ |
| while (1) |
| { |
| #if DEBUG_STATES |
| MHD_DLOG (daemon, |
| MHD_SC_STATE_MACHINE_STATUS_REPORT, |
| _ ("In function %s handling connection at state: %s\n"), |
| __FUNCTION__, |
| MHD_state_to_string (request->state)); |
| #endif |
| switch (request->state) |
| { |
| case MHD_REQUEST_INIT: |
| case MHD_REQUEST_URL_RECEIVED: |
| case MHD_REQUEST_HEADER_PART_RECEIVED: |
| /* while reading headers, we always grow the |
| read buffer if needed, no size-check required */ |
| if ( (request->read_buffer_offset == request->read_buffer_size) && |
| (! try_grow_read_buffer (request)) ) |
| { |
| transmit_error_response (request, |
| MHD_SC_CLIENT_HEADER_TOO_BIG, |
| (NULL != request->url) |
| ? MHD_HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE |
| : MHD_HTTP_URI_TOO_LONG, |
| REQUEST_TOO_BIG); |
| continue; |
| } |
| if (! connection->read_closed) |
| request->event_loop_info = MHD_EVENT_LOOP_INFO_READ; |
| else |
| request->event_loop_info = MHD_EVENT_LOOP_INFO_BLOCK; |
| break; |
| case MHD_REQUEST_HEADERS_RECEIVED: |
| mhd_assert (0); |
| break; |
| case MHD_REQUEST_HEADERS_PROCESSED: |
| mhd_assert (0); |
| break; |
| case MHD_REQUEST_CONTINUE_SENDING: |
| request->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; |
| break; |
| case MHD_REQUEST_CONTINUE_SENT: |
| if (request->read_buffer_offset == request->read_buffer_size) |
| { |
| if ( (! try_grow_read_buffer (request)) && |
| (MHD_TM_EXTERNAL_EVENT_LOOP != daemon->threading_mode) ) |
| { |
| /* 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 (request, |
| MHD_SC_APPLICATION_HUNG_CONNECTION_CLOSED, |
| MHD_HTTP_INTERNAL_SERVER_ERROR, |
| INTERNAL_ERROR); |
| continue; |
| } |
| } |
| if ( (request->read_buffer_offset < request->read_buffer_size) && |
| (! connection->read_closed) ) |
| request->event_loop_info = MHD_EVENT_LOOP_INFO_READ; |
| else |
| request->event_loop_info = MHD_EVENT_LOOP_INFO_BLOCK; |
| break; |
| case MHD_REQUEST_BODY_RECEIVED: |
| case MHD_REQUEST_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, |
| MHD_SC_CONNECTION_READ_FAIL_CLOSED, |
| NULL); |
| continue; |
| } |
| request->event_loop_info = MHD_EVENT_LOOP_INFO_READ; |
| /* transition to FOOTERS_RECEIVED |
| happens in read handler */ |
| break; |
| case MHD_REQUEST_FOOTERS_RECEIVED: |
| request->event_loop_info = MHD_EVENT_LOOP_INFO_BLOCK; |
| break; |
| case MHD_REQUEST_HEADERS_SENDING: |
| /* headers in buffer, keep writing */ |
| request->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; |
| break; |
| case MHD_REQUEST_HEADERS_SENT: |
| mhd_assert (0); |
| break; |
| case MHD_REQUEST_NORMAL_BODY_READY: |
| request->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; |
| break; |
| case MHD_REQUEST_NORMAL_BODY_UNREADY: |
| request->event_loop_info = MHD_EVENT_LOOP_INFO_BLOCK; |
| break; |
| case MHD_REQUEST_CHUNKED_BODY_READY: |
| request->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; |
| break; |
| case MHD_REQUEST_CHUNKED_BODY_UNREADY: |
| request->event_loop_info = MHD_EVENT_LOOP_INFO_BLOCK; |
| break; |
| case MHD_REQUEST_BODY_SENT: |
| mhd_assert (0); |
| break; |
| case MHD_REQUEST_FOOTERS_SENDING: |
| request->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; |
| break; |
| case MHD_REQUEST_FOOTERS_SENT: |
| mhd_assert (0); |
| break; |
| case MHD_REQUEST_CLOSED: |
| request->event_loop_info = MHD_EVENT_LOOP_INFO_CLEANUP; |
| return; /* do nothing, not even reading */ |
| #ifdef UPGRADE_SUPPORT |
| case MHD_REQUEST_UPGRADE: |
| mhd_assert (0); |
| break; |
| #endif /* UPGRADE_SUPPORT */ |
| default: |
| mhd_assert (0); |
| } |
| break; |
| } |
| } |
| |
| |
| /** |
| * This function was created to handle per-request processing that |
| * has to happen even if the socket cannot be read or written to. |
| * @remark To be called only from thread that process request's |
| * recv(), send() and response. |
| * |
| * @param request the request to handle |
| * @return true if we should continue to process the |
| * request (not dead yet), false if it died |
| */ |
| bool |
| MHD_request_handle_idle_ (struct MHD_Request *request) |
| { |
| struct MHD_Daemon *daemon = request->daemon; |
| struct MHD_Connection *connection = request->connection; |
| char *line; |
| size_t line_len; |
| bool ret; |
| |
| request->in_idle = true; |
| while (! connection->suspended) |
| { |
| #ifdef HTTPS_SUPPORT |
| struct MHD_TLS_Plugin *tls; |
| |
| if ( (NULL != (tls = daemon->tls_api)) && |
| (! tls->idle_ready (tls->cls, |
| connection->tls_cs)) ) |
| break; |
| #endif /* HTTPS_SUPPORT */ |
| #if DEBUG_STATES |
| MHD_DLOG (daemon, |
| MHD_SC_STATE_MACHINE_STATUS_REPORT, |
| _ ("In function %s handling connection at state: %s\n"), |
| __FUNCTION__, |
| MHD_state_to_string (request->state)); |
| #endif |
| switch (request->state) |
| { |
| case MHD_REQUEST_INIT: |
| line = get_next_header_line (request, |
| &line_len); |
| /* Check for empty string, as we might want |
| to tolerate 'spurious' empty lines; also |
| NULL means we didn't get a full line yet; |
| line is not 0-terminated here. */ |
| if ( (NULL == line) || |
| (0 == line[0]) ) |
| { |
| if (MHD_REQUEST_INIT != request->state) |
| continue; |
| if (connection->read_closed) |
| { |
| CONNECTION_CLOSE_ERROR (connection, |
| MHD_SC_CONNECTION_READ_FAIL_CLOSED, |
| NULL); |
| continue; |
| } |
| break; |
| } |
| if (MHD_NO == |
| parse_initial_message_line (request, |
| line, |
| line_len)) |
| CONNECTION_CLOSE_ERROR (connection, |
| MHD_SC_CONNECTION_CLOSED, |
| NULL); |
| else |
| request->state = MHD_REQUEST_URL_RECEIVED; |
| continue; |
| case MHD_REQUEST_URL_RECEIVED: |
| line = get_next_header_line (request, |
| NULL); |
| if (NULL == line) |
| { |
| if (MHD_REQUEST_URL_RECEIVED != request->state) |
| continue; |
| if (connection->read_closed) |
| { |
| CONNECTION_CLOSE_ERROR (connection, |
| MHD_SC_CONNECTION_READ_FAIL_CLOSED, |
| NULL); |
| continue; |
| } |
| break; |
| } |
| if (0 == line[0]) |
| { |
| request->state = MHD_REQUEST_HEADERS_RECEIVED; |
| request->header_size = (size_t) (line - request->read_buffer); |
| continue; |
| } |
| if (! process_header_line (request, |
| line)) |
| { |
| transmit_error_response (request, |
| MHD_SC_CONNECTION_PARSE_FAIL_CLOSED, |
| MHD_HTTP_BAD_REQUEST, |
| REQUEST_MALFORMED); |
| break; |
| } |
| request->state = MHD_REQUEST_HEADER_PART_RECEIVED; |
| continue; |
| case MHD_REQUEST_HEADER_PART_RECEIVED: |
| line = get_next_header_line (request, |
| NULL); |
| if (NULL == line) |
| { |
| if (request->state != MHD_REQUEST_HEADER_PART_RECEIVED) |
| continue; |
| if (connection->read_closed) |
| { |
| CONNECTION_CLOSE_ERROR (connection, |
| MHD_SC_CONNECTION_READ_FAIL_CLOSED, |
| NULL); |
| continue; |
| } |
| break; |
| } |
| if (MHD_NO == |
| process_broken_line (request, |
| line, |
| MHD_HEADER_KIND)) |
| continue; |
| if (0 == line[0]) |
| { |
| request->state = MHD_REQUEST_HEADERS_RECEIVED; |
| request->header_size = (size_t) (line - request->read_buffer); |
| continue; |
| } |
| continue; |
| case MHD_REQUEST_HEADERS_RECEIVED: |
| parse_request_headers (request); |
| if (MHD_REQUEST_CLOSED == request->state) |
| continue; |
| request->state = MHD_REQUEST_HEADERS_PROCESSED; |
| if (connection->suspended) |
| break; |
| continue; |
| case MHD_REQUEST_HEADERS_PROCESSED: |
| call_request_handler (request); /* first call */ |
| if (MHD_REQUEST_CLOSED == request->state) |
| continue; |
| if (need_100_continue (request)) |
| { |
| request->state = MHD_REQUEST_CONTINUE_SENDING; |
| if (socket_flush_possible (connection)) |
| socket_start_extra_buffering (connection); |
| else |
| socket_start_no_buffering (connection); |
| break; |
| } |
| if ( (NULL != request->response) && |
| ( (MHD_METHOD_POST == request->method) || |
| (MHD_METHOD_PUT == request->method) ) ) |
| { |
| /* we refused (no upload allowed!) */ |
| request->remaining_upload_size = 0; |
| /* force close, in case client still tries to upload... */ |
| connection->read_closed = true; |
| } |
| request->state = (0 == request->remaining_upload_size) |
| ? MHD_REQUEST_FOOTERS_RECEIVED |
| : MHD_REQUEST_CONTINUE_SENT; |
| if (connection->suspended) |
| break; |
| continue; |
| case MHD_REQUEST_CONTINUE_SENDING: |
| if (request->continue_message_write_offset == |
| MHD_STATICSTR_LEN_ (HTTP_100_CONTINUE)) |
| { |
| request->state = MHD_REQUEST_CONTINUE_SENT; |
| if (! socket_flush_possible (connection)) |
| socket_start_no_buffering_flush (connection); |
| else |
| socket_start_normal_buffering (connection); |
| continue; |
| } |
| break; |
| case MHD_REQUEST_CONTINUE_SENT: |
| if (0 != request->read_buffer_offset) |
| { |
| process_request_body (request); /* loop call */ |
| if (MHD_REQUEST_CLOSED == request->state) |
| continue; |
| } |
| if ( (0 == request->remaining_upload_size) || |
| ( (MHD_SIZE_UNKNOWN == request->remaining_upload_size) && |
| (0 == request->read_buffer_offset) && |
| (connection->read_closed) ) ) |
| { |
| if ( (request->have_chunked_upload) && |
| (! connection->read_closed) ) |
| request->state = MHD_REQUEST_BODY_RECEIVED; |
| else |
| request->state = MHD_REQUEST_FOOTERS_RECEIVED; |
| if (connection->suspended) |
| break; |
| continue; |
| } |
| break; |
| case MHD_REQUEST_BODY_RECEIVED: |
| line = get_next_header_line (request, |
| NULL); |
| if (NULL == line) |
| { |
| if (request->state != MHD_REQUEST_BODY_RECEIVED) |
| continue; |
| if (connection->read_closed) |
| { |
| CONNECTION_CLOSE_ERROR (connection, |
| MHD_SC_CONNECTION_CLOSED, |
| NULL); |
| continue; |
| } |
| break; |
| } |
| if (0 == line[0]) |
| { |
| request->state = MHD_REQUEST_FOOTERS_RECEIVED; |
| if (connection->suspended) |
| break; |
| continue; |
| } |
| if (MHD_NO == process_header_line (request, |
| line)) |
| { |
| transmit_error_response (request, |
| MHD_SC_CONNECTION_PARSE_FAIL_CLOSED, |
| MHD_HTTP_BAD_REQUEST, |
| REQUEST_MALFORMED); |
| break; |
| } |
| request->state = MHD_REQUEST_FOOTER_PART_RECEIVED; |
| continue; |
| case MHD_REQUEST_FOOTER_PART_RECEIVED: |
| line = get_next_header_line (request, |
| NULL); |
| if (NULL == line) |
| { |
| if (request->state != MHD_REQUEST_FOOTER_PART_RECEIVED) |
| continue; |
| if (connection->read_closed) |
| { |
| CONNECTION_CLOSE_ERROR (connection, |
| MHD_SC_CONNECTION_CLOSED, |
| NULL); |
| continue; |
| } |
| break; |
| } |
| if (MHD_NO == |
| process_broken_line (request, |
| line, |
| MHD_FOOTER_KIND)) |
| continue; |
| if (0 == line[0]) |
| { |
| request->state = MHD_REQUEST_FOOTERS_RECEIVED; |
| if (connection->suspended) |
| break; |
| continue; |
| } |
| continue; |
| case MHD_REQUEST_FOOTERS_RECEIVED: |
| call_request_handler (request); /* "final" call */ |
| if (request->state == MHD_REQUEST_CLOSED) |
| continue; |
| if (NULL == request->response) |
| break; /* try again next time */ |
| if (! build_header_response (request)) |
| { |
| /* oops - close! */ |
| CONNECTION_CLOSE_ERROR (connection, |
| MHD_SC_FAILED_RESPONSE_HEADER_GENERATION, |
| _ ( |
| "Closing connection (failed to create response header).\n")); |
| continue; |
| } |
| request->state = MHD_REQUEST_HEADERS_SENDING; |
| if (MHD_NO != socket_flush_possible (connection)) |
| socket_start_extra_buffering (connection); |
| else |
| socket_start_no_buffering (connection); |
| |
| break; |
| case MHD_REQUEST_HEADERS_SENDING: |
| /* no default action */ |
| break; |
| case MHD_REQUEST_HEADERS_SENT: |
| /* Some clients may take some actions right after header receive */ |
| if (MHD_NO != socket_flush_possible (connection)) |
| socket_start_no_buffering_flush (connection); |
| |
| #ifdef UPGRADE_SUPPORT |
| if (NULL != request->response->upgrade_handler) |
| { |
| socket_start_normal_buffering (connection); |
| request->state = MHD_REQUEST_UPGRADE; |
| #if FIXME_LEGACY_STYLE |
| /* This request is "upgraded". Pass socket to application. */ |
| if (! MHD_response_execute_upgrade_ (request->response, |
| request)) |
| { |
| /* upgrade failed, fail hard */ |
| CONNECTION_CLOSE_ERROR (connection, |
| MHD_SC_CONNECTION_CLOSED, |
| NULL); |
| continue; |
| } |
| #endif |
| /* Response is not required anymore for this request. */ |
| { |
| struct MHD_Response *const resp = request->response; |
| |
| request->response = NULL; |
| MHD_response_queue_for_destroy (resp); |
| } |
| continue; |
| } |
| #endif /* UPGRADE_SUPPORT */ |
| if (MHD_NO != socket_flush_possible (connection)) |
| socket_start_extra_buffering (connection); |
| else |
| socket_start_normal_buffering (connection); |
| |
| if (request->have_chunked_upload) |
| request->state = MHD_REQUEST_CHUNKED_BODY_UNREADY; |
| else |
| request->state = MHD_REQUEST_NORMAL_BODY_UNREADY; |
| continue; |
| case MHD_REQUEST_NORMAL_BODY_READY: |
| /* nothing to do here */ |
| break; |
| case MHD_REQUEST_NORMAL_BODY_UNREADY: |
| if (NULL != request->response->crc) |
| MHD_mutex_lock_chk_ (&request->response->mutex); |
| if (0 == request->response->total_size) |
| { |
| if (NULL != request->response->crc) |
| MHD_mutex_unlock_chk_ (&request->response->mutex); |
| request->state = MHD_REQUEST_BODY_SENT; |
| continue; |
| } |
| if (try_ready_normal_body (request)) |
| { |
| if (NULL != request->response->crc) |
| MHD_mutex_unlock_chk_ (&request->response->mutex); |
| request->state = MHD_REQUEST_NORMAL_BODY_READY; |
| /* Buffering for flushable socket was already enabled*/ |
| if (MHD_NO == socket_flush_possible (connection)) |
| socket_start_no_buffering (connection); |
| break; |
| } |
| /* mutex was already unlocked by "try_ready_normal_body */ |
| /* not ready, no socket action */ |
| break; |
| case MHD_REQUEST_CHUNKED_BODY_READY: |
| /* nothing to do here */ |
| break; |
| case MHD_REQUEST_CHUNKED_BODY_UNREADY: |
| if (NULL != request->response->crc) |
| MHD_mutex_lock_chk_ (&request->response->mutex); |
| if ( (0 == request->response->total_size) || |
| (request->response_write_position == |
| request->response->total_size) ) |
| { |
| if (NULL != request->response->crc) |
| MHD_mutex_unlock_chk_ (&request->response->mutex); |
| request->state = MHD_REQUEST_BODY_SENT; |
| continue; |
| } |
| if (try_ready_chunked_body (request)) |
| { |
| if (NULL != request->response->crc) |
| MHD_mutex_unlock_chk_ (&request->response->mutex); |
| request->state = MHD_REQUEST_CHUNKED_BODY_READY; |
| /* Buffering for flushable socket was already enabled */ |
| if (MHD_NO == socket_flush_possible (connection)) |
| socket_start_no_buffering (connection); |
| continue; |
| } |
| /* mutex was already unlocked by try_ready_chunked_body */ |
| break; |
| case MHD_REQUEST_BODY_SENT: |
| if (! build_header_response (request)) |
| { |
| /* oops - close! */ |
| CONNECTION_CLOSE_ERROR (connection, |
| MHD_SC_FAILED_RESPONSE_HEADER_GENERATION, |
| _ ( |
| "Closing connection (failed to create response header).\n")); |
| continue; |
| } |
| if ( (! request->have_chunked_upload) || |
| (request->write_buffer_send_offset == |
| request->write_buffer_append_offset) ) |
| request->state = MHD_REQUEST_FOOTERS_SENT; |
| else |
| request->state = MHD_REQUEST_FOOTERS_SENDING; |
| continue; |
| case MHD_REQUEST_FOOTERS_SENDING: |
| /* no default action */ |
| break; |
| case MHD_REQUEST_FOOTERS_SENT: |
| { |
| struct MHD_Response *response = request->response; |
| |
| if (MHD_HTTP_PROCESSING == response->status_code) |
| { |
| /* After this type of response, we allow sending another! */ |
| request->state = MHD_REQUEST_HEADERS_PROCESSED; |
| MHD_response_queue_for_destroy (response); |
| request->response = NULL; |
| /* FIXME: maybe partially reset memory pool? */ |
| continue; |
| } |
| if (socket_flush_possible (connection)) |
| socket_start_no_buffering_flush (connection); |
| else |
| socket_start_normal_buffering (connection); |
| |
| if (NULL != response->termination_cb) |
| { |
| response->termination_cb (response->termination_cb_cls, |
| MHD_REQUEST_TERMINATED_COMPLETED_OK, |
| request->client_context); |
| } |
| MHD_response_queue_for_destroy (response); |
| request->response = NULL; |
| } |
| if ( (MHD_CONN_USE_KEEPALIVE != request->keepalive) || |
| (connection->read_closed) ) |
| { |
| /* have to close for some reason */ |
| MHD_connection_close_ (connection, |
| MHD_REQUEST_TERMINATED_COMPLETED_OK); |
| MHD_pool_destroy (connection->pool); |
| connection->pool = NULL; |
| request->read_buffer = NULL; |
| request->read_buffer_size = 0; |
| request->read_buffer_offset = 0; |
| } |
| else |
| { |
| /* can try to keep-alive */ |
| if (socket_flush_possible (connection)) |
| socket_start_normal_buffering (connection); |
| request->version_s = NULL; |
| request->state = MHD_REQUEST_INIT; |
| request->last = NULL; |
| request->colon = NULL; |
| request->header_size = 0; |
| request->keepalive = MHD_CONN_KEEPALIVE_UNKOWN; |
| /* Reset the read buffer to the starting size, |
| preserving the bytes we have already read. */ |
| request->read_buffer |
| = MHD_pool_reset (connection->pool, |
| request->read_buffer, |
| request->read_buffer_offset, |
| daemon->connection_memory_limit_b / 2); |
| request->read_buffer_size |
| = daemon->connection_memory_limit_b / 2; |
| } |
| // FIXME: this is too much, NULLs out some of the things |
| // initialized above... |
| memset (request, |
| 0, |
| sizeof (struct MHD_Request)); |
| request->daemon = daemon; |
| request->connection = connection; |
| continue; |
| case MHD_REQUEST_CLOSED: |
| cleanup_connection (connection); |
| request->in_idle = false; |
| return false; |
| #ifdef UPGRADE_SUPPORT |
| case MHD_REQUEST_UPGRADE: |
| request->in_idle = false; |
| return true; /* keep open */ |
| #endif /* UPGRADE_SUPPORT */ |
| default: |
| mhd_assert (0); |
| break; |
| } |
| break; |
| } |
| if (! connection->suspended) |
| { |
| time_t timeout; |
| timeout = connection->connection_timeout; |
| if ( (0 != timeout) && |
| (timeout <= (MHD_monotonic_sec_counter () |
| - connection->last_activity)) ) |
| { |
| MHD_connection_close_ (connection, |
| MHD_REQUEST_TERMINATED_TIMEOUT_REACHED); |
| request->in_idle = false; |
| return true; |
| } |
| } |
| connection_update_event_loop_info (connection); |
| ret = true; |
| #ifdef EPOLL_SUPPORT |
| if ( (! connection->suspended) && |
| (MHD_ELS_EPOLL == daemon->event_loop_syscall) ) |
| { |
| ret = connection_epoll_update_ (connection); |
| } |
| #endif /* EPOLL_SUPPORT */ |
| request->in_idle = false; |
| return ret; |
| } |
| |
| |
| /** |
| * Call the handlers for a connection in the appropriate order based |
| * on the readiness as detected by the event loop. |
| * |
| * @param con connection to handle |
| * @param read_ready set if the socket is ready for reading |
| * @param write_ready set if the socket is ready for writing |
| * @param force_close set if a hard error was detected on the socket; |
| * if this information is not available, simply pass #MHD_NO |
| * @return #MHD_YES to continue normally, |
| * #MHD_NO if a serious error was encountered and the |
| * connection is to be closed. |
| */ |
| // FIXME: rename connection->request? |
| int |
| MHD_connection_call_handlers_ (struct MHD_Connection *con, |
| bool read_ready, |
| bool write_ready, |
| bool force_close) |
| { |
| struct MHD_Daemon *daemon = con->daemon; |
| int ret; |
| bool states_info_processed = false; |
| /* Fast track flag */ |
| bool on_fasttrack = (con->request.state == MHD_REQUEST_INIT); |
| |
| #ifdef HTTPS_SUPPORT |
| if (con->tls_read_ready) |
| read_ready = true; |
| #endif /* HTTPS_SUPPORT */ |
| if (! force_close) |
| { |
| if ( (MHD_EVENT_LOOP_INFO_READ == |
| con->request.event_loop_info) && |
| read_ready) |
| { |
| MHD_request_handle_read_ (&con->request); |
| ret = MHD_request_handle_idle_ (&con->request); |
| states_info_processed = true; |
| } |
| /* No need to check value of 'ret' here as closed connection |
| * cannot be in MHD_EVENT_LOOP_INFO_WRITE state. */ |
| if ( (MHD_EVENT_LOOP_INFO_WRITE == |
| con->request.event_loop_info) && |
| write_ready) |
| { |
| MHD_request_handle_write_ (&con->request); |
| ret = MHD_request_handle_idle_ (&con->request); |
| states_info_processed = true; |
| } |
| } |
| else |
| { |
| MHD_connection_close_ (con, |
| MHD_REQUEST_TERMINATED_WITH_ERROR); |
| return MHD_request_handle_idle_ (&con->request); |
| } |
| |
| if (! states_info_processed) |
| { /* Connection is not read or write ready, but external conditions |
| * may be changed and need to be processed. */ |
| ret = MHD_request_handle_idle_ (&con->request); |
| } |
| /* Fast track for fast connections. */ |
| /* If full request was read by single read_handler() invocation |
| and headers were completely prepared by single MHD_request_handle_idle_() |
| then try not to wait for next sockets polling and send response |
| immediately. |
| As writeability of socket was not checked and it may have |
| some data pending in system buffers, use this optimization |
| only for non-blocking sockets. *//* No need to check 'ret' as connection is always in |
| * MHD_CONNECTION_CLOSED state if 'ret' is equal 'MHD_NO'. */else if (on_fasttrack && |
| con->sk_nonblck) |
| { |
| if (MHD_REQUEST_HEADERS_SENDING == con->request.state) |
| { |
| MHD_request_handle_write_ (&con->request); |
| /* Always call 'MHD_request_handle_idle_()' after each read/write. */ |
| ret = MHD_request_handle_idle_ (&con->request); |
| } |
| /* If all headers were sent by single write_handler() and |
| * response body is prepared by single MHD_request_handle_idle_() |
| * call - continue. */ |
| if ((MHD_REQUEST_NORMAL_BODY_READY == con->request.state) || |
| (MHD_REQUEST_CHUNKED_BODY_READY == con->request.state)) |
| { |
| MHD_request_handle_write_ (&con->request); |
| ret = MHD_request_handle_idle_ (&con->request); |
| } |
| } |
| |
| /* All connection's data and states are processed for this turn. |
| * If connection already has more data to be processed - use |
| * zero timeout for next select()/poll(). */ |
| /* Thread-per-connection do not need global zero timeout as |
| * connections are processed individually. */ |
| /* Note: no need to check for read buffer availability for |
| * TLS read-ready connection in 'read info' state as connection |
| * without space in read buffer will be market as 'info block'. */ |
| if ( (! daemon->data_already_pending) && |
| (MHD_TM_THREAD_PER_CONNECTION != daemon->threading_mode) ) |
| { |
| if (MHD_EVENT_LOOP_INFO_BLOCK == |
| con->request.event_loop_info) |
| daemon->data_already_pending = true; |
| #ifdef HTTPS_SUPPORT |
| else if ( (con->tls_read_ready) && |
| (MHD_EVENT_LOOP_INFO_READ == |
| con->request.event_loop_info) ) |
| daemon->data_already_pending = true; |
| #endif /* HTTPS_SUPPORT */ |
| } |
| return ret; |
| } |
| |
| |
| /* end of connection_call_handlers.c */ |