aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvgeny Grin (Karlson2k) <k2k@narod.ru>2023-06-19 18:47:57 +0300
committerEvgeny Grin (Karlson2k) <k2k@narod.ru>2023-06-20 23:22:58 +0300
commitf02888d4de4a9b24c1023112119a9a3e46fcb654 (patch)
treea20863ddee504dee0d835b5dbc56116f868eb644
parent8b17c9155fdac05273867ef5aab10d08b482bbee (diff)
downloadlibmicrohttpd-f02888d4de4a9b24c1023112119a9a3e46fcb654.tar.gz
libmicrohttpd-f02888d4de4a9b24c1023112119a9a3e46fcb654.zip
Focused all read-buffer grows in a single point, related improvements.
Improved handling of low memory in connection pool. Improved handling of read buffer growing. Removed impossible conditions combinations handling.
-rw-r--r--src/microhttpd/connection.c313
1 files changed, 234 insertions, 79 deletions
diff --git a/src/microhttpd/connection.c b/src/microhttpd/connection.c
index d6c4ac1e..4d912ce8 100644
--- a/src/microhttpd/connection.c
+++ b/src/microhttpd/connection.c
@@ -57,6 +57,16 @@
57#include "mhd_assert.h" 57#include "mhd_assert.h"
58 58
59/** 59/**
60 * The reasonable length of the upload chunk "header" (the size specifier
61 * with optional chunk extension).
62 * MHD tries to keep the space in the read buffer large enough to read
63 * the chunk "header" in one step.
64 * The real "header" could be much larger, it will be handled correctly
65 * anyway, however it may require several rounds of buffer grow.
66 */
67#define MHD_CHUNK_HEADER_REASONABLE_LEN 24
68
69/**
60 * Message to transmit when http 1.1 request is received 70 * Message to transmit when http 1.1 request is received
61 */ 71 */
62#define HTTP_100_CONTINUE "HTTP/1.1 100 Continue\r\n\r\n" 72#define HTTP_100_CONTINUE "HTTP/1.1 100 Continue\r\n\r\n"
@@ -444,13 +454,13 @@
444 * minimal. 454 * minimal.
445 */ 455 */
446#ifdef HAVE_MESSAGES 456#ifdef HAVE_MESSAGES
447#define INTERNAL_ERROR \ 457#define ERROR_MSG_DATA_NOT_HANDLED_BY_APP \
448 "<html><head><title>Internal server error</title></head>" \ 458 "<html><head><title>Internal server error</title></head>" \
449 "<body>Please ask the developer of this Web server to carefully " \ 459 "<body>Please ask the developer of this Web server to carefully " \
450 "read the GNU libmicrohttpd documentation about connection "\ 460 "read the GNU libmicrohttpd documentation about connection "\
451 "management and blocking.</body></html>" 461 "management and blocking.</body></html>"
452#else 462#else
453#define INTERNAL_ERROR "" 463#define ERROR_MSG_DATA_NOT_HANDLED_BY_APP ""
454#endif 464#endif
455 465
456/** 466/**
@@ -2819,6 +2829,182 @@ transmit_error_response_len (struct MHD_Connection *connection,
2819 hd_n, hd_n_l, \ 2829 hd_n, hd_n_l, \
2820 hd_v, hd_v_l) 2830 hd_v, hd_v_l)
2821 2831
2832
2833/**
2834 * Check whether the read buffer has any upload body data ready to
2835 * be processed.
2836 * Must be called only when connection is in MHD_CONNECTION_BODY_RECEIVING
2837 * state.
2838 *
2839 * @param c the connection to check
2840 * @return 'true' if upload body data is already in the read buffer,
2841 * 'false' if no upload data is received and not processed.
2842 */
2843static bool
2844has_unprocessed_upload_body_data_in_buffer (struct MHD_Connection *c)
2845{
2846 mhd_assert (MHD_CONNECTION_BODY_RECEIVING == c->state);
2847 if (! c->rq.have_chunked_upload)
2848 return 0 != c->read_buffer_offset;
2849
2850 /* Chunked upload */
2851 mhd_assert (0 != c->rq.remaining_upload_size); /* Must not be possible in MHD_CONNECTION_BODY_RECEIVING state */
2852 if (c->rq.current_chunk_offset == c->rq.current_chunk_size)
2853 {
2854 /* 0 == c->rq.current_chunk_size: Waiting the chunk size (chunk header).
2855 0 != c->rq.current_chunk_size: Waiting for chunk-closing CRLF. */
2856 return false; /* */
2857 }
2858 return 0 != c->read_buffer_offset; /* Chunk payload data in the read buffer */
2859}
2860
2861
2862/**
2863 * Check whether enough space is available in the read buffer for the next
2864 * operation.
2865 * Handles grow of the buffer if required and error conditions (when buffer
2866 * grow is required but not possible).
2867 * Must be called only when processing the event loop states and when
2868 * reading is required for the next phase.
2869 * @param c the connection to check
2870 * @return true if connection handled successfully and enough buffer
2871 * is available,
2872 * false if not enough buffer is available and the loop's states
2873 * must be processed again as connection is in the error state.
2874 */
2875static bool
2876check_and_grow_read_buffer_space (struct MHD_Connection *c)
2877{
2878 /**
2879 * The increase of read buffer size is desirable.
2880 */
2881 bool rbuff_grow_desired;
2882 /**
2883 * The increase of read buffer size is a hard requirement.
2884 */
2885 bool rbuff_grow_required;
2886
2887 mhd_assert (0 != (MHD_EVENT_LOOP_INFO_READ & c->event_loop_info));
2888 mhd_assert (! c->discard_request);
2889
2890 rbuff_grow_required = (c->read_buffer_offset == c->read_buffer_size);
2891 if (rbuff_grow_required)
2892 rbuff_grow_desired = true;
2893 else
2894 {
2895 rbuff_grow_desired = (c->read_buffer_offset + c->daemon->pool_increment >
2896 c->read_buffer_size);
2897
2898 if ((rbuff_grow_desired) &&
2899 (MHD_CONNECTION_BODY_RECEIVING == c->state))
2900 {
2901 if (! c->rq.have_chunked_upload)
2902 {
2903 mhd_assert (MHD_SIZE_UNKNOWN != c->rq.remaining_upload_size);
2904 /* Do not grow read buffer more than necessary to process the current
2905 request. */
2906 rbuff_grow_desired =
2907 (c->rq.remaining_upload_size > c->read_buffer_size);
2908 }
2909 else
2910 {
2911 mhd_assert (MHD_SIZE_UNKNOWN == c->rq.remaining_upload_size);
2912 if (0 == c->rq.current_chunk_size)
2913 rbuff_grow_desired = /* Reading value of the next chunk size */
2914 (MHD_CHUNK_HEADER_REASONABLE_LEN >
2915 c->read_buffer_size);
2916 else
2917 {
2918 const size_t cur_chunk_left =
2919 c->rq.current_chunk_size - c->rq.current_chunk_offset;
2920 /* Do not grow read buffer more than necessary to process the current
2921 chunk with terminating CRLF. */
2922 mhd_assert (c->rq.current_chunk_offset <= c->rq.current_chunk_size);
2923 rbuff_grow_desired = ((cur_chunk_left + 2) > c->read_buffer_size);
2924 }
2925 }
2926 }
2927 }
2928
2929 if (! rbuff_grow_desired)
2930 return true; /* No need to increase the buffer */
2931
2932 if (try_grow_read_buffer (c, rbuff_grow_required))
2933 return true; /* Buffer increase succeed */
2934
2935 if (! rbuff_grow_required)
2936 return true; /* Can continue without buffer increase */
2937
2938 /* Failed to increase the read buffer size, but need to read the data
2939 from the network.
2940 No more space in the buffer, no more space to increase the buffer. */
2941
2942 /* 'PROCESS_READ' event state flag must be set only if the last application
2943 callback has processed some data. If any data is processed then some
2944 space in the read buffer must be available. */
2945 mhd_assert (0 == (MHD_EVENT_LOOP_INFO_PROCESS & c->event_loop_info));
2946
2947 if (MHD_CONNECTION_BODY_RECEIVING != c->state)
2948 {
2949 /* Receiving request line, request headers or request footers */
2950 /* TODO: Improve detection of the conditions */
2951 if (c->rq.url != NULL)
2952 transmit_error_response_static (c,
2953 MHD_HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE,
2954 REQUEST_TOO_BIG);
2955 else
2956 transmit_error_response_static (c,
2957 MHD_HTTP_URI_TOO_LONG,
2958 REQUEST_TOO_BIG);
2959 return false;
2960 }
2961
2962 /* Receiving the request body and no space left in the buffer */
2963
2964 if (! has_unprocessed_upload_body_data_in_buffer (c))
2965 {
2966 /* Full header is received and no space left for reading
2967 the request body.
2968 For chunked upload encoding: chunk-extension is too long or
2969 chunk size is encoded with excessive number of leading zeros. */
2970 /* TODO: report proper cause for the error */
2971 transmit_error_response_static (c,
2972 MHD_HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE,
2973 REQUEST_TOO_BIG);
2974 return false;
2975 }
2976
2977 /* No space left in the buffer but some upload body data can be processed
2978 and some space could be freed. */
2979 mhd_assert (! c->rq.some_payload_processed);
2980 if (0 == (c->daemon->options & MHD_USE_INTERNAL_POLLING_THREAD))
2981 {
2982 /* The application is handling processing cycles.
2983 The data may be processed later. */
2984 c->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS;
2985 return true;
2986 }
2987
2988 /* Using internal thread for sockets polling */
2989
2990 /* failed to grow the read buffer, and the
2991 client which is supposed to handle the
2992 received data in a *blocking* fashion
2993 (in this mode) did not handle the data as
2994 it was supposed to!
2995 => we would either have to do busy-waiting
2996 (on the client, which would likely fail),
2997 or if we do nothing, we would just timeout
2998 on the connection (if a timeout is even
2999 set!).
3000 Solution: we kill the connection with an error */
3001 transmit_error_response_static (c,
3002 MHD_HTTP_INTERNAL_SERVER_ERROR,
3003 ERROR_MSG_DATA_NOT_HANDLED_BY_APP);
3004 return false;
3005}
3006
3007
2822/** 3008/**
2823 * Update the 'event_loop_info' field of this connection based on the state 3009 * Update the 'event_loop_info' field of this connection based on the state
2824 * that the connection is now in. May also close the connection or 3010 * that the connection is now in. May also close the connection or
@@ -2875,31 +3061,15 @@ MHD_connection_update_event_loop_info (struct MHD_Connection *connection)
2875 { 3061 {
2876 case MHD_CONNECTION_INIT: 3062 case MHD_CONNECTION_INIT:
2877 case MHD_CONNECTION_REQ_LINE_RECEIVING: 3063 case MHD_CONNECTION_REQ_LINE_RECEIVING:
3064 connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ;
3065 break;
2878 case MHD_CONNECTION_REQ_LINE_RECEIVED: 3066 case MHD_CONNECTION_REQ_LINE_RECEIVED:
3067 mhd_assert (0);
3068 break;
2879 case MHD_CONNECTION_REQ_HEADERS_RECEIVING: 3069 case MHD_CONNECTION_REQ_HEADERS_RECEIVING:
2880 /* while reading headers, we always grow the 3070 connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ;
2881 read buffer if needed, no size-check required */
2882 if ( (connection->read_buffer_offset == connection->read_buffer_size) &&
2883 (! try_grow_read_buffer (connection, true)) )
2884 {
2885 if (connection->rq.url != NULL)
2886 transmit_error_response_static (connection,
2887 MHD_HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE,
2888 REQUEST_TOO_BIG);
2889 else
2890 transmit_error_response_static (connection,
2891 MHD_HTTP_URI_TOO_LONG,
2892 REQUEST_TOO_BIG);
2893 continue;
2894 }
2895 if (! connection->discard_request)
2896 connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ;
2897 else
2898 connection->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS;
2899 break; 3071 break;
2900 case MHD_CONNECTION_HEADERS_RECEIVED: 3072 case MHD_CONNECTION_HEADERS_RECEIVED:
2901 mhd_assert (0);
2902 break;
2903 case MHD_CONNECTION_HEADERS_PROCESSED: 3073 case MHD_CONNECTION_HEADERS_PROCESSED:
2904 mhd_assert (0); 3074 mhd_assert (0);
2905 break; 3075 break;
@@ -2907,54 +3077,37 @@ MHD_connection_update_event_loop_info (struct MHD_Connection *connection)
2907 connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; 3077 connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE;
2908 break; 3078 break;
2909 case MHD_CONNECTION_BODY_RECEIVING: 3079 case MHD_CONNECTION_BODY_RECEIVING:
2910 if (connection->read_buffer_offset == connection->read_buffer_size) 3080 if ((connection->rq.some_payload_processed) &&
3081 has_unprocessed_upload_body_data_in_buffer (connection))
2911 { 3082 {
2912 const bool internal_poll = (0 != (connection->daemon->options 3083 /* Some data was processed, the buffer must have some free space */
2913 & MHD_USE_INTERNAL_POLLING_THREAD)); 3084 mhd_assert (connection->read_buffer_offset < \
2914 if ( (! try_grow_read_buffer (connection, true)) && 3085 connection->read_buffer_size);
2915 internal_poll) 3086 if (! connection->rq.have_chunked_upload)
2916 { 3087 {
2917 /* failed to grow the read buffer, and the 3088 /* Not a chunked upload. Do not read more than necessary to
2918 client which is supposed to handle the 3089 process the current request. */
2919 received data in a *blocking* fashion 3090 if (connection->rq.remaining_upload_size >=
2920 (in this mode) did not handle the data as 3091 connection->read_buffer_offset)
2921 it was supposed to! 3092 connection->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS;
2922 => we would either have to do busy-waiting 3093 else
2923 (on the client, which would likely fail), 3094 connection->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS_READ;
2924 or if we do nothing, we would just timeout 3095 }
2925 on the connection (if a timeout is even 3096 else
2926 set!). 3097 {
2927 Solution: we kill the connection with an error */ 3098 /* Chunked upload. The size of the current request is unknown.
2928 transmit_error_response_static (connection, 3099 Continue reading as the space in the read buffer is available. */
2929 MHD_HTTP_INTERNAL_SERVER_ERROR, 3100 connection->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS_READ;
2930 INTERNAL_ERROR);
2931 continue;
2932 } 3101 }
2933 } 3102 }
2934 if (connection->discard_request)
2935 connection->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS;
2936 else if (connection->read_buffer_offset == connection->read_buffer_size)
2937 connection->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS;
2938 else if (0 == connection->read_buffer_offset)
2939 connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ;
2940 else if (connection->rq.some_payload_processed)
2941 connection->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS_READ;
2942 else 3103 else
2943 connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ; 3104 connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ;
2944 break; 3105 break;
2945 case MHD_CONNECTION_BODY_RECEIVED: 3106 case MHD_CONNECTION_BODY_RECEIVED:
3107 mhd_assert (0);
3108 break;
2946 case MHD_CONNECTION_FOOTERS_RECEIVING: 3109 case MHD_CONNECTION_FOOTERS_RECEIVING:
2947 /* while reading footers, we always grow the
2948 read buffer if needed, no size-check required */
2949 if (connection->read_closed)
2950 {
2951 CONNECTION_CLOSE_ERROR (connection,
2952 NULL);
2953 continue;
2954 }
2955 connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ; 3110 connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ;
2956 /* transition to FOOTERS_RECEIVED
2957 happens in read handler */
2958 break; 3111 break;
2959 case MHD_CONNECTION_FOOTERS_RECEIVED: 3112 case MHD_CONNECTION_FOOTERS_RECEIVED:
2960 mhd_assert (0); 3113 mhd_assert (0);
@@ -2972,18 +3125,18 @@ MHD_connection_update_event_loop_info (struct MHD_Connection *connection)
2972 case MHD_CONNECTION_HEADERS_SENT: 3125 case MHD_CONNECTION_HEADERS_SENT:
2973 mhd_assert (0); 3126 mhd_assert (0);
2974 break; 3127 break;
2975 case MHD_CONNECTION_NORMAL_BODY_READY:
2976 connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE;
2977 break;
2978 case MHD_CONNECTION_NORMAL_BODY_UNREADY: 3128 case MHD_CONNECTION_NORMAL_BODY_UNREADY:
2979 connection->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS; 3129 connection->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS;
2980 break; 3130 break;
2981 case MHD_CONNECTION_CHUNKED_BODY_READY: 3131 case MHD_CONNECTION_NORMAL_BODY_READY:
2982 connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; 3132 connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE;
2983 break; 3133 break;
2984 case MHD_CONNECTION_CHUNKED_BODY_UNREADY: 3134 case MHD_CONNECTION_CHUNKED_BODY_UNREADY:
2985 connection->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS; 3135 connection->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS;
2986 break; 3136 break;
3137 case MHD_CONNECTION_CHUNKED_BODY_READY:
3138 connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE;
3139 break;
2987 case MHD_CONNECTION_CHUNKED_BODY_SENT: 3140 case MHD_CONNECTION_CHUNKED_BODY_SENT:
2988 mhd_assert (0); 3141 mhd_assert (0);
2989 break; 3142 break;
@@ -3004,7 +3157,17 @@ MHD_connection_update_event_loop_info (struct MHD_Connection *connection)
3004 default: 3157 default:
3005 mhd_assert (0); 3158 mhd_assert (0);
3006 } 3159 }
3007 break; 3160
3161 if (0 != (MHD_EVENT_LOOP_INFO_READ & connection->event_loop_info))
3162 {
3163 /* Check whether the space is available to receive data */
3164 if (! check_and_grow_read_buffer_space (connection))
3165 {
3166 mhd_assert (connection->discard_request);
3167 continue;
3168 }
3169 }
3170 break; /* Everything was processed. */
3008 } 3171 }
3009} 3172}
3010 3173
@@ -3555,8 +3718,6 @@ process_request_body (struct MHD_Connection *connection)
3555 size_t left_unprocessed; 3718 size_t left_unprocessed;
3556 size_t processed_size; 3719 size_t processed_size;
3557 3720
3558 connection->rq.some_payload_processed = false;
3559
3560 instant_retry = false; 3721 instant_retry = false;
3561 if (connection->rq.have_chunked_upload) 3722 if (connection->rq.have_chunked_upload)
3562 { 3723 {
@@ -3756,9 +3917,9 @@ process_request_body (struct MHD_Connection *connection)
3756 if (left_unprocessed > to_be_processed) 3917 if (left_unprocessed > to_be_processed)
3757 MHD_PANIC (_ ("libmicrohttpd API violation.\n")); 3918 MHD_PANIC (_ ("libmicrohttpd API violation.\n"));
3758 3919
3759 if (left_unprocessed != to_be_processed) 3920 connection->rq.some_payload_processed =
3760 /* Something was processed by the application. */ 3921 (left_unprocessed != to_be_processed);
3761 connection->rq.some_payload_processed = true; 3922
3762 if (0 != left_unprocessed) 3923 if (0 != left_unprocessed)
3763 { 3924 {
3764 instant_retry = false; /* client did not process everything */ 3925 instant_retry = false; /* client did not process everything */
@@ -5461,16 +5622,10 @@ MHD_connection_handle_read (struct MHD_Connection *connection,
5461 } 5622 }
5462#endif /* HTTPS_SUPPORT */ 5623#endif /* HTTPS_SUPPORT */
5463 5624
5464 /* make sure "read" has a reasonable number of bytes 5625 mhd_assert (NULL != connection->read_buffer);
5465 in buffer to use per system call (if possible) */
5466 if (connection->read_buffer_offset + connection->daemon->pool_increment >
5467 connection->read_buffer_size)
5468 try_grow_read_buffer (connection,
5469 (connection->read_buffer_size ==
5470 connection->read_buffer_offset));
5471
5472 if (connection->read_buffer_size == connection->read_buffer_offset) 5626 if (connection->read_buffer_size == connection->read_buffer_offset)
5473 return; /* No space for receiving data. */ 5627 return; /* No space for receiving data. */
5628
5474 bytes_read = connection->recv_cls (connection, 5629 bytes_read = connection->recv_cls (connection,
5475 &connection->read_buffer 5630 &connection->read_buffer
5476 [connection->read_buffer_offset], 5631 [connection->read_buffer_offset],