static int s_linestate_header()

in source/h1_decoder.c [382:580]


static int s_linestate_header(struct aws_h1_decoder *decoder, struct aws_byte_cursor input) {
    int err;

    /* The \r\n was just processed by `s_state_getline`. */
    /* Empty line signifies end of headers, and beginning of body or end of trailers. */
    /* RFC-7230 section 3 Message Format */
    if (input.len == 0) {
        if (AWS_LIKELY(!decoder->doing_trailers)) {
            if (decoder->body_headers_ignored) {
                err = s_mark_done(decoder);
                if (err) {
                    return AWS_OP_ERR;
                }
            } else if (decoder->transfer_encoding & AWS_HTTP_TRANSFER_ENCODING_CHUNKED) {
                s_set_line_state(decoder, s_linestate_chunk_size);
            } else if (decoder->content_length > 0) {
                s_set_state(decoder, s_state_unchunked_body);
            } else {
                err = s_mark_done(decoder);
                if (err) {
                    return AWS_OP_ERR;
                }
            }
        } else {
            /* Empty line means end of message. */
            err = s_mark_done(decoder);
            if (err) {
                return AWS_OP_ERR;
            }
        }

        return AWS_OP_SUCCESS;
    }

    /* Each header field consists of a case-insensitive field name followed by a colon (":"),
     * optional leading whitespace, the field value, and optional trailing whitespace.
     * RFC-7230 3.2 */
    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.", 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_PROTOCOL_ERROR);
    }

    struct aws_byte_cursor name = splits[0];
    if (!aws_strutil_is_http_token(name)) {
        AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=%p: Invalid incoming header, bad name.", 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_PROTOCOL_ERROR);
    }

    struct aws_byte_cursor value = aws_strutil_trim_http_whitespace(splits[1]);
    if (!aws_strutil_is_http_field_value(value)) {
        AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=%p: Invalid incoming header, bad value.", 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_PROTOCOL_ERROR);
    }

    struct aws_h1_decoded_header header;
    header.name = aws_http_str_to_header_name(name);
    header.name_data = name;
    header.value_data = value;
    header.data = input;

    switch (header.name) {
        case AWS_HTTP_HEADER_CONTENT_LENGTH:
            if (decoder->transfer_encoding) {
                AWS_LOGF_ERROR(
                    AWS_LS_HTTP_STREAM,
                    "id=%p: Incoming headers for both content-length and transfer-encoding received. This is illegal.",
                    decoder->logging_id);
                return aws_raise_error(AWS_ERROR_HTTP_PROTOCOL_ERROR);
            }

            if (aws_byte_cursor_utf8_parse_u64(header.value_data, &decoder->content_length)) {
                AWS_LOGF_ERROR(
                    AWS_LS_HTTP_STREAM,
                    "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_raise_error(AWS_ERROR_HTTP_PROTOCOL_ERROR);
            }

            if (decoder->body_headers_forbidden && decoder->content_length != 0) {
                AWS_LOGF_ERROR(
                    AWS_LS_HTTP_STREAM,
                    "id=%p: Incoming headers for content-length received, but it is illegal for this message to have a "
                    "body",
                    decoder->logging_id);
                return aws_raise_error(AWS_ERROR_HTTP_PROTOCOL_ERROR);
            }

            break;

        case AWS_HTTP_HEADER_TRANSFER_ENCODING: {
            if (decoder->content_length) {
                AWS_LOGF_ERROR(
                    AWS_LS_HTTP_STREAM,
                    "id=%p: Incoming headers for both content-length and transfer-encoding received. This is illegal.",
                    decoder->logging_id);
                return aws_raise_error(AWS_ERROR_HTTP_PROTOCOL_ERROR);
            }

            if (decoder->body_headers_forbidden) {
                AWS_LOGF_ERROR(
                    AWS_LS_HTTP_STREAM,
                    "id=%p: Incoming headers for transfer-encoding received, but it is illegal for this message to "
                    "have a body",
                    decoder->logging_id);
                return aws_raise_error(AWS_ERROR_HTTP_PROTOCOL_ERROR);
            }
            /* RFC-7230 section 3.3.1 Transfer-Encoding */
            /* RFC-7230 section 4.2 Compression Codings */

            /* Note that it's possible for multiple Transfer-Encoding headers to exist, in which case the values
             * should be appended with those from any previously encountered Transfer-Encoding headers. */
            struct aws_byte_cursor split;
            AWS_ZERO_STRUCT(split);
            while (aws_byte_cursor_next_split(&header.value_data, ',', &split)) {
                struct aws_byte_cursor coding = aws_strutil_trim_http_whitespace(split);
                int prev_flags = decoder->transfer_encoding;

                if (aws_string_eq_byte_cursor_ignore_case(s_transfer_coding_chunked, &coding)) {
                    decoder->transfer_encoding |= AWS_HTTP_TRANSFER_ENCODING_CHUNKED;

                } else if (
                    aws_string_eq_byte_cursor_ignore_case(s_transfer_coding_compress, &coding) ||
                    aws_string_eq_byte_cursor_ignore_case(s_transfer_coding_x_compress, &coding)) {
                    /* A recipient SHOULD consider "x-compress" to be equivalent to "compress". RFC-7230 4.2.1 */
                    decoder->transfer_encoding |= AWS_HTTP_TRANSFER_ENCODING_DEPRECATED_COMPRESS;

                } else if (aws_string_eq_byte_cursor_ignore_case(s_transfer_coding_deflate, &coding)) {
                    decoder->transfer_encoding |= AWS_HTTP_TRANSFER_ENCODING_DEFLATE;

                } else if (
                    aws_string_eq_byte_cursor_ignore_case(s_transfer_coding_gzip, &coding) ||
                    aws_string_eq_byte_cursor_ignore_case(s_transfer_coding_x_gzip, &coding)) {
                    /* A recipient SHOULD consider "x-gzip" to be equivalent to "gzip". RFC-7230 4.2.3 */
                    decoder->transfer_encoding |= AWS_HTTP_TRANSFER_ENCODING_GZIP;

                } else if (coding.len > 0) {
                    AWS_LOGF_ERROR(
                        AWS_LS_HTTP_STREAM,
                        "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_PROTOCOL_ERROR);
                }

                /* If any transfer coding other than chunked is applied to a request payload body, the sender MUST
                 * apply chunked as the final transfer coding to ensure that the message is properly framed.
                 * RFC-7230 3.3.1 */
                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 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_PROTOCOL_ERROR);
                }
            }

            /* 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
             * the connection and, thus, is considered complete regardless of the number of message body octets
             * received, provided that the header section was received intact.
             * RFC-7230 3.4 */
        } break;

        default:
            break;
    }

    err = decoder->vtable.on_header(&header, decoder->user_data);
    if (err) {
        return AWS_OP_ERR;
    }

    s_set_line_state(decoder, s_linestate_header);

    return AWS_OP_SUCCESS;
}