123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915 |
- /**
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0.
- */
- #include <aws/http/private/h1_encoder.h>
- #include <aws/http/private/strutil.h>
- #include <aws/http/status_code.h>
- #include <aws/io/logging.h>
- #include <aws/io/stream.h>
- #include <inttypes.h>
- #define ENCODER_LOGF(level, encoder, text, ...) \
- AWS_LOGF_##level(AWS_LS_HTTP_STREAM, "id=%p: " text, (void *)encoder->current_stream, __VA_ARGS__)
- #define ENCODER_LOG(level, encoder, text) ENCODER_LOGF(level, encoder, "%s", text)
- #define MAX_ASCII_HEX_CHUNK_STR_SIZE (sizeof(uint64_t) * 2 + 1)
- #define CRLF_SIZE 2
- /**
- * Scan headers to detect errors and determine anything we'll need to know later (ex: total length).
- */
- static int s_scan_outgoing_headers(
- struct aws_h1_encoder_message *encoder_message,
- const struct aws_http_message *message,
- size_t *out_header_lines_len,
- bool body_headers_ignored,
- bool body_headers_forbidden) {
- size_t total = 0;
- bool has_body_stream = aws_http_message_get_body_stream(message);
- bool has_content_length_header = false;
- bool has_transfer_encoding_header = false;
- const size_t num_headers = aws_http_message_get_header_count(message);
- for (size_t i = 0; i < num_headers; ++i) {
- struct aws_http_header header;
- aws_http_message_get_header(message, &header, i);
- /* Validate header field-name (RFC-7230 3.2): field-name = token */
- if (!aws_strutil_is_http_token(header.name)) {
- AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=static: Header name is invalid");
- return aws_raise_error(AWS_ERROR_HTTP_INVALID_HEADER_NAME);
- }
- /* Validate header field-value.
- * The value itself isn't supposed to have whitespace on either side,
- * but we'll trim it off before validation so we don't start needlessly
- * failing requests that used to work before we added validation.
- * This should be OK because field-value can be sent with any amount
- * of whitespace around it, which the other side will just ignore (RFC-7230 3.2):
- * header-field = field-name ":" OWS field-value OWS */
- struct aws_byte_cursor field_value = aws_strutil_trim_http_whitespace(header.value);
- if (!aws_strutil_is_http_field_value(field_value)) {
- AWS_LOGF_ERROR(
- AWS_LS_HTTP_STREAM,
- "id=static: Header '" PRInSTR "' has invalid value",
- AWS_BYTE_CURSOR_PRI(header.name));
- return aws_raise_error(AWS_ERROR_HTTP_INVALID_HEADER_VALUE);
- }
- enum aws_http_header_name name_enum = aws_http_str_to_header_name(header.name);
- switch (name_enum) {
- case AWS_HTTP_HEADER_CONNECTION: {
- if (aws_byte_cursor_eq_c_str(&field_value, "close")) {
- encoder_message->has_connection_close_header = true;
- }
- } break;
- case AWS_HTTP_HEADER_CONTENT_LENGTH: {
- has_content_length_header = true;
- if (aws_byte_cursor_utf8_parse_u64(field_value, &encoder_message->content_length)) {
- AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=static: Invalid Content-Length");
- return aws_raise_error(AWS_ERROR_HTTP_INVALID_HEADER_VALUE);
- }
- } break;
- case AWS_HTTP_HEADER_TRANSFER_ENCODING: {
- has_transfer_encoding_header = true;
- if (0 == field_value.len) {
- AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=static: Transfer-Encoding must include a valid value");
- return aws_raise_error(AWS_ERROR_HTTP_INVALID_HEADER_VALUE);
- }
- struct aws_byte_cursor substr;
- AWS_ZERO_STRUCT(substr);
- while (aws_byte_cursor_next_split(&field_value, ',', &substr)) {
- struct aws_byte_cursor trimmed = aws_strutil_trim_http_whitespace(substr);
- if (0 == trimmed.len) {
- AWS_LOGF_ERROR(
- AWS_LS_HTTP_STREAM,
- "id=static: Transfer-Encoding header whitespace only "
- "comma delimited header value");
- return aws_raise_error(AWS_ERROR_HTTP_INVALID_HEADER_VALUE);
- }
- if (encoder_message->has_chunked_encoding_header) {
- AWS_LOGF_ERROR(
- AWS_LS_HTTP_STREAM, "id=static: Transfer-Encoding header must end with \"chunked\"");
- return aws_raise_error(AWS_ERROR_HTTP_INVALID_HEADER_VALUE);
- }
- if (aws_byte_cursor_eq_c_str(&trimmed, "chunked")) {
- encoder_message->has_chunked_encoding_header = true;
- }
- }
- } break;
- default:
- break;
- }
- /* header-line: "{name}: {value}\r\n" */
- 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) {
- return AWS_OP_ERR;
- }
- }
- if (!encoder_message->has_chunked_encoding_header && has_transfer_encoding_header) {
- AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=static: Transfer-Encoding header must include \"chunked\"");
- return aws_raise_error(AWS_ERROR_HTTP_INVALID_HEADER_VALUE);
- }
- /* Per RFC 7230: A sender MUST NOT send a Content-Length header field in any message that contains a
- * Transfer-Encoding header field. */
- if (encoder_message->has_chunked_encoding_header && has_content_length_header) {
- AWS_LOGF_ERROR(
- AWS_LS_HTTP_STREAM, "id=static: Both Content-Length and Transfer-Encoding are set. Only one may be used");
- return aws_raise_error(AWS_ERROR_HTTP_INVALID_HEADER_VALUE);
- }
- if (encoder_message->has_chunked_encoding_header && has_body_stream) {
- AWS_LOGF_ERROR(
- AWS_LS_HTTP_STREAM,
- "id=static: Both Transfer-Encoding chunked header and body stream is set. "
- "chunked data must use the chunk API to write the body stream.");
- return aws_raise_error(AWS_ERROR_HTTP_INVALID_BODY_STREAM);
- }
- if (body_headers_forbidden && (encoder_message->content_length > 0 || has_transfer_encoding_header)) {
- AWS_LOGF_ERROR(
- AWS_LS_HTTP_STREAM,
- "id=static: Transfer-Encoding or Content-Length headers may not be present in such a message");
- return aws_raise_error(AWS_ERROR_HTTP_INVALID_HEADER_FIELD);
- }
- if (body_headers_ignored) {
- /* Don't send body, no matter what the headers are */
- encoder_message->content_length = 0;
- encoder_message->has_chunked_encoding_header = false;
- }
- if (encoder_message->content_length > 0 && !has_body_stream) {
- return aws_raise_error(AWS_ERROR_HTTP_MISSING_BODY_STREAM);
- }
- *out_header_lines_len = total;
- return AWS_OP_SUCCESS;
- }
- static int s_scan_outgoing_trailer(const struct aws_http_headers *headers, size_t *out_size) {
- const size_t num_headers = aws_http_headers_count(headers);
- size_t total = 0;
- for (size_t i = 0; i < num_headers; i++) {
- struct aws_http_header header;
- aws_http_headers_get_index(headers, i, &header);
- /* Validate header field-name (RFC-7230 3.2): field-name = token */
- if (!aws_strutil_is_http_token(header.name)) {
- AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=static: Header name is invalid");
- return aws_raise_error(AWS_ERROR_HTTP_INVALID_HEADER_NAME);
- }
- /* Validate header field-value.
- * The value itself isn't supposed to have whitespace on either side,
- * but we'll trim it off before validation so we don't start needlessly
- * failing requests that used to work before we added validation.
- * This should be OK because field-value can be sent with any amount
- * of whitespace around it, which the other side will just ignore (RFC-7230 3.2):
- * header-field = field-name ":" OWS field-value OWS */
- struct aws_byte_cursor field_value = aws_strutil_trim_http_whitespace(header.value);
- if (!aws_strutil_is_http_field_value(field_value)) {
- AWS_LOGF_ERROR(
- AWS_LS_HTTP_STREAM,
- "id=static: Header '" PRInSTR "' has invalid value",
- AWS_BYTE_CURSOR_PRI(header.name));
- return aws_raise_error(AWS_ERROR_HTTP_INVALID_HEADER_VALUE);
- }
- enum aws_http_header_name name_enum = aws_http_str_to_header_name(header.name);
- if (name_enum == AWS_HTTP_HEADER_TRANSFER_ENCODING || name_enum == AWS_HTTP_HEADER_CONTENT_LENGTH ||
- name_enum == AWS_HTTP_HEADER_HOST || name_enum == AWS_HTTP_HEADER_EXPECT ||
- name_enum == AWS_HTTP_HEADER_CACHE_CONTROL || name_enum == AWS_HTTP_HEADER_MAX_FORWARDS ||
- name_enum == AWS_HTTP_HEADER_PRAGMA || name_enum == AWS_HTTP_HEADER_RANGE ||
- name_enum == AWS_HTTP_HEADER_TE || name_enum == AWS_HTTP_HEADER_CONTENT_ENCODING ||
- name_enum == AWS_HTTP_HEADER_CONTENT_TYPE || name_enum == AWS_HTTP_HEADER_CONTENT_RANGE ||
- name_enum == AWS_HTTP_HEADER_TRAILER || name_enum == AWS_HTTP_HEADER_WWW_AUTHENTICATE ||
- name_enum == AWS_HTTP_HEADER_AUTHORIZATION || name_enum == AWS_HTTP_HEADER_PROXY_AUTHENTICATE ||
- name_enum == AWS_HTTP_HEADER_PROXY_AUTHORIZATION || name_enum == AWS_HTTP_HEADER_SET_COOKIE ||
- name_enum == AWS_HTTP_HEADER_COOKIE || name_enum == AWS_HTTP_HEADER_AGE ||
- name_enum == AWS_HTTP_HEADER_EXPIRES || name_enum == AWS_HTTP_HEADER_DATE ||
- name_enum == AWS_HTTP_HEADER_LOCATION || name_enum == AWS_HTTP_HEADER_RETRY_AFTER ||
- name_enum == AWS_HTTP_HEADER_VARY || name_enum == AWS_HTTP_HEADER_WARNING) {
- AWS_LOGF_ERROR(
- AWS_LS_HTTP_STREAM,
- "id=static: Trailing Header '" PRInSTR "' has invalid value",
- AWS_BYTE_CURSOR_PRI(header.name));
- return aws_raise_error(AWS_ERROR_HTTP_INVALID_HEADER_FIELD);
- }
- 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) {
- return AWS_OP_ERR;
- }
- }
- if (aws_add_size_checked(2, total, &total)) { /* "\r\n" */
- return AWS_OP_ERR;
- }
- *out_size = total;
- return AWS_OP_SUCCESS;
- }
- static bool s_write_crlf(struct aws_byte_buf *dst) {
- AWS_PRECONDITION(aws_byte_buf_is_valid(dst));
- struct aws_byte_cursor crlf_cursor = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("\r\n");
- return aws_byte_buf_write_from_whole_cursor(dst, crlf_cursor);
- }
- static void s_write_headers(struct aws_byte_buf *dst, const struct aws_http_headers *headers) {
- const size_t num_headers = aws_http_headers_count(headers);
- bool wrote_all = true;
- for (size_t i = 0; i < num_headers; ++i) {
- struct aws_http_header header;
- aws_http_headers_get_index(headers, i, &header);
- /* header-line: "{name}: {value}\r\n" */
- wrote_all &= aws_byte_buf_write_from_whole_cursor(dst, header.name);
- wrote_all &= aws_byte_buf_write_u8(dst, ':');
- wrote_all &= aws_byte_buf_write_u8(dst, ' ');
- wrote_all &= aws_byte_buf_write_from_whole_cursor(dst, header.value);
- wrote_all &= s_write_crlf(dst);
- }
- AWS_ASSERT(wrote_all);
- (void)wrote_all;
- }
- int aws_h1_encoder_message_init_from_request(
- struct aws_h1_encoder_message *message,
- struct aws_allocator *allocator,
- const struct aws_http_message *request,
- struct aws_linked_list *pending_chunk_list) {
- AWS_PRECONDITION(aws_linked_list_is_valid(pending_chunk_list));
- AWS_ZERO_STRUCT(*message);
- message->body = aws_input_stream_acquire(aws_http_message_get_body_stream(request));
- message->pending_chunk_list = pending_chunk_list;
- struct aws_byte_cursor method;
- int err = aws_http_message_get_request_method(request, &method);
- if (err) {
- AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=static: Request method not set");
- aws_raise_error(AWS_ERROR_HTTP_INVALID_METHOD);
- goto error;
- }
- /* RFC-7230 3.1.1: method = token */
- if (!aws_strutil_is_http_token(method)) {
- AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=static: Request method is invalid");
- aws_raise_error(AWS_ERROR_HTTP_INVALID_METHOD);
- goto error;
- }
- struct aws_byte_cursor uri;
- err = aws_http_message_get_request_path(request, &uri);
- if (err) {
- AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=static: Request path not set");
- aws_raise_error(AWS_ERROR_HTTP_INVALID_PATH);
- goto error;
- }
- if (!aws_strutil_is_http_request_target(uri)) {
- AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=static: Request path is invalid");
- aws_raise_error(AWS_ERROR_HTTP_INVALID_PATH);
- goto error;
- }
- struct aws_byte_cursor version = aws_http_version_to_str(AWS_HTTP_VERSION_1_1);
- /**
- * Calculate total size needed for outgoing_head_buffer, then write to buffer.
- */
- size_t header_lines_len;
- err = s_scan_outgoing_headers(
- message, request, &header_lines_len, false /*body_headers_ignored*/, false /*body_headers_forbidden*/);
- if (err) {
- goto error;
- }
- /* request-line: "{method} {uri} {version}\r\n" */
- size_t request_line_len = 4; /* 2 spaces + "\r\n" */
- err |= aws_add_size_checked(method.len, request_line_len, &request_line_len);
- err |= aws_add_size_checked(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;
- 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) {
- goto error;
- }
- err = aws_byte_buf_init(&message->outgoing_head_buf, allocator, head_total_len);
- if (err) {
- goto error;
- }
- bool wrote_all = true;
- wrote_all &= aws_byte_buf_write_from_whole_cursor(&message->outgoing_head_buf, method);
- wrote_all &= aws_byte_buf_write_u8(&message->outgoing_head_buf, ' ');
- wrote_all &= aws_byte_buf_write_from_whole_cursor(&message->outgoing_head_buf, uri);
- wrote_all &= aws_byte_buf_write_u8(&message->outgoing_head_buf, ' ');
- wrote_all &= aws_byte_buf_write_from_whole_cursor(&message->outgoing_head_buf, version);
- wrote_all &= s_write_crlf(&message->outgoing_head_buf);
- s_write_headers(&message->outgoing_head_buf, aws_http_message_get_const_headers(request));
- wrote_all &= s_write_crlf(&message->outgoing_head_buf);
- (void)wrote_all;
- AWS_ASSERT(wrote_all);
- return AWS_OP_SUCCESS;
- error:
- aws_h1_encoder_message_clean_up(message);
- return AWS_OP_ERR;
- }
- int aws_h1_encoder_message_init_from_response(
- struct aws_h1_encoder_message *message,
- struct aws_allocator *allocator,
- const struct aws_http_message *response,
- bool body_headers_ignored,
- struct aws_linked_list *pending_chunk_list) {
- AWS_PRECONDITION(aws_linked_list_is_valid(pending_chunk_list));
- AWS_ZERO_STRUCT(*message);
- message->body = aws_input_stream_acquire(aws_http_message_get_body_stream(response));
- message->pending_chunk_list = pending_chunk_list;
- struct aws_byte_cursor version = aws_http_version_to_str(AWS_HTTP_VERSION_1_1);
- int status_int;
- int err = aws_http_message_get_response_status(response, &status_int);
- if (err) {
- return aws_raise_error(AWS_ERROR_HTTP_INVALID_STATUS_CODE);
- }
- /* Status code must fit in 3 digits */
- AWS_ASSERT(status_int >= 0 && status_int <= 999); /* aws_http_message should have already checked this */
- char status_code_str[4] = "XXX";
- snprintf(status_code_str, sizeof(status_code_str), "%03d", status_int);
- struct aws_byte_cursor status_code = aws_byte_cursor_from_c_str(status_code_str);
- struct aws_byte_cursor status_text = aws_byte_cursor_from_c_str(aws_http_status_text(status_int));
- /**
- * Calculate total size needed for outgoing_head_buffer, then write to buffer.
- */
- size_t header_lines_len;
- /**
- * no body needed in the response
- * RFC-7230 section 3.3 Message Body
- */
- body_headers_ignored |= status_int == AWS_HTTP_STATUS_CODE_304_NOT_MODIFIED;
- bool body_headers_forbidden = status_int == AWS_HTTP_STATUS_CODE_204_NO_CONTENT || status_int / 100 == 1;
- err = s_scan_outgoing_headers(message, response, &header_lines_len, body_headers_ignored, body_headers_forbidden);
- if (err) {
- goto error;
- }
- /* valid status must be three digital code, change it into byte_cursor */
- /* response-line: "{version} {status} {status_text}\r\n" */
- size_t response_line_len = 4; /* 2 spaces + "\r\n" */
- err |= aws_add_size_checked(version.len, response_line_len, &response_line_len);
- err |= aws_add_size_checked(status_code.len, response_line_len, &response_line_len);
- err |= aws_add_size_checked(status_text.len, response_line_len, &response_line_len);
- /* head-end: "\r\n" */
- size_t head_end_len = 2;
- size_t head_total_len = response_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) {
- goto error;
- }
- err = aws_byte_buf_init(&message->outgoing_head_buf, allocator, head_total_len);
- if (err) {
- return AWS_OP_ERR;
- }
- bool wrote_all = true;
- wrote_all &= aws_byte_buf_write_from_whole_cursor(&message->outgoing_head_buf, version);
- wrote_all &= aws_byte_buf_write_u8(&message->outgoing_head_buf, ' ');
- wrote_all &= aws_byte_buf_write_from_whole_cursor(&message->outgoing_head_buf, status_code);
- wrote_all &= aws_byte_buf_write_u8(&message->outgoing_head_buf, ' ');
- wrote_all &= aws_byte_buf_write_from_whole_cursor(&message->outgoing_head_buf, status_text);
- wrote_all &= s_write_crlf(&message->outgoing_head_buf);
- s_write_headers(&message->outgoing_head_buf, aws_http_message_get_const_headers(response));
- wrote_all &= s_write_crlf(&message->outgoing_head_buf);
- (void)wrote_all;
- AWS_ASSERT(wrote_all);
- /* Success! */
- return AWS_OP_SUCCESS;
- error:
- aws_h1_encoder_message_clean_up(message);
- return AWS_OP_ERR;
- }
- void aws_h1_encoder_message_clean_up(struct aws_h1_encoder_message *message) {
- aws_input_stream_release(message->body);
- aws_byte_buf_clean_up(&message->outgoing_head_buf);
- aws_h1_trailer_destroy(message->trailer);
- AWS_ZERO_STRUCT(*message);
- }
- void aws_h1_encoder_init(struct aws_h1_encoder *encoder, struct aws_allocator *allocator) {
- AWS_ZERO_STRUCT(*encoder);
- encoder->allocator = allocator;
- }
- void aws_h1_encoder_clean_up(struct aws_h1_encoder *encoder) {
- AWS_ZERO_STRUCT(*encoder);
- }
- int aws_h1_encoder_start_message(
- struct aws_h1_encoder *encoder,
- struct aws_h1_encoder_message *message,
- struct aws_http_stream *stream) {
- AWS_PRECONDITION(encoder);
- AWS_PRECONDITION(message);
- if (encoder->message) {
- ENCODER_LOG(ERROR, encoder, "Attempting to start new request while previous request is in progress.");
- return aws_raise_error(AWS_ERROR_INVALID_STATE);
- }
- encoder->current_stream = stream;
- encoder->message = message;
- return AWS_OP_SUCCESS;
- }
- static bool s_write_chunk_size(struct aws_byte_buf *dst, uint64_t chunk_size) {
- AWS_PRECONDITION(dst);
- AWS_PRECONDITION(aws_byte_buf_is_valid(dst));
- char ascii_hex_chunk_size_str[MAX_ASCII_HEX_CHUNK_STR_SIZE] = {0};
- snprintf(ascii_hex_chunk_size_str, sizeof(ascii_hex_chunk_size_str), "%" PRIX64, chunk_size);
- return aws_byte_buf_write_from_whole_cursor(dst, aws_byte_cursor_from_c_str(ascii_hex_chunk_size_str));
- }
- static bool s_write_chunk_extension(struct aws_byte_buf *dst, struct aws_http1_chunk_extension *chunk_extension) {
- AWS_PRECONDITION(chunk_extension);
- AWS_PRECONDITION(aws_byte_buf_is_valid(dst));
- bool wrote_all = true;
- wrote_all &= aws_byte_buf_write_u8(dst, ';');
- wrote_all &= aws_byte_buf_write_from_whole_cursor(dst, chunk_extension->key);
- wrote_all &= aws_byte_buf_write_u8(dst, '=');
- wrote_all &= aws_byte_buf_write_from_whole_cursor(dst, chunk_extension->value);
- return wrote_all;
- }
- static size_t s_calculate_chunk_line_size(const struct aws_http1_chunk_options *options) {
- size_t chunk_line_size = MAX_ASCII_HEX_CHUNK_STR_SIZE + CRLF_SIZE;
- for (size_t i = 0; i < options->num_extensions; ++i) {
- struct aws_http1_chunk_extension *chunk_extension = options->extensions + i;
- chunk_line_size += sizeof(';');
- chunk_line_size += chunk_extension->key.len;
- chunk_line_size += sizeof('=');
- chunk_line_size += chunk_extension->value.len;
- }
- return chunk_line_size;
- }
- static void s_populate_chunk_line_buffer(
- struct aws_byte_buf *chunk_line,
- const struct aws_http1_chunk_options *options) {
- bool wrote_chunk_line = true;
- wrote_chunk_line &= s_write_chunk_size(chunk_line, options->chunk_data_size);
- for (size_t i = 0; i < options->num_extensions; ++i) {
- wrote_chunk_line &= s_write_chunk_extension(chunk_line, options->extensions + i);
- }
- wrote_chunk_line &= s_write_crlf(chunk_line);
- AWS_ASSERT(wrote_chunk_line);
- (void)wrote_chunk_line;
- }
- struct aws_h1_trailer *aws_h1_trailer_new(
- struct aws_allocator *allocator,
- const struct aws_http_headers *trailing_headers) {
- /* Allocate trailer along with storage for the trailer-line */
- size_t trailer_size = 0;
- if (s_scan_outgoing_trailer(trailing_headers, &trailer_size)) {
- return NULL;
- }
- struct aws_h1_trailer *trailer = aws_mem_calloc(allocator, 1, sizeof(struct aws_h1_trailer));
- trailer->allocator = allocator;
- aws_byte_buf_init(&trailer->trailer_data, allocator, trailer_size); /* cannot fail */
- s_write_headers(&trailer->trailer_data, trailing_headers);
- s_write_crlf(&trailer->trailer_data); /* \r\n */
- return trailer;
- }
- void aws_h1_trailer_destroy(struct aws_h1_trailer *trailer) {
- if (trailer == NULL) {
- return;
- }
- aws_byte_buf_clean_up(&trailer->trailer_data);
- aws_mem_release(trailer->allocator, trailer);
- }
- struct aws_h1_chunk *aws_h1_chunk_new(struct aws_allocator *allocator, const struct aws_http1_chunk_options *options) {
- /* Allocate chunk along with storage for the chunk-line */
- struct aws_h1_chunk *chunk;
- size_t chunk_line_size = s_calculate_chunk_line_size(options);
- void *chunk_line_storage;
- if (!aws_mem_acquire_many(
- allocator, 2, &chunk, sizeof(struct aws_h1_chunk), &chunk_line_storage, chunk_line_size)) {
- return NULL;
- }
- chunk->allocator = allocator;
- chunk->data = aws_input_stream_acquire(options->chunk_data);
- chunk->data_size = options->chunk_data_size;
- chunk->on_complete = options->on_complete;
- chunk->user_data = options->user_data;
- chunk->chunk_line = aws_byte_buf_from_empty_array(chunk_line_storage, chunk_line_size);
- s_populate_chunk_line_buffer(&chunk->chunk_line, options);
- return chunk;
- }
- void aws_h1_chunk_destroy(struct aws_h1_chunk *chunk) {
- AWS_PRECONDITION(chunk);
- aws_input_stream_release(chunk->data);
- aws_mem_release(chunk->allocator, chunk);
- }
- void aws_h1_chunk_complete_and_destroy(
- struct aws_h1_chunk *chunk,
- struct aws_http_stream *http_stream,
- int error_code) {
- AWS_PRECONDITION(chunk);
- aws_http1_stream_write_chunk_complete_fn *on_complete = chunk->on_complete;
- void *user_data = chunk->user_data;
- /* Clean up before firing callback */
- aws_h1_chunk_destroy(chunk);
- if (NULL != on_complete) {
- on_complete(http_stream, error_code, user_data);
- }
- }
- static void s_clean_up_current_chunk(struct aws_h1_encoder *encoder, int error_code) {
- AWS_PRECONDITION(encoder->current_chunk);
- AWS_PRECONDITION(&encoder->current_chunk->node == aws_linked_list_front(encoder->message->pending_chunk_list));
- aws_linked_list_remove(&encoder->current_chunk->node);
- aws_h1_chunk_complete_and_destroy(encoder->current_chunk, encoder->current_stream, error_code);
- encoder->current_chunk = NULL;
- }
- /* Write as much as possible from src_buf to dst, using encoder->progress_len to track progress.
- * Returns true if the entire src_buf has been copied */
- static bool s_encode_buf(struct aws_h1_encoder *encoder, struct aws_byte_buf *dst, const struct aws_byte_buf *src) {
- /* advance src_cursor to current position in src_buf */
- struct aws_byte_cursor src_cursor = aws_byte_cursor_from_buf(src);
- aws_byte_cursor_advance(&src_cursor, (size_t)encoder->progress_bytes);
- /* write as much as possible to dst, src_cursor is advanced as write occurs */
- struct aws_byte_cursor written = aws_byte_buf_write_to_capacity(dst, &src_cursor);
- encoder->progress_bytes += written.len;
- return src_cursor.len == 0;
- }
- /* Write as much body stream as possible into dst buffer.
- * Increments encoder->progress_bytes to track progress */
- static int s_encode_stream(
- struct aws_h1_encoder *encoder,
- struct aws_byte_buf *dst,
- struct aws_input_stream *stream,
- uint64_t total_length,
- bool *out_done) {
- *out_done = false;
- if (dst->capacity == dst->len) {
- /* Return success because we want to try again later */
- return AWS_OP_SUCCESS;
- }
- /* Read from stream */
- ENCODER_LOG(TRACE, encoder, "Reading from body stream.");
- const size_t prev_len = dst->len;
- int err = aws_input_stream_read(stream, dst);
- const size_t amount_read = dst->len - prev_len;
- if (err) {
- ENCODER_LOGF(
- ERROR,
- encoder,
- "Failed to read body stream, error %d (%s)",
- aws_last_error(),
- aws_error_name(aws_last_error()));
- return AWS_OP_ERR;
- }
- /* Increment progress_bytes, and make sure we haven't written too much */
- int add_err = aws_add_u64_checked(encoder->progress_bytes, amount_read, &encoder->progress_bytes);
- if (add_err || encoder->progress_bytes > total_length) {
- ENCODER_LOGF(ERROR, encoder, "Body stream has exceeded expected length: %" PRIu64, total_length);
- return aws_raise_error(AWS_ERROR_HTTP_OUTGOING_STREAM_LENGTH_INCORRECT);
- }
- ENCODER_LOGF(
- TRACE,
- encoder,
- "Sending %zu bytes of body, progress: %" PRIu64 "/%" PRIu64,
- amount_read,
- encoder->progress_bytes,
- total_length);
- /* Return if we're done sending stream */
- if (encoder->progress_bytes == total_length) {
- *out_done = true;
- return AWS_OP_SUCCESS;
- }
- /* Return if stream failed to write anything. Maybe the data isn't ready yet. */
- if (amount_read == 0) {
- /* Ensure we're not at end-of-stream too early */
- struct aws_stream_status status;
- err = aws_input_stream_get_status(stream, &status);
- if (err) {
- ENCODER_LOGF(
- TRACE,
- encoder,
- "Failed to query body stream status, error %d (%s)",
- aws_last_error(),
- aws_error_name(aws_last_error()));
- return AWS_OP_ERR;
- }
- if (status.is_end_of_stream) {
- ENCODER_LOGF(
- ERROR,
- encoder,
- "Reached end of body stream but sent less than declared length %" PRIu64 "/%" PRIu64,
- encoder->progress_bytes,
- total_length);
- return aws_raise_error(AWS_ERROR_HTTP_OUTGOING_STREAM_LENGTH_INCORRECT);
- }
- }
- /* Not done streaming data out yet */
- return AWS_OP_SUCCESS;
- }
- /* A state function should:
- * - Raise an error only if unrecoverable error occurs.
- * - `return s_switch_state(...)` to switch states.
- * - `return AWS_OP_SUCCESS` if it can't progress any further (waiting for more
- * space to write into, waiting for more chunks, etc). */
- typedef int encoder_state_fn(struct aws_h1_encoder *encoder, struct aws_byte_buf *dst);
- /* Switch state.
- * The only reason this returns a value is so it can be called with `return` to conclude a state function */
- static int s_switch_state(struct aws_h1_encoder *encoder, enum aws_h1_encoder_state state) {
- encoder->state = state;
- encoder->progress_bytes = 0;
- return AWS_OP_SUCCESS;
- }
- /* Initial state. Waits until a new message is set */
- static int s_state_fn_init(struct aws_h1_encoder *encoder, struct aws_byte_buf *dst) {
- (void)dst;
- if (!encoder->message) {
- /* Remain in this state. */
- return AWS_OP_SUCCESS;
- }
- /* Start encoding message */
- ENCODER_LOG(TRACE, encoder, "Starting to send data.");
- return s_switch_state(encoder, AWS_H1_ENCODER_STATE_HEAD);
- }
- /* Write out first line of request/response, plus all the headers.
- * These have been pre-encoded in aws_h1_encoder_message->outgoing_head_buf. */
- static int s_state_fn_head(struct aws_h1_encoder *encoder, struct aws_byte_buf *dst) {
- bool done = s_encode_buf(encoder, dst, &encoder->message->outgoing_head_buf);
- if (!done) {
- /* Remain in this state */
- return AWS_OP_SUCCESS;
- }
- /* Don't NEED to free this buffer now, but we don't need it anymore, so why not */
- aws_byte_buf_clean_up(&encoder->message->outgoing_head_buf);
- /* Pick next state */
- if (encoder->message->body && encoder->message->content_length) {
- return s_switch_state(encoder, AWS_H1_ENCODER_STATE_UNCHUNKED_BODY);
- } else if (encoder->message->has_chunked_encoding_header) {
- return s_switch_state(encoder, AWS_H1_ENCODER_STATE_CHUNK_NEXT);
- } else {
- return s_switch_state(encoder, AWS_H1_ENCODER_STATE_DONE);
- }
- }
- /* Write out body (not using chunked encoding). */
- static int s_state_fn_unchunked_body(struct aws_h1_encoder *encoder, struct aws_byte_buf *dst) {
- bool done;
- if (s_encode_stream(encoder, dst, encoder->message->body, encoder->message->content_length, &done)) {
- return AWS_OP_ERR;
- }
- if (!done) {
- /* Remain in this state until we're done writing out body */
- return AWS_OP_SUCCESS;
- }
- /* Message is done */
- return s_switch_state(encoder, AWS_H1_ENCODER_STATE_DONE);
- }
- /* Select next chunk to work on.
- * Encoder is essentially "paused" here if no chunks are available. */
- static int s_state_fn_chunk_next(struct aws_h1_encoder *encoder, struct aws_byte_buf *dst) {
- (void)dst;
- if (aws_linked_list_empty(encoder->message->pending_chunk_list)) {
- /* Remain in this state until more chunks arrive */
- ENCODER_LOG(TRACE, encoder, "No chunks ready to send, waiting for more...");
- return AWS_OP_SUCCESS;
- }
- /* Set next chunk and go to next state */
- struct aws_linked_list_node *node = aws_linked_list_front(encoder->message->pending_chunk_list);
- encoder->current_chunk = AWS_CONTAINER_OF(node, struct aws_h1_chunk, node);
- encoder->chunk_count++;
- ENCODER_LOGF(
- TRACE,
- encoder,
- "Begin sending chunk %zu with size %" PRIu64,
- encoder->chunk_count,
- encoder->current_chunk->data_size);
- return s_switch_state(encoder, AWS_H1_ENCODER_STATE_CHUNK_LINE);
- }
- /* Write out "chunk-size [chunk-ext] CRLF".
- * This data is pre-encoded in the chunk's chunk_line buffer */
- static int s_state_fn_chunk_line(struct aws_h1_encoder *encoder, struct aws_byte_buf *dst) {
- bool done = s_encode_buf(encoder, dst, &encoder->current_chunk->chunk_line);
- if (!done) {
- /* Remain in state until done writing line */
- return AWS_OP_SUCCESS;
- }
- /* Pick next state */
- if (encoder->current_chunk->data_size == 0) {
- /* If data_size is 0, then this was the last chunk, which has no body.
- * Mark it complete and move on to trailer. */
- ENCODER_LOG(TRACE, encoder, "Final chunk complete");
- s_clean_up_current_chunk(encoder, AWS_ERROR_SUCCESS);
- return s_switch_state(encoder, AWS_H1_ENCODER_STATE_CHUNK_TRAILER);
- }
- return s_switch_state(encoder, AWS_H1_ENCODER_STATE_CHUNK_BODY);
- }
- /* Write out data for current chunk */
- static int s_state_fn_chunk_body(struct aws_h1_encoder *encoder, struct aws_byte_buf *dst) {
- bool done;
- if (s_encode_stream(encoder, dst, encoder->current_chunk->data, encoder->current_chunk->data_size, &done)) {
- int error_code = aws_last_error();
- /* The error was caused by the chunk itself, report that specific error in its completion callback */
- s_clean_up_current_chunk(encoder, error_code);
- /* Re-raise error, in case it got cleared during user callback */
- return aws_raise_error(error_code);
- }
- if (!done) {
- /* Remain in this state until we're done writing out body */
- return AWS_OP_SUCCESS;
- }
- return s_switch_state(encoder, AWS_H1_ENCODER_STATE_CHUNK_END);
- }
- /* Write CRLF and mark chunk as complete */
- static int s_state_fn_chunk_end(struct aws_h1_encoder *encoder, struct aws_byte_buf *dst) {
- bool done = s_write_crlf(dst);
- if (!done) {
- /* Remain in this state until done writing out CRLF */
- return AWS_OP_SUCCESS;
- }
- ENCODER_LOG(TRACE, encoder, "Chunk complete");
- s_clean_up_current_chunk(encoder, AWS_ERROR_SUCCESS);
- /* Pick next chunk to work on */
- return s_switch_state(encoder, AWS_H1_ENCODER_STATE_CHUNK_NEXT);
- }
- /* Write out trailer after last chunk */
- static int s_state_fn_chunk_trailer(struct aws_h1_encoder *encoder, struct aws_byte_buf *dst) {
- bool done;
- /* if a chunked trailer was set */
- if (encoder->message->trailer) {
- done = s_encode_buf(encoder, dst, &encoder->message->trailer->trailer_data);
- } else {
- done = s_write_crlf(dst);
- }
- if (!done) {
- /* Remain in this state until we're done writing out trailer */
- return AWS_OP_SUCCESS;
- }
- return s_switch_state(encoder, AWS_H1_ENCODER_STATE_DONE);
- }
- /* Message is done, loop back to start of state machine */
- static int s_state_fn_done(struct aws_h1_encoder *encoder, struct aws_byte_buf *dst) {
- (void)dst;
- ENCODER_LOG(TRACE, encoder, "Done sending data.");
- encoder->message = NULL;
- return s_switch_state(encoder, AWS_H1_ENCODER_STATE_INIT);
- }
- struct encoder_state_def {
- encoder_state_fn *fn;
- const char *name;
- };
- static struct encoder_state_def s_encoder_states[] = {
- [AWS_H1_ENCODER_STATE_INIT] = {.fn = s_state_fn_init, .name = "INIT"},
- [AWS_H1_ENCODER_STATE_HEAD] = {.fn = s_state_fn_head, .name = "HEAD"},
- [AWS_H1_ENCODER_STATE_UNCHUNKED_BODY] = {.fn = s_state_fn_unchunked_body, .name = "BODY"},
- [AWS_H1_ENCODER_STATE_CHUNK_NEXT] = {.fn = s_state_fn_chunk_next, .name = "CHUNK_NEXT"},
- [AWS_H1_ENCODER_STATE_CHUNK_LINE] = {.fn = s_state_fn_chunk_line, .name = "CHUNK_LINE"},
- [AWS_H1_ENCODER_STATE_CHUNK_BODY] = {.fn = s_state_fn_chunk_body, .name = "CHUNK_BODY"},
- [AWS_H1_ENCODER_STATE_CHUNK_END] = {.fn = s_state_fn_chunk_end, .name = "CHUNK_END"},
- [AWS_H1_ENCODER_STATE_CHUNK_TRAILER] = {.fn = s_state_fn_chunk_trailer, .name = "CHUNK_TRAILER"},
- [AWS_H1_ENCODER_STATE_DONE] = {.fn = s_state_fn_done, .name = "DONE"},
- };
- int aws_h1_encoder_process(struct aws_h1_encoder *encoder, struct aws_byte_buf *out_buf) {
- AWS_PRECONDITION(encoder);
- AWS_PRECONDITION(out_buf);
- if (!encoder->message) {
- ENCODER_LOG(ERROR, encoder, "No message is currently set for encoding.");
- return aws_raise_error(AWS_ERROR_INVALID_STATE);
- }
- /* Run state machine until states stop changing. (due to out_buf running
- * out of space, input_stream stalling, waiting for more chunks, etc) */
- enum aws_h1_encoder_state prev_state;
- do {
- prev_state = encoder->state;
- if (s_encoder_states[encoder->state].fn(encoder, out_buf)) {
- return AWS_OP_ERR;
- }
- } while (prev_state != encoder->state);
- return AWS_OP_SUCCESS;
- }
- bool aws_h1_encoder_is_message_in_progress(const struct aws_h1_encoder *encoder) {
- return encoder->message;
- }
- bool aws_h1_encoder_is_waiting_for_chunks(const struct aws_h1_encoder *encoder) {
- return encoder->state == AWS_H1_ENCODER_STATE_CHUNK_NEXT &&
- aws_linked_list_empty(encoder->message->pending_chunk_list);
- }
|