AZ_NODISCARD az_result az_http_response_get_next_header()

in sdk/src/azure/core/az_http_response.c [139:280]


AZ_NODISCARD az_result az_http_response_get_next_header(
    az_http_response* ref_response,
    az_span* out_name,
    az_span* out_value)
{
  _az_PRECONDITION_NOT_NULL(ref_response);
  _az_PRECONDITION_NOT_NULL(out_name);
  _az_PRECONDITION_NOT_NULL(out_value);

  az_span* reader = &ref_response->_internal.parser.remaining;
  {
    _az_http_response_kind const kind = ref_response->_internal.parser.next_kind;
    // if reader is expecting to read body (all headers were read), return
    // AZ_ERROR_HTTP_END_OF_HEADERS so we know we reach end of headers
    if (kind == _az_HTTP_RESPONSE_KIND_BODY)
    {
      return AZ_ERROR_HTTP_END_OF_HEADERS;
    }
    // Can't read a header if status line was not previously called,
    // User needs to call az_http_response_status_line() which would reset parser and set kind to
    // headers
    if (kind != _az_HTTP_RESPONSE_KIND_HEADER)
    {
      return AZ_ERROR_HTTP_INVALID_STATE;
    }
  }

  if (az_span_size(ref_response->_internal.parser.remaining) == 0)
  {
    // avoid reading address if span is size 0
    return AZ_ERROR_HTTP_CORRUPT_RESPONSE_HEADER;
  }

  // check if we are at the end of all headers to change state to Body.
  // We keep state to Headers if current char is not '\r' (there is another header)
  if (az_span_ptr(ref_response->_internal.parser.remaining)[0] == '\r')
  {
    _az_RETURN_IF_FAILED(_az_is_expected_span(reader, AZ_SPAN_FROM_STR("\r\n")));
    ref_response->_internal.parser.next_kind = _az_HTTP_RESPONSE_KIND_BODY;
    return AZ_ERROR_HTTP_END_OF_HEADERS;
  }

  // https://tools.ietf.org/html/rfc7230#section-3.2
  // header-field   = field-name ":" OWS field-value OWS
  // field-name     = token
  {
    // https://tools.ietf.org/html/rfc7230#section-3.2.6
    // token = 1*tchar
    // tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^" /
    //         "_" / "`" / "|" / "~" / DIGIT / ALPHA;
    // any VCHAR,
    //    except delimiters
    int32_t field_name_length = 0;
    int32_t input_size = az_span_size(*reader);
    uint8_t const* const ptr = az_span_ptr(*reader);
    for (; field_name_length < input_size; ++field_name_length)
    {
      uint8_t next_byte = ptr[field_name_length];
      if (next_byte == ':')
      {
        break;
      }
      if (!az_http_valid_token[next_byte])
      {
        return AZ_ERROR_HTTP_CORRUPT_RESPONSE_HEADER;
      }
    }
    if (field_name_length == input_size)
    {
      return AZ_ERROR_HTTP_CORRUPT_RESPONSE_HEADER;
    }

    // form a header name. Reader is currently at char ':'
    *out_name = az_span_slice(*reader, 0, field_name_length);
    // update reader to next position after colon (add one)
    *reader = az_span_slice_to_end(*reader, field_name_length + 1);

    // Remove whitespace characters from header name
    // https://github.com/Azure/azure-sdk-for-c/issues/604
    *out_name = _az_span_trim_whitespace(*out_name);

    // OWS -> remove the optional whitespace characters before header value
    int32_t ows_len = 0;
    input_size = az_span_size(*reader);
    uint8_t const* const ptr_space = az_span_ptr(*reader);
    for (; ows_len < input_size; ++ows_len)
    {
      uint8_t next_byte = ptr_space[ows_len];
      if (next_byte != ' ' && next_byte != '\t')
      {
        break;
      }
    }
    if (ows_len == input_size)
    {
      return AZ_ERROR_HTTP_CORRUPT_RESPONSE_HEADER;
    }

    *reader = az_span_slice_to_end(*reader, ows_len);
  }
  // field-value    = *( field-content / obs-fold )
  // field-content  = field-vchar [ 1*( SP / HTAB ) field-vchar ]
  // field-vchar    = VCHAR / obs-text
  //
  // obs-fold       = CRLF 1*( SP / HTAB )
  //                ; obsolete line folding
  //                ; see Section 3.2.4
  //
  // Note: obs-fold is not implemented.
  {
    int32_t offset = 0;
    int32_t offset_value_end = offset;
    while (true)
    {
      uint8_t c = az_span_ptr(*reader)[offset];
      offset += 1;
      if (c == '\r')
      {
        break; // break as soon as end of value char is found
      }
      if (_az_is_http_whitespace(c))
      {
        continue; // whitespace or tab is accepted. It can be any number after value (OWS)
      }
      if (c < ' ')
      {
        return AZ_ERROR_HTTP_CORRUPT_RESPONSE_HEADER;
      }
      offset_value_end = offset; // increasing index only for valid chars,
    }
    *out_value = az_span_slice(*reader, 0, offset_value_end);
    // moving reader. It is currently after \r was found
    *reader = az_span_slice_to_end(*reader, offset);

    // Remove whitespace characters from value https://github.com/Azure/azure-sdk-for-c/issues/604
    *out_value = _az_span_trim_whitespace_from_end(*out_value);
  }

  _az_RETURN_IF_FAILED(_az_is_expected_span(reader, AZ_SPAN_FROM_STR("\n")));

  return AZ_OK;
}