in source/h1_encoder.c [23:157]
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;
}