in source/h2_decoder.c [1234:1365]
static struct aws_h2err s_process_header_field(
struct aws_h2_decoder *decoder,
const struct aws_http_header *header_field) {
struct aws_header_block_in_progress *current_block = &decoder->header_block_in_progress;
if (current_block->malformed) {
goto already_malformed;
}
const struct aws_byte_cursor name = header_field->name;
if (name.len == 0) {
DECODER_LOG(ERROR, decoder, "Header name is blank");
goto malformed;
}
enum aws_http_header_name name_enum = aws_http_lowercase_str_to_header_name(name);
bool is_pseudoheader = name.ptr[0] == ':';
if (is_pseudoheader) {
if (current_block->pseudoheaders_done) {
/* Note: being careful not to leak possibly sensitive data except at DEBUG level and lower */
DECODER_LOG(ERROR, decoder, "Pseudo-headers must appear before regular fields.");
DECODER_LOGF(DEBUG, decoder, "Misplaced pseudo-header is '" PRInSTR "'", AWS_BYTE_CURSOR_PRI(name));
goto malformed;
}
enum pseudoheader_name pseudoheader_enum = s_header_to_pseudoheader_name(name_enum);
if (pseudoheader_enum == PSEUDOHEADER_UNKNOWN) {
DECODER_LOG(ERROR, decoder, "Unrecognized pseudo-header");
DECODER_LOGF(DEBUG, decoder, "Unrecognized pseudo-header is '" PRInSTR "'", AWS_BYTE_CURSOR_PRI(name));
goto malformed;
}
/* Ensure request pseudo-headers vs response pseudoheaders were sent appropriately.
* This also ensures that request and response pseudoheaders aren't being mixed. */
bool expect_request_pseudoheader = decoder->is_server || current_block->is_push_promise;
bool is_request_pseudoheader = pseudoheader_enum != PSEUDOHEADER_STATUS;
if (expect_request_pseudoheader != is_request_pseudoheader) {
DECODER_LOGF(
ERROR, /* ok to log name of recognized pseudo-header at ERROR level */
decoder,
"'" PRInSTR "' pseudo-header cannot be in %s header-block to %s",
AWS_BYTE_CURSOR_PRI(name),
current_block->is_push_promise ? "PUSH_PROMISE" : "HEADERS",
decoder->is_server ? "server" : "client");
goto malformed;
}
/* Protect against duplicates. */
if (current_block->pseudoheader_values[pseudoheader_enum] != NULL) {
/* ok to log name of recognized pseudo-header at ERROR level */
DECODER_LOGF(
ERROR, decoder, "'" PRInSTR "' pseudo-header occurred multiple times", AWS_BYTE_CURSOR_PRI(name));
goto malformed;
}
/* Buffer up pseudo-headers, we'll deliver them later once they're all validated. */
current_block->pseudoheader_compression[pseudoheader_enum] = header_field->compression;
current_block->pseudoheader_values[pseudoheader_enum] =
aws_string_new_from_cursor(decoder->alloc, &header_field->value);
if (!current_block->pseudoheader_values[pseudoheader_enum]) {
return aws_h2err_from_last_error();
}
} else { /* Else regular header-field. */
/* Regular header-fields come after pseudo-headers, so make sure pseudo-headers are flushed */
if (!current_block->pseudoheaders_done) {
struct aws_h2err err = s_flush_pseudoheaders(decoder);
if (aws_h2err_failed(err)) {
return err;
}
/* might have realized that header-block is malformed during flush */
if (current_block->malformed) {
goto already_malformed;
}
}
/* Validate header name (not necessary if string already matched against a known enum) */
if (name_enum == AWS_HTTP_HEADER_UNKNOWN) {
if (!aws_strutil_is_lowercase_http_token(name)) {
DECODER_LOG(ERROR, decoder, "Header name contains invalid characters");
DECODER_LOGF(DEBUG, decoder, "Bad header name is '" PRInSTR "'", AWS_BYTE_CURSOR_PRI(name));
goto malformed;
}
}
/* #TODO Validate characters used in header_field->value */
switch (name_enum) {
case AWS_HTTP_HEADER_COOKIE:
/* for a header cookie, we will not fire callback until we concatenate them all, let's store it at the
* buffer */
if (header_field->compression > current_block->cookie_header_compression_type) {
current_block->cookie_header_compression_type = header_field->compression;
}
if (current_block->cookies.len) {
/* add a delimiter */
struct aws_byte_cursor delimiter = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("; ");
if (aws_byte_buf_append_dynamic(¤t_block->cookies, &delimiter)) {
return aws_h2err_from_last_error();
}
}
if (aws_byte_buf_append_dynamic(¤t_block->cookies, &header_field->value)) {
return aws_h2err_from_last_error();
}
break;
/* TODO: Validate connection-specific header field (RFC7540 8.1.2.2) */
default:
/* Deliver header-field via callback */
if (current_block->is_push_promise) {
DECODER_CALL_VTABLE_STREAM_ARGS(decoder, on_push_promise_i, header_field, name_enum);
} else {
DECODER_CALL_VTABLE_STREAM_ARGS(
decoder, on_headers_i, header_field, name_enum, current_block->block_type);
}
break;
}
}
return AWS_H2ERR_SUCCESS;
malformed:
/* A malformed header-block is not a connection error, it's a Stream Error (RFC-7540 5.4.2).
* We continue decoding and report that it's malformed in on_headers_end(). */
current_block->malformed = true;
return AWS_H2ERR_SUCCESS;
already_malformed:
return AWS_H2ERR_SUCCESS;
}