Skip to content

Commit 246de20

Browse files
committed
Fix 2xx status check, add indeterminate-length unit tests, minor cleanups
- Fix code_val/200==1 to code_val/100==2 (matched 200-399 instead of 200-299) - Add unit tests for indeterminate-length response body: - h1_client_response_indeterminate_length_body (multi-chunk body) - h1_client_response_indeterminate_length_empty_body (empty body edge case) - h1_client_response_204_no_indeterminate_length (negative case) - Fix typo: 'response lien' -> 'response line' - Reset body_headers_ignored_on_2xx in s_reset_state() for consistency - Fix inconsistent Server header in mock server ('echo-server' -> 'crt-local-server')
1 parent fd61a12 commit 246de20

4 files changed

Lines changed: 131 additions & 4 deletions

File tree

source/h1_decoder.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ static void s_reset_state(struct aws_h1_decoder *decoder) {
217217
decoder->doing_trailers = false;
218218
decoder->is_done = false;
219219
decoder->body_headers_ignored = false;
220+
decoder->body_headers_ignored_on_2xx = false;
220221
decoder->body_headers_forbidden = false;
221222
decoder->content_length_received = false;
222223
decoder->response_body_indeterminate_length = false;
@@ -382,7 +383,7 @@ static int s_linestate_header(struct aws_h1_decoder *decoder, struct aws_byte_cu
382383
!decoder->is_decoding_requests && !decoder->content_length_received &&
383384
!decoder->body_headers_forbidden && !decoder->body_headers_ignored_on_2xx) {
384385
/* RFC-7230 3.3.3: If a message is received without Transfer-Encoding and without Content-Length and NOT
385-
* determined by the response lien to have no body, then the message body length is determined by
386+
* determined by the response line to have no body, then the message body length is determined by
386387
* reading the stream until connection closure. Note: This only applies to responses. A request without
387388
* Content-Length or Transfer-Encoding has no message body (per RFC 7230 section 3.3.3). */
388389
/* The failure response for CONNECT with body is undefined, and since the client will handle the CONNECT
@@ -715,7 +716,7 @@ static int s_linestate_response(struct aws_h1_decoder *decoder, struct aws_byte_
715716
/* RFC-7230 section 3.3 Message Body */
716717
decoder->body_headers_ignored |= code_val == AWS_HTTP_STATUS_CODE_304_NOT_MODIFIED;
717718
if (decoder->body_headers_ignored_on_2xx) {
718-
decoder->body_headers_ignored |= code_val / 200 == 1;
719+
decoder->body_headers_ignored |= code_val / 100 == 2;
719720
}
720721
decoder->body_headers_forbidden = code_val == AWS_HTTP_STATUS_CODE_204_NO_CONTENT || code_val / 100 == 1;
721722

tests/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,9 @@ add_test_case(h1_client_connection_close_before_request_finishes_with_buffer)
157157
add_test_case(h1_client_connection_close_before_request_finishes_with_buffer_incomplete_response)
158158
add_test_case(h1_client_connection_close_before_request_finishes_with_buffer_force_shutdown)
159159
add_test_case(h1_client_connection_close_before_request_finishes_with_buffer_stream_cancel)
160+
add_test_case(h1_client_response_indeterminate_length_body)
161+
add_test_case(h1_client_response_indeterminate_length_empty_body)
162+
add_test_case(h1_client_response_204_no_indeterminate_length)
160163

161164
add_test_case(strutil_trim_http_whitespace)
162165
add_test_case(strutil_is_http_token)

tests/mock_server/h11mock_server.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ async def send_response(wrapper, request):
198198
# Build the raw HTTP response
199199
response_lines = [
200200
b"HTTP/1.1 200 OK",
201-
b"Server: echo-server",
201+
b"Server: crt-local-server",
202202
b"Content-Type: text/plain; charset=utf-8",
203203
b"Connection: close",
204204
b"", # Empty line separates headers from body
@@ -212,7 +212,7 @@ async def send_response(wrapper, request):
212212
# We simulate the response flow through h11's state machine without actually sending bytes
213213
wrapper.info("Updating h11 state to MUST_CLOSE after raw response with Connection: close")
214214
headers = [
215-
("Server", "echo-server"),
215+
("Server", "crt-local-server"),
216216
("Content-Type", "text/plain; charset=utf-8"),
217217
("Connection", "close"),
218218
]

tests/test_h1_client.c

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5141,3 +5141,126 @@ H1_CLIENT_TEST_CASE(h1_client_connection_close_before_request_finishes_with_buff
51415141
(void)ctx;
51425142
return s_h1_client_connection_close_before_request_finishes_with_buffer_force_shutdown_helper(allocator, false);
51435143
}
5144+
5145+
/* Test: Response with no Content-Length or Transfer-Encoding has its body determined by connection closure */
5146+
H1_CLIENT_TEST_CASE(h1_client_response_indeterminate_length_body) {
5147+
(void)ctx;
5148+
struct tester tester;
5149+
ASSERT_SUCCESS(s_tester_init(&tester, allocator));
5150+
5151+
/* send request */
5152+
struct aws_http_message *request = s_new_default_get_request(allocator);
5153+
5154+
struct client_stream_tester stream_tester;
5155+
ASSERT_SUCCESS(s_stream_tester_init(&stream_tester, &tester, request));
5156+
5157+
testing_channel_drain_queued_tasks(&tester.testing_channel);
5158+
aws_http_message_destroy(request);
5159+
5160+
/* send response with no Content-Length or Transfer-Encoding */
5161+
ASSERT_SUCCESS(testing_channel_push_read_str(
5162+
&tester.testing_channel,
5163+
"HTTP/1.1 200 OK\r\n"
5164+
"Connection: close\r\n"
5165+
"\r\n"
5166+
"hello "));
5167+
5168+
testing_channel_drain_queued_tasks(&tester.testing_channel);
5169+
5170+
/* stream should NOT be complete yet - waiting for connection close */
5171+
ASSERT_FALSE(stream_tester.complete);
5172+
ASSERT_UINT_EQUALS(6, stream_tester.response_body.len);
5173+
5174+
/* send more body data */
5175+
ASSERT_SUCCESS(testing_channel_push_read_str(&tester.testing_channel, "world"));
5176+
testing_channel_drain_queued_tasks(&tester.testing_channel);
5177+
5178+
ASSERT_FALSE(stream_tester.complete);
5179+
ASSERT_UINT_EQUALS(11, stream_tester.response_body.len);
5180+
5181+
/* close the connection - this should complete the stream */
5182+
aws_channel_shutdown(tester.testing_channel.channel, AWS_ERROR_SUCCESS);
5183+
testing_channel_drain_queued_tasks(&tester.testing_channel);
5184+
5185+
ASSERT_TRUE(stream_tester.complete);
5186+
ASSERT_INT_EQUALS(AWS_ERROR_SUCCESS, stream_tester.on_complete_error_code);
5187+
ASSERT_INT_EQUALS(200, stream_tester.response_status);
5188+
ASSERT_TRUE(aws_byte_buf_eq_c_str(&stream_tester.response_body, "hello world"));
5189+
5190+
/* clean up */
5191+
client_stream_tester_clean_up(&stream_tester);
5192+
ASSERT_SUCCESS(s_tester_clean_up(&tester));
5193+
return AWS_OP_SUCCESS;
5194+
}
5195+
5196+
/* Test: Response with no Content-Length and empty body - connection closes right after headers */
5197+
H1_CLIENT_TEST_CASE(h1_client_response_indeterminate_length_empty_body) {
5198+
(void)ctx;
5199+
struct tester tester;
5200+
ASSERT_SUCCESS(s_tester_init(&tester, allocator));
5201+
5202+
struct aws_http_message *request = s_new_default_get_request(allocator);
5203+
5204+
struct client_stream_tester stream_tester;
5205+
ASSERT_SUCCESS(s_stream_tester_init(&stream_tester, &tester, request));
5206+
5207+
testing_channel_drain_queued_tasks(&tester.testing_channel);
5208+
aws_http_message_destroy(request);
5209+
5210+
/* send response headers only, no body, no Content-Length */
5211+
ASSERT_SUCCESS(testing_channel_push_read_str(
5212+
&tester.testing_channel,
5213+
"HTTP/1.1 200 OK\r\n"
5214+
"\r\n"));
5215+
5216+
testing_channel_drain_queued_tasks(&tester.testing_channel);
5217+
5218+
/* stream should NOT be complete yet */
5219+
ASSERT_FALSE(stream_tester.complete);
5220+
5221+
/* close connection */
5222+
aws_channel_shutdown(tester.testing_channel.channel, AWS_ERROR_SUCCESS);
5223+
testing_channel_drain_queued_tasks(&tester.testing_channel);
5224+
5225+
ASSERT_TRUE(stream_tester.complete);
5226+
ASSERT_INT_EQUALS(AWS_ERROR_SUCCESS, stream_tester.on_complete_error_code);
5227+
ASSERT_INT_EQUALS(200, stream_tester.response_status);
5228+
ASSERT_UINT_EQUALS(0, stream_tester.response_body.len);
5229+
5230+
client_stream_tester_clean_up(&stream_tester);
5231+
ASSERT_SUCCESS(s_tester_clean_up(&tester));
5232+
return AWS_OP_SUCCESS;
5233+
}
5234+
5235+
/* Test: 1xx and 204 responses must NOT enter indeterminate-length mode (body_headers_forbidden) */
5236+
H1_CLIENT_TEST_CASE(h1_client_response_204_no_indeterminate_length) {
5237+
(void)ctx;
5238+
struct tester tester;
5239+
ASSERT_SUCCESS(s_tester_init(&tester, allocator));
5240+
5241+
struct aws_http_message *request = s_new_default_get_request(allocator);
5242+
5243+
struct client_stream_tester stream_tester;
5244+
ASSERT_SUCCESS(s_stream_tester_init(&stream_tester, &tester, request));
5245+
5246+
testing_channel_drain_queued_tasks(&tester.testing_channel);
5247+
aws_http_message_destroy(request);
5248+
5249+
/* 204 with no Content-Length should complete immediately, not wait for connection close */
5250+
ASSERT_SUCCESS(testing_channel_push_read_str(
5251+
&tester.testing_channel,
5252+
"HTTP/1.1 204 No Content\r\n"
5253+
"\r\n"));
5254+
5255+
testing_channel_drain_queued_tasks(&tester.testing_channel);
5256+
5257+
/* stream should be complete immediately - 204 has no body */
5258+
ASSERT_TRUE(stream_tester.complete);
5259+
ASSERT_INT_EQUALS(AWS_ERROR_SUCCESS, stream_tester.on_complete_error_code);
5260+
ASSERT_INT_EQUALS(204, stream_tester.response_status);
5261+
ASSERT_UINT_EQUALS(0, stream_tester.response_body.len);
5262+
5263+
client_stream_tester_clean_up(&stream_tester);
5264+
ASSERT_SUCCESS(s_tester_clean_up(&tester));
5265+
return AWS_OP_SUCCESS;
5266+
}

0 commit comments

Comments
 (0)