Skip to content

Commit 722cc07

Browse files
authored
Content-Length is the boss (#158)
* Content-Length is the boss - Don't freak out if body stream is set when Content-Length is 0. - aws-crt-java unit tests were doing this. - Obey Content-Length when sending body, rather than trusting end-of-stream.
1 parent 0d8d673 commit 722cc07

File tree

6 files changed

+182
-56
lines changed

6 files changed

+182
-56
lines changed

include/aws/http/http.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ enum aws_http_errors {
3030
AWS_ERROR_HTTP_INVALID_STATUS_CODE,
3131
AWS_ERROR_HTTP_MISSING_BODY_STREAM,
3232
AWS_ERROR_HTTP_INVALID_BODY_STREAM,
33-
AWS_ERROR_HTTP_MISSING_BODY_HEADERS,
3433
AWS_ERROR_HTTP_CONNECTION_CLOSED,
3534
AWS_ERROR_HTTP_SWITCHED_PROTOCOLS,
3635
AWS_ERROR_HTTP_UNSUPPORTED_PROTOCOL,

include/aws/http/private/h1_encoder.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ struct aws_h1_encoder_message {
2626
/* Upon creation, the "head" (everything preceding body) is buffered here. */
2727
struct aws_byte_buf outgoing_head_buf;
2828
struct aws_input_stream *body;
29+
uint64_t content_length;
2930
bool has_connection_close_header;
3031
};
3132

@@ -41,7 +42,7 @@ struct aws_h1_encoder {
4142

4243
enum aws_h1_encoder_state state;
4344
struct aws_h1_encoder_message *message;
44-
size_t outgoing_head_progress;
45+
uint64_t progress_bytes;
4546
void *logging_id;
4647
};
4748

source/h1_encoder.c

Lines changed: 60 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
#include <aws/io/logging.h>
1919
#include <aws/io/stream.h>
2020

21+
#include <inttypes.h>
22+
2123
#define ENCODER_LOGF(level, encoder, text, ...) \
2224
AWS_LOGF_##level(AWS_LS_HTTP_STREAM, "id=%p: " text, encoder->logging_id, __VA_ARGS__)
2325
#define ENCODER_LOG(level, encoder, text) ENCODER_LOGF(level, encoder, "%s", text)
@@ -26,19 +28,16 @@
2628
* Scan headers to detect errors and determine anything we'll need to know later (ex: total length).
2729
*/
2830
static int s_scan_outgoing_headers(
31+
struct aws_h1_encoder_message *encoder_message,
2932
const struct aws_http_message *message,
3033
size_t *out_header_lines_len,
31-
bool *out_has_connection_close_header,
3234
bool body_headers_ignored,
3335
bool body_headers_forbidden) {
3436

3537
size_t total = 0;
36-
3738
bool has_body_stream = aws_http_message_get_body_stream(message);
3839
bool has_body_headers = false;
3940

40-
bool has_connection_close_header = false;
41-
4241
const size_t num_headers = aws_http_message_get_header_count(message);
4342
for (size_t i = 0; i < num_headers; ++i) {
4443
struct aws_http_header header;
@@ -49,19 +48,22 @@ static int s_scan_outgoing_headers(
4948
case AWS_HTTP_HEADER_CONNECTION: {
5049
struct aws_byte_cursor trimmed_value = aws_strutil_trim_http_whitespace(header.value);
5150
if (aws_byte_cursor_eq_c_str(&trimmed_value, "close")) {
52-
has_connection_close_header = true;
51+
encoder_message->has_connection_close_header = true;
5352
}
5453
} break;
5554
case AWS_HTTP_HEADER_CONTENT_LENGTH: {
56-
uint64_t content_length = 0;
57-
aws_strutil_read_unsigned_num(aws_strutil_trim_http_whitespace(header.value), &content_length);
58-
if (content_length > 0) {
55+
struct aws_byte_cursor trimmed_value = aws_strutil_trim_http_whitespace(header.value);
56+
if (aws_strutil_read_unsigned_num(trimmed_value, &encoder_message->content_length)) {
57+
AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=static: Invalid Content-Length");
58+
return aws_raise_error(AWS_ERROR_HTTP_INVALID_HEADER_VALUE);
59+
}
60+
if (encoder_message->content_length > 0) {
5961
has_body_headers = true;
6062
}
6163
} break;
6264
case AWS_HTTP_HEADER_TRANSFER_ENCODING:
63-
has_body_headers = true;
64-
break;
65+
AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=static: Sending of chunked messages not yet implemented");
66+
return aws_raise_error(AWS_ERROR_UNIMPLEMENTED);
6567
default:
6668
break;
6769
}
@@ -80,23 +82,16 @@ static int s_scan_outgoing_headers(
8082
}
8183

8284
if (body_headers_ignored) {
83-
/* no body should follow, no matter what the headers are */
84-
if (has_body_stream) {
85-
return aws_raise_error(AWS_ERROR_HTTP_INVALID_BODY_STREAM);
86-
}
85+
/* Don't send body, no matter what the headers are */
8786
has_body_headers = false;
87+
encoder_message->content_length = 0;
8888
}
8989

9090
if (has_body_headers && !has_body_stream) {
9191
return aws_raise_error(AWS_ERROR_HTTP_MISSING_BODY_STREAM);
9292
}
9393

94-
if (!has_body_headers && has_body_stream) {
95-
return aws_raise_error(AWS_ERROR_HTTP_MISSING_BODY_HEADERS);
96-
}
97-
9894
*out_header_lines_len = total;
99-
*out_has_connection_close_header = has_connection_close_header;
10095
return AWS_OP_SUCCESS;
10196
}
10297

@@ -151,11 +146,7 @@ int aws_h1_encoder_message_init_from_request(
151146

152147
size_t header_lines_len;
153148
err = s_scan_outgoing_headers(
154-
request,
155-
&header_lines_len,
156-
&message->has_connection_close_header,
157-
false /*body_headers_ignored*/,
158-
false /*body_headers_forbidden*/);
149+
message, request, &header_lines_len, false /*body_headers_ignored*/, false /*body_headers_forbidden*/);
159150
if (err) {
160151
goto error;
161152
}
@@ -241,12 +232,7 @@ int aws_h1_encoder_message_init_from_response(
241232
*/
242233
body_headers_ignored |= status_int == AWS_HTTP_STATUS_304_NOT_MODIFIED;
243234
bool body_headers_forbidden = status_int == AWS_HTTP_STATUS_204_NO_CONTENT || status_int / 100 == 1;
244-
err = s_scan_outgoing_headers(
245-
response,
246-
&header_lines_len,
247-
&message->has_connection_close_header,
248-
body_headers_ignored,
249-
body_headers_forbidden);
235+
err = s_scan_outgoing_headers(message, response, &header_lines_len, body_headers_ignored, body_headers_forbidden);
250236
if (err) {
251237
goto error;
252238
}
@@ -328,7 +314,7 @@ int aws_h1_encoder_start_message(
328314
encoder->logging_id = log_as_stream;
329315
encoder->message = message;
330316
encoder->state = AWS_H1_ENCODER_STATE_HEAD;
331-
encoder->outgoing_head_progress = 0;
317+
encoder->progress_bytes = 0;
332318

333319
return AWS_OP_SUCCESS;
334320
}
@@ -354,34 +340,35 @@ int aws_h1_encoder_process(struct aws_h1_encoder *encoder, struct aws_byte_buf *
354340

355341
/* Copy data from outgoing_head_buf */
356342
struct aws_byte_buf *src = &encoder->message->outgoing_head_buf;
357-
size_t src_progress = encoder->outgoing_head_progress;
343+
size_t src_progress = (size_t)encoder->progress_bytes;
358344
size_t src_remaining = src->len - src_progress;
359345
size_t transferring = src_remaining < dst_available ? src_remaining : dst_available;
360346

361347
bool success = aws_byte_buf_write(dst, src->buffer + src_progress, transferring);
362348
(void)success;
363349
AWS_ASSERT(success);
364350

365-
encoder->outgoing_head_progress += transferring;
351+
encoder->progress_bytes += transferring;
366352

367353
ENCODER_LOGF(
368354
TRACE,
369355
encoder,
370-
"Writing to message, outgoing head progress %zu/%zu.",
371-
encoder->outgoing_head_progress,
356+
"Writing to message, outgoing head progress %" PRIu64 "/%zu.",
357+
encoder->progress_bytes,
372358
encoder->message->outgoing_head_buf.len);
373359

374-
if (encoder->outgoing_head_progress == src->len) {
360+
if (encoder->progress_bytes == src->len) {
375361
/* Don't NEED to free this buffer now, but we don't need it anymore, so why not */
376362
aws_byte_buf_clean_up(&encoder->message->outgoing_head_buf);
377363

364+
encoder->progress_bytes = 0;
378365
encoder->state++;
379366
}
380367
}
381368

382369
if (encoder->state == AWS_H1_ENCODER_STATE_BODY) {
383-
if (!encoder->message->body) {
384-
ENCODER_LOG(TRACE, encoder, "No body to send.")
370+
if (!encoder->message->body || !encoder->message->content_length) {
371+
ENCODER_LOG(TRACE, encoder, "Skipping body")
385372
encoder->state++;
386373
} else {
387374
while (true) {
@@ -408,29 +395,51 @@ int aws_h1_encoder_process(struct aws_h1_encoder *encoder, struct aws_byte_buf *
408395
return AWS_OP_ERR;
409396
}
410397

411-
ENCODER_LOGF(TRACE, encoder, "Writing %zu body bytes to message", amount_read);
412-
413-
struct aws_stream_status status;
414-
err = aws_input_stream_get_status(encoder->message->body, &status);
415-
if (err) {
398+
if ((amount_read > encoder->message->content_length) ||
399+
(encoder->progress_bytes > encoder->message->content_length - amount_read)) {
416400
ENCODER_LOGF(
417-
TRACE,
401+
ERROR,
418402
encoder,
419-
"Failed to query body stream status, error %d (%s)",
420-
aws_last_error(),
421-
aws_error_name(aws_last_error()));
422-
423-
return AWS_OP_ERR;
403+
"Body stream has exceeded Content-Length: %" PRIu64,
404+
encoder->message->content_length);
405+
return aws_raise_error(AWS_ERROR_HTTP_OUTGOING_STREAM_LENGTH_INCORRECT);
424406
}
425-
if (status.is_end_of_stream) {
426-
ENCODER_LOG(TRACE, encoder, "Done sending body.");
427407

408+
encoder->progress_bytes += amount_read;
409+
410+
ENCODER_LOGF(TRACE, encoder, "Writing %zu body bytes to message", amount_read);
411+
412+
if (encoder->progress_bytes == encoder->message->content_length) {
413+
ENCODER_LOG(TRACE, encoder, "Done sending body.");
414+
encoder->progress_bytes = 0;
428415
encoder->state++;
429416
break;
430417
}
431418

432419
/* Return if user failed to write anything. Maybe their data isn't ready yet. */
433420
if (amount_read == 0) {
421+
/* Ensure we're not at end-of-stream too early */
422+
struct aws_stream_status status;
423+
err = aws_input_stream_get_status(encoder->message->body, &status);
424+
if (err) {
425+
ENCODER_LOGF(
426+
TRACE,
427+
encoder,
428+
"Failed to query body stream status, error %d (%s)",
429+
aws_last_error(),
430+
aws_error_name(aws_last_error()));
431+
432+
return AWS_OP_ERR;
433+
}
434+
if (status.is_end_of_stream) {
435+
ENCODER_LOGF(
436+
ERROR,
437+
encoder,
438+
"Reached end of body stream before Content-Length: %" PRIu64 " sent",
439+
encoder->message->content_length);
440+
return aws_raise_error(AWS_ERROR_HTTP_OUTGOING_STREAM_LENGTH_INCORRECT);
441+
}
442+
434443
ENCODER_LOG(
435444
TRACE,
436445
encoder,

source/http.c

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,6 @@ static struct aws_error_info s_errors[] = {
5858
AWS_DEFINE_ERROR_INFO_HTTP(
5959
AWS_ERROR_HTTP_INVALID_BODY_STREAM,
6060
"A body stream provided, but the message does not allow body (ex: response for HEAD Request and 304 response)"),
61-
AWS_DEFINE_ERROR_INFO_HTTP(
62-
AWS_ERROR_HTTP_MISSING_BODY_HEADERS,
63-
"Missing headers (ex: Content-Length) required to send a body."),
6461
AWS_DEFINE_ERROR_INFO_HTTP(
6562
AWS_ERROR_HTTP_CONNECTION_CLOSED,
6663
"The connection has closed or is closing."),

tests/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ add_test_case(h1_client_request_send_headers)
3939
add_test_case(h1_client_request_send_body)
4040
add_test_case(h1_client_request_send_large_body)
4141
add_test_case(h1_client_request_send_large_head)
42+
add_test_case(h1_client_request_content_length_0_ok)
43+
add_test_case(h1_client_request_content_length_too_small_is_error)
44+
add_test_case(h1_client_request_content_length_too_large_is_error)
4245
add_test_case(h1_client_request_send_multiple_in_1_io_message)
4346
add_test_case(h1_client_request_close_header_ends_connection)
4447
add_test_case(h1_client_request_close_header_with_pipelining)

0 commit comments

Comments
 (0)