diff --git a/source/connection.c b/source/connection.c index 152d54678..357c8fbd9 100644 --- a/source/connection.c +++ b/source/connection.c @@ -68,8 +68,8 @@ static struct aws_http_connection *s_connection_new( AWS_LS_HTTP_CONNECTION, "static: Failed to insert slot into channel %p, error %d (%s).", (void *)channel, - err, - aws_error_name(err)); + aws_last_error(), + aws_error_name(aws_last_error())); goto error; } @@ -143,8 +143,8 @@ static struct aws_http_connection *s_connection_new( AWS_LS_HTTP_CONNECTION, "static: Failed to setting HTTP handler into slot on channel %p, error %d (%s).", (void *)channel, - err, - aws_error_name(err)); + aws_last_error(), + aws_error_name(aws_last_error())); goto error; } @@ -527,8 +527,8 @@ int aws_http_client_connect(const struct aws_http_client_connection_options *opt AWS_LOGF_ERROR( AWS_LS_HTTP_CONNECTION, "static: Failed to initiate socket channel for new client connection, error %d (%s).", - err, - aws_error_name(err)); + aws_last_error(), + aws_error_name(aws_last_error())); goto error; } diff --git a/source/connection_h1.c b/source/connection_h1.c index c739a3abb..02f23acc0 100644 --- a/source/connection_h1.c +++ b/source/connection_h1.c @@ -166,7 +166,7 @@ enum stream_type { enum stream_outgoing_state { STREAM_OUTGOING_STATE_HEAD, - STREAM_OUTGOING_STATE_BODY, /* TODO: support 100-continue */ + STREAM_OUTGOING_STATE_BODY, STREAM_OUTGOING_STATE_DONE, }; @@ -278,7 +278,7 @@ static int s_stream_scan_outgoing_headers( size_t num_headers, size_t *out_header_lines_len) { - *out_header_lines_len = 0; + size_t total = 0; for (size_t i = 0; i < num_headers; ++i) { struct aws_http_header header = header_array[i]; @@ -288,20 +288,25 @@ static int s_stream_scan_outgoing_headers( if (header.name.len > 0) { name_enum = aws_http_str_to_header_name(header.name); } else { - AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "static: No name set for header[%zu].", i); + AWS_LOGF_ERROR( + AWS_LS_HTTP_CONNECTION, + "id=%p: Failed to create stream, no name set for header[%zu].", + (void *)stream->base.owning_connection, + i); return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); } switch (name_enum) { case AWS_HTTP_HEADER_CONTENT_LENGTH: case AWS_HTTP_HEADER_TRANSFER_ENCODING: - /* TODO: actually process the values in these headers*/ stream->has_outgoing_body = true; if (!stream->base.stream_outgoing_body) { AWS_LOGF_ERROR( - AWS_LS_HTTP_STREAM, - "static: '" PRInSTR "' header specified, but body-streaming callback is not set.", + AWS_LS_HTTP_CONNECTION, + "id=%p: Failed to create stream, '" PRInSTR + "' header specified but body-streaming callback is not set.", + (void *)stream->base.owning_connection, AWS_BYTE_CURSOR_PRI(header.name)); return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); @@ -312,11 +317,22 @@ static int s_stream_scan_outgoing_headers( } /* header-line: "{name}: {value}\r\n" */ - *out_header_lines_len += header.name.len + 2 + header.value.len + 2; - - /* TODO: check for overflows anywhere we do addition/subtraction? */ + int err = 0; + err |= aws_add_size_checked(header.name.len, total, &total); + err |= aws_add_size_checked(header.value.len, total, &total); + err |= aws_add_size_checked(4, total, &total); /* ": " + "\r\n" */ + if (err) { + AWS_LOGF_ERROR( + AWS_LS_HTTP_CONNECTION, + "id=%p: Failed to create stream, header size calculation produced error %d (%s)'", + (void *)stream->base.owning_connection, + aws_last_error(), + aws_error_name(aws_last_error())); + return AWS_OP_ERR; + } } + *out_header_lines_len = total; return AWS_OP_SUCCESS; } @@ -339,7 +355,10 @@ static void s_write_headers(struct aws_byte_buf *dst, const struct aws_http_head struct aws_http_stream *s_new_client_request_stream(const struct aws_http_request_options *options) { if (options->uri.len == 0 || options->method.len == 0) { - AWS_LOGF_ERROR(AWS_LS_HTTP_CONNECTION, "static: Invalid options, cannot create client request."); + AWS_LOGF_ERROR( + AWS_LS_HTTP_CONNECTION, + "id=%p: Cannot create client request, options are invalid.", + (void *)options->client_connection); aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); return NULL; } @@ -368,24 +387,48 @@ struct aws_http_stream *s_new_client_request_stream(const struct aws_http_reques /** * Calculate total size needed for outgoing_head_buffer, then write to buffer. - * The head will look like this: - * request-line: "{method} {uri} {version}\r\n" - * header-line: "{name}: {value}\r\n" - * head-end: "\r\n" */ - size_t request_line_len = options->method.len + 1 + options->uri.len + 1 + version.len + 2; + size_t header_lines_len; int err = s_stream_scan_outgoing_headers(stream, options->header_array, options->num_headers, &header_lines_len); if (err) { - goto error; + /* errors already logged by scan_outgoing_headers() function */ + goto error_scanning_headers; } + /* request-line: "{method} {uri} {version}\r\n" */ + size_t request_line_len = 4; /* 2 spaces + "\r\n" */ + err |= aws_add_size_checked(options->method.len, request_line_len, &request_line_len); + err |= aws_add_size_checked(options->uri.len, request_line_len, &request_line_len); + err |= aws_add_size_checked(version.len, request_line_len, &request_line_len); + + /* head-end: "\r\n" */ size_t head_end_len = 2; - size_t head_total_len = request_line_len + header_lines_len + head_end_len; + size_t head_total_len = request_line_len; + err |= aws_add_size_checked(header_lines_len, head_total_len, &head_total_len); + err |= aws_add_size_checked(head_end_len, head_total_len, &head_total_len); + + if (err) { + AWS_LOGF_ERROR( + AWS_LS_HTTP_CONNECTION, + "id=%p: Failed to create request, size calculation had error %d (%s).", + (void *)options->client_connection, + aws_last_error(), + aws_error_name(aws_last_error())); + goto error_calculating_size; + } + err = aws_byte_buf_init(&stream->outgoing_head_buf, stream->base.alloc, head_total_len); if (err) { - goto error; + AWS_LOGF_ERROR( + AWS_LS_HTTP_CONNECTION, + "id=%p: Failed to create request, buffer initialization had error %d (%s).", + (void *)options->client_connection, + aws_last_error(), + aws_error_name(aws_last_error())); + + goto error_initializing_buf; } bool wrote_all = true; @@ -430,18 +473,19 @@ struct aws_http_stream *s_new_client_request_stream(const struct aws_http_reques if (is_shutting_down) { AWS_LOGF_ERROR( - AWS_LS_HTTP_STREAM, - "static: Connection is closed, cannot create " PRInSTR " request.", - AWS_BYTE_CURSOR_PRI(options->method)); + AWS_LS_HTTP_CONNECTION, + "id=%p: Connection is closed, cannot create request.", + (void *)options->client_connection); aws_raise_error(AWS_ERROR_HTTP_CONNECTION_CLOSED); - goto error; + goto error_connection_closed; } AWS_LOGF_DEBUG( AWS_LS_HTTP_STREAM, - "id=%p: Created client request: " PRInSTR " " PRInSTR " " PRInSTR, + "id=%p: Created client request on connection=%p: " PRInSTR " " PRInSTR " " PRInSTR, (void *)&stream->base, + (void *)options->client_connection, AWS_BYTE_CURSOR_PRI(options->method), AWS_BYTE_CURSOR_PRI(options->uri), AWS_BYTE_CURSOR_PRI(aws_http_version_to_str(connection->base.http_version))); @@ -453,8 +497,11 @@ struct aws_http_stream *s_new_client_request_stream(const struct aws_http_reques return &stream->base; -error: +error_connection_closed: aws_byte_buf_clean_up(&stream->outgoing_head_buf); +error_initializing_buf: +error_calculating_size: +error_scanning_headers: aws_mem_release(stream->base.alloc, stream); return NULL; } @@ -472,7 +519,14 @@ static void s_stream_destroy(struct aws_http_stream *stream_base) { static void s_update_window_action(struct h1_connection *connection, size_t increment_size) { int err = aws_channel_slot_increment_read_window(connection->base.channel_slot, increment_size); if (err) { - /* TODO: log warning OR remove error code from aws_channel_slot_increment_read_window */ + AWS_LOGF_ERROR( + AWS_LS_HTTP_CONNECTION, + "id=%p: Failed to increment read window, error %d (%s). Closing connection.", + (void *)&connection->base, + aws_last_error(), + aws_error_name(aws_last_error())); + + s_shutdown_connection(connection, aws_last_error()); } } @@ -856,8 +910,8 @@ static void s_outgoing_stream_task(struct aws_channel_task *task, void *arg, enu AWS_LS_HTTP_CONNECTION, "id=%p: Failed to send message up channel, error %d (%s). Closing connection.", (void *)&connection->base, - err, - aws_error_name(err)); + aws_last_error(), + aws_error_name(aws_last_error())); goto error; } @@ -914,8 +968,6 @@ static int s_decoder_on_request( AWS_BYTE_CURSOR_PRI(*method_str), AWS_BYTE_CURSOR_PRI(*uri)); - /* TODO: Limit on lengths of incoming data https://httpwg.org/specs/rfc7230.html#attack.protocol.element.length */ - /* Copy strings to internal buffer */ struct aws_byte_buf *storage_buf = &incoming_stream->incoming_storage_buf; assert(storage_buf->capacity == 0); @@ -976,8 +1028,6 @@ static int s_decoder_on_header(const struct aws_http_decoded_header *header, voi AWS_BYTE_CURSOR_PRI(header->name_data), AWS_BYTE_CURSOR_PRI(header->value_data)); - /* TODO? how to support trailing headers? distinct cb? invoke same cb again? */ - if (incoming_stream->base.on_incoming_headers) { struct aws_http_header deliver = { .name = header->name_data, @@ -1263,8 +1313,8 @@ static int s_handler_process_read_message( AWS_LS_HTTP_CONNECTION, "id=%p: Message processing failed, error %d (%s). Closing connection.", (void *)&connection->base, - err, - aws_error_name(err)); + aws_last_error(), + aws_error_name(aws_last_error())); goto error; } @@ -1282,8 +1332,8 @@ static int s_handler_process_read_message( AWS_LS_HTTP_CONNECTION, "id=%p: Failed to increment read window, error %d (%s). Closing connection.", (void *)&connection->base, - err, - aws_error_name(err)); + aws_last_error(), + aws_error_name(aws_last_error())); goto error; } diff --git a/source/decode.c b/source/decode.c index 7d2500618..fe7c03646 100644 --- a/source/decode.c +++ b/source/decode.c @@ -412,8 +412,10 @@ static int s_linestate_chunk_size(struct aws_http_decoder *decoder, struct aws_b AWS_ZERO_STRUCT(size); if (!aws_byte_cursor_next_split(&input, ';', &size)) { AWS_LOGF_ERROR( + AWS_LS_HTTP_STREAM, "id=%p: Incoming chunk is invalid, first line is malformed.", decoder->logging_id); + AWS_LOGF_DEBUG( AWS_LS_HTTP_STREAM, - "id=%p: Incoming chunk is invalid, first line is malformed: '" PRInSTR "'", + "id=%p: Bad chunk line is: '" PRInSTR "'", decoder->logging_id, AWS_BYTE_CURSOR_PRI(input)); @@ -422,9 +424,10 @@ static int s_linestate_chunk_size(struct aws_http_decoder *decoder, struct aws_b int err = s_read_size_hex(size, &decoder->chunk_size); if (err) { - AWS_LOGF_ERROR( + AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=%p: Failed to parse size of incoming chunk.", decoder->logging_id); + AWS_LOGF_DEBUG( AWS_LS_HTTP_STREAM, - "id=%p: Failed to parse size of incoming chunk: '" PRInSTR "'", + "id=%p: Bad chunk size is: '" PRInSTR "'", decoder->logging_id, AWS_BYTE_CURSOR_PRI(size)); @@ -491,21 +494,17 @@ static int s_linestate_header(struct aws_http_decoder *decoder, struct aws_byte_ struct aws_byte_cursor splits[2]; err = s_cursor_split_first_n_times(input, ':', splits, 2); /* value may contain more colons */ if (err) { - AWS_LOGF_ERROR( - AWS_LS_HTTP_STREAM, - "id=%p: Invalid incoming header, missing colon: '" PRInSTR "'", - decoder->logging_id, - AWS_BYTE_CURSOR_PRI(input)); + AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=%p: Invalid incoming header, missing colon.", decoder->logging_id); + AWS_LOGF_DEBUG( + AWS_LS_HTTP_STREAM, "id=%p: Bad header is: '" PRInSTR "'", decoder->logging_id, AWS_BYTE_CURSOR_PRI(input)); return aws_raise_error(AWS_ERROR_HTTP_PARSE); } struct aws_byte_cursor name = splits[0]; if (name.len == 0) { - AWS_LOGF_ERROR( - AWS_LS_HTTP_STREAM, - "id=%p: Invalid incoming header, name is empty: '" PRInSTR "'", - decoder->logging_id, - AWS_BYTE_CURSOR_PRI(input)); + AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=%p: Invalid incoming header, name is empty.", decoder->logging_id); + AWS_LOGF_DEBUG( + AWS_LS_HTTP_STREAM, "id=%p: Bad header is: '" PRInSTR "'", decoder->logging_id, AWS_BYTE_CURSOR_PRI(input)); return aws_raise_error(AWS_ERROR_HTTP_PARSE); } @@ -530,7 +529,11 @@ static int s_linestate_header(struct aws_http_decoder *decoder, struct aws_byte_ if (s_read_size(header.value_data, &decoder->content_length) != AWS_OP_SUCCESS) { AWS_LOGF_ERROR( AWS_LS_HTTP_STREAM, - "id=%p: Incoming content-length header has invalid value: '" PRInSTR "'", + "id=%p: Incoming content-length header has invalid value.", + decoder->logging_id); + AWS_LOGF_DEBUG( + AWS_LS_HTTP_STREAM, + "id=%p: Bad content-length value is: '" PRInSTR "'", decoder->logging_id, AWS_BYTE_CURSOR_PRI(header.value_data)); return AWS_OP_ERR; @@ -578,7 +581,11 @@ static int s_linestate_header(struct aws_http_decoder *decoder, struct aws_byte_ } else if (coding.len > 0) { AWS_LOGF_ERROR( AWS_LS_HTTP_STREAM, - "id=%p: Incoming transfer-encoding header lists unrecognized coding: '" PRInSTR "'", + "id=%p: Incoming transfer-encoding header lists unrecognized coding.", + decoder->logging_id); + AWS_LOGF_DEBUG( + AWS_LS_HTTP_STREAM, + "id=%p: Unrecognized coding is: '" PRInSTR "'", decoder->logging_id, AWS_BYTE_CURSOR_PRI(coding)); return aws_raise_error(AWS_ERROR_HTTP_PARSE); @@ -590,16 +597,17 @@ static int s_linestate_header(struct aws_http_decoder *decoder, struct aws_byte_ if ((prev_flags & AWS_HTTP_TRANSFER_ENCODING_CHUNKED) && (decoder->transfer_encoding != prev_flags)) { AWS_LOGF_ERROR( AWS_LS_HTTP_STREAM, - "id=%p: Incoming transfer-encoding header lists another coding '" PRInSTR - "' after 'chunked', this is illegal.", + "id=%p: Incoming transfer-encoding header lists a coding after 'chunked', this is illegal.", + decoder->logging_id); + AWS_LOGF_DEBUG( + AWS_LS_HTTP_STREAM, + "id=%p: Misplaced coding is '" PRInSTR "'", decoder->logging_id, AWS_BYTE_CURSOR_PRI(coding)); return aws_raise_error(AWS_ERROR_HTTP_PARSE); } } - /* TODO: need to preserve order of codings. what happens if there are duplicates? */ - /* TODO: deal with body of indeterminate length, marking it as successful when connection is closed: * * A response that has neither chunked transfer coding nor Content-Length is terminated by closure of @@ -627,8 +635,10 @@ static int s_linestate_request(struct aws_http_decoder *decoder, struct aws_byte int err = s_cursor_split_exactly_n_times(input, ' ', cursors, 3); /* extra spaces not allowed */ if (err) { AWS_LOGF_ERROR( + AWS_LS_HTTP_STREAM, "id=%p: Incoming request line has wrong number of spaces.", decoder->logging_id); + AWS_LOGF_DEBUG( AWS_LS_HTTP_STREAM, - "id=%p: Incoming request line has wrong number of spaces: '" PRInSTR "'", + "id=%p: Bad request line is: '" PRInSTR "'", decoder->logging_id, AWS_BYTE_CURSOR_PRI(input)); return aws_raise_error(AWS_ERROR_HTTP_PARSE); @@ -636,9 +646,10 @@ static int s_linestate_request(struct aws_http_decoder *decoder, struct aws_byte for (size_t i = 0; i < AWS_ARRAY_SIZE(cursors); ++i) { if (cursors[i].len == 0) { - AWS_LOGF_ERROR( + AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=%p: Incoming request line has empty values.", decoder->logging_id); + AWS_LOGF_DEBUG( AWS_LS_HTTP_STREAM, - "id=%p: Incoming request line has empty values: '" PRInSTR "'", + "id=%p: Bad request line is: '" PRInSTR "'", decoder->logging_id, AWS_BYTE_CURSOR_PRI(input)); return aws_raise_error(AWS_ERROR_HTTP_PARSE); @@ -652,8 +663,10 @@ static int s_linestate_request(struct aws_http_decoder *decoder, struct aws_byte struct aws_byte_cursor version_expected = aws_http_version_to_str(AWS_HTTP_VERSION_1_1); if (!aws_byte_cursor_eq(&version, &version_expected)) { AWS_LOGF_ERROR( + AWS_LS_HTTP_STREAM, "id=%p: Incoming request uses unsupported HTTP version.", decoder->logging_id); + AWS_LOGF_DEBUG( AWS_LS_HTTP_STREAM, - "id=%p: Incoming request uses unsupported HTTP version: '" PRInSTR "'", + "id=%p: Unsupported version is: '" PRInSTR "'", decoder->logging_id, AWS_BYTE_CURSOR_PRI(version)); return aws_raise_error(AWS_ERROR_HTTP_PARSE); @@ -673,9 +686,10 @@ static int s_linestate_response(struct aws_http_decoder *decoder, struct aws_byt struct aws_byte_cursor cursors[3]; int err = s_cursor_split_first_n_times(input, ' ', cursors, 3); /* phrase may contain spaces */ if (err) { - AWS_LOGF_ERROR( + AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=%p: Incoming response status line is invalid.", decoder->logging_id); + AWS_LOGF_DEBUG( AWS_LS_HTTP_STREAM, - "id=%p: Incoming response status line is invalid: '" PRInSTR "'", + "id=%p: Bad status line is: '" PRInSTR "'", decoder->logging_id, AWS_BYTE_CURSOR_PRI(input)); return aws_raise_error(AWS_ERROR_HTTP_PARSE); @@ -689,8 +703,10 @@ static int s_linestate_response(struct aws_http_decoder *decoder, struct aws_byt struct aws_byte_cursor version_expected = aws_http_version_to_str(AWS_HTTP_VERSION_1_1); if (!aws_byte_cursor_eq(&version, &version_expected)) { AWS_LOGF_ERROR( + AWS_LS_HTTP_STREAM, "id=%p: Incoming response uses unsupported HTTP version.", decoder->logging_id); + AWS_LOGF_DEBUG( AWS_LS_HTTP_STREAM, - "id=%p: Incoming response uses unsupported HTTP version: '" PRInSTR "'", + "id=%p: Unsupported version is: '" PRInSTR "'", decoder->logging_id, AWS_BYTE_CURSOR_PRI(version)); return aws_raise_error(AWS_ERROR_HTTP_PARSE); @@ -700,9 +716,10 @@ static int s_linestate_response(struct aws_http_decoder *decoder, struct aws_byt size_t code_val; err = s_read_size(code, &code_val); if (err || code.len != 3 || code_val > 999) { - AWS_LOGF_ERROR( + AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=%p: Incoming response has invalid status code.", decoder->logging_id); + AWS_LOGF_DEBUG( AWS_LS_HTTP_STREAM, - "id=%p: Incoming response has invalid status code: '" PRInSTR "'", + "id=%p: Bad status code is: '" PRInSTR "'", decoder->logging_id, AWS_BYTE_CURSOR_PRI(code)); return AWS_OP_ERR; diff --git a/source/request_response.c b/source/request_response.c index 7b7426af7..5c08291b8 100644 --- a/source/request_response.c +++ b/source/request_response.c @@ -20,7 +20,10 @@ struct aws_http_stream *aws_http_stream_new_client_request(const struct aws_http_request_options *options) { if (!options || options->self_size == 0 || !options->client_connection) { - AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "static: Invalid options, cannot create client request."); + AWS_LOGF_ERROR( + AWS_LS_HTTP_CONNECTION, + "id=%p: Cannot create client request, options are invalid.", + (void *)(options ? options->client_connection : NULL)); aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); return NULL; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 28c8b617f..96c448526 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -36,7 +36,10 @@ add_test_case(h1_client_response_get_headers) add_test_case(h1_client_response_get_body) add_test_case(h1_client_response_get_1_from_multiple_io_messages) add_test_case(h1_client_response_get_multiple_from_1_io_message) +add_test_case(h1_client_response_with_bad_data_shuts_down_connection) add_test_case(h1_client_response_with_too_much_data_shuts_down_connection) +add_test_case(h1_client_response_arrives_before_request_done_sending_is_ok) +add_test_case(h1_client_response_without_request_shuts_down_connection) add_test_case(h1_client_window_reopens_by_default) add_test_case(h1_client_window_shrinks_if_user_says_so) add_test_case(h1_client_window_manual_update) diff --git a/tests/test_h1_client.c b/tests/test_h1_client.c index 1519a7a91..3ae08926c 100644 --- a/tests/test_h1_client.c +++ b/tests/test_h1_client.c @@ -115,6 +115,27 @@ static int s_check_message(struct tester *tester, const char *expected) { return AWS_OP_SUCCESS; } +/* Pop all messages from queue and compare their contents to expected string */ +static int s_check_all_messages(struct tester *tester, const char *expected) { + struct aws_byte_buf all_msgs; + ASSERT_SUCCESS(aws_byte_buf_init(&all_msgs, tester->alloc, 1024)); + + struct aws_linked_list *msgs = testing_channel_get_written_message_queue(&tester->testing_channel); + while (!aws_linked_list_empty(msgs)) { + struct aws_linked_list_node *node = aws_linked_list_pop_front(msgs); + struct aws_io_message *msg = AWS_CONTAINER_OF(node, struct aws_io_message, queueing_handle); + + struct aws_byte_cursor msg_cursor = aws_byte_cursor_from_buf(&msg->message_data); + aws_byte_buf_append_dynamic(&all_msgs, &msg_cursor); + + aws_mem_release(msg->allocator, msg); + } + + ASSERT_TRUE(aws_byte_buf_eq_c_str(&all_msgs, expected)); + aws_byte_buf_clean_up(&all_msgs); + return AWS_OP_SUCCESS; +} + /* Send 1 line request, doesn't care about response */ H1_CLIENT_TEST_CASE(h1_client_request_send_1liner) { (void)ctx; @@ -489,6 +510,9 @@ struct response_tester { int on_complete_error_code; bool stop_auto_window_update; + + /* If a specific test needs to add some custom data */ + void *specific_test_data; }; void s_response_tester_on_headers( @@ -575,10 +599,11 @@ void s_response_tester_on_complete(struct aws_http_stream *stream, int error_cod } /* Create request stream and hook it up so callbacks feed data to the response_tester */ -int s_response_tester_init( +int s_response_tester_init_ex( struct response_tester *response, struct aws_allocator *alloc, - struct aws_http_request_options *opt) { + struct aws_http_request_options *opt, + void *specific_test_data) { AWS_ZERO_STRUCT(*response); ASSERT_SUCCESS(aws_byte_buf_init(&response->storage, alloc, 1024 * 1024 * 1)); /* big enough */ @@ -589,12 +614,21 @@ int s_response_tester_init( opt->on_response_body = s_response_tester_on_body; opt->on_complete = s_response_tester_on_complete; + response->specific_test_data = specific_test_data; response->stream = aws_http_stream_new_client_request(opt); ASSERT_NOT_NULL(response->stream); return AWS_OP_SUCCESS; } +int s_response_tester_init( + struct response_tester *response, + struct aws_allocator *alloc, + struct aws_http_request_options *opt) { + + return s_response_tester_init_ex(response, alloc, opt, NULL); +} + int s_response_tester_clean_up(struct response_tester *response) { aws_http_stream_release(response->stream); aws_byte_buf_clean_up(&response->storage); @@ -834,6 +868,37 @@ H1_CLIENT_TEST_CASE(h1_client_response_get_multiple_from_1_io_message) { return AWS_OP_SUCCESS; } +H1_CLIENT_TEST_CASE(h1_client_response_with_bad_data_shuts_down_connection) { + (void)ctx; + struct tester tester; + ASSERT_SUCCESS(s_tester_init(&tester, allocator)); + + /* send request */ + struct aws_http_request_options opt = AWS_HTTP_REQUEST_OPTIONS_INIT; + opt.client_connection = tester.connection; + opt.method = aws_byte_cursor_from_c_str("GET"); + opt.uri = aws_byte_cursor_from_c_str("/"); + + struct response_tester response; + ASSERT_SUCCESS(s_response_tester_init(&response, allocator, &opt)); + + testing_channel_execute_queued_tasks(&tester.testing_channel); + + /* send response */ + ASSERT_SUCCESS(s_send_response_str_ignore_errors(&tester, "Mmmm garbage data\r\n\r\n")); + + testing_channel_execute_queued_tasks(&tester.testing_channel); + + /* check result */ + ASSERT_TRUE(response.on_complete_cb_count == 1); + ASSERT_INT_EQUALS(AWS_ERROR_HTTP_PARSE, response.on_complete_error_code); + + /* clean up */ + ASSERT_SUCCESS(s_response_tester_clean_up(&response)); + ASSERT_SUCCESS(s_tester_clean_up(&tester)); + return AWS_OP_SUCCESS; +} + /* Test case is: 1 request has been sent. Then 2 responses arrive in 1 io message. * The 1st request should complete just fine, then the connection should shutdown with error */ H1_CLIENT_TEST_CASE(h1_client_response_with_too_much_data_shuts_down_connection) { @@ -876,6 +941,121 @@ H1_CLIENT_TEST_CASE(h1_client_response_with_too_much_data_shuts_down_connection) return AWS_OP_SUCCESS; } +struct slow_body_sender { + struct aws_byte_cursor cursor; + size_t delay_ticks; /* Don't send anything the first N ticks */ + size_t bytes_per_tick; /* Don't send more than N bytes per tick */ +}; + +enum aws_http_outgoing_body_state s_slow_send_body( + struct aws_http_stream *stream, + struct aws_byte_buf *buf, + void *user_data) { + + (void)stream; + struct response_tester *response = user_data; + struct slow_body_sender *sender = response->specific_test_data; + size_t dst_available = buf->capacity - buf->len; + size_t writing = 0; + if (sender->delay_ticks > 0) { + sender->delay_ticks--; + } else { + writing = sender->cursor.len; + + if (dst_available < writing) { + writing = dst_available; + } + + if ((sender->bytes_per_tick < writing) && (sender->bytes_per_tick > 0)) { + writing = sender->bytes_per_tick; + } + } + + aws_byte_buf_write(buf, sender->cursor.ptr, writing); + aws_byte_cursor_advance(&sender->cursor, writing); + + return (sender->cursor.len == 0) ? AWS_HTTP_OUTGOING_BODY_DONE : AWS_HTTP_OUTGOING_BODY_IN_PROGRESS; +} + +/* It should be fine to receive a response before the request has finished sending */ +H1_CLIENT_TEST_CASE(h1_client_response_arrives_before_request_done_sending_is_ok) { + (void)ctx; + struct tester tester; + ASSERT_SUCCESS(s_tester_init(&tester, allocator)); + + /* set up request whose body won't send immediately */ + struct slow_body_sender body_sender = { + .cursor = aws_byte_cursor_from_c_str("write more tests"), + .delay_ticks = 5, + .bytes_per_tick = 1, + }; + + struct aws_http_header headers[] = { + { + .name = aws_byte_cursor_from_c_str("Content-Length"), + .value = aws_byte_cursor_from_c_str("16"), + }, + }; + + struct aws_http_request_options opt = AWS_HTTP_REQUEST_OPTIONS_INIT; + opt.client_connection = tester.connection; + opt.method = aws_byte_cursor_from_c_str("PUT"); + opt.uri = aws_byte_cursor_from_c_str("/plan.txt"); + opt.header_array = headers; + opt.num_headers = AWS_ARRAY_SIZE(headers); + opt.stream_outgoing_body = s_slow_send_body; + + struct response_tester response; + ASSERT_SUCCESS(s_response_tester_init_ex(&response, allocator, &opt, &body_sender)); + + /* send head of request */ + testing_channel_execute_queued_tasks(&tester.testing_channel); + + /* send response */ + ASSERT_SUCCESS(s_send_response_str(&tester, "HTTP/1.1 200 OK\r\n\r\n")); + + /* tick loop until body finishes sending.*/ + while (body_sender.cursor.len > 0) { + testing_channel_execute_queued_tasks(&tester.testing_channel); + } + + /* check result */ + const char *expected = "PUT /plan.txt HTTP/1.1\r\n" + "Content-Length: 16\r\n" + "\r\n" + "write more tests"; + ASSERT_SUCCESS(s_check_all_messages(&tester, expected)); + + ASSERT_TRUE(response.on_complete_cb_count == 1); + ASSERT_TRUE(response.on_complete_error_code == AWS_ERROR_SUCCESS); + ASSERT_TRUE(response.status == 200); + ASSERT_TRUE(response.on_response_header_block_done_cb_count == 1); + ASSERT_TRUE(response.num_headers == 0); + ASSERT_TRUE(response.body.len == 0); + + /* clean up */ + ASSERT_SUCCESS(s_response_tester_clean_up(&response)); + ASSERT_SUCCESS(s_tester_clean_up(&tester)); + return AWS_OP_SUCCESS; +} + +/* Response data arrives, but there was no outstanding request */ +H1_CLIENT_TEST_CASE(h1_client_response_without_request_shuts_down_connection) { + (void)ctx; + struct tester tester; + ASSERT_SUCCESS(s_tester_init(&tester, allocator)); + + ASSERT_SUCCESS(s_send_response_str_ignore_errors(&tester, "HTTP/1.1 200 OK\r\n\r\n")); + testing_channel_execute_queued_tasks(&tester.testing_channel); + + ASSERT_TRUE(tester.is_shut_down); + ASSERT_TRUE(tester.shutdown_error_code != AWS_ERROR_SUCCESS); + + /* clean up */ + ASSERT_SUCCESS(s_tester_clean_up(&tester)); + return AWS_OP_SUCCESS; +} + /* By default, after reading an aws_io_message of N bytes, the connection should issue window update of N bytes */ H1_CLIENT_TEST_CASE(h1_client_window_reopens_by_default) { (void)ctx; @@ -1328,11 +1508,3 @@ H1_CLIENT_TEST_CASE(h1_client_close_from_on_thread_makes_not_open) { ASSERT_SUCCESS(s_tester_clean_up(&tester)); return AWS_OP_SUCCESS; } - -/* Tests TODO -- Responses - - Responses finishing before request done sending - - bad data - - data comes in but no incoming_stream - - invalid data freaks out the decoder -*/