nhttp_headers (nhttp_lib v1.1.1)

View Source

Protocol-agnostic header utilities.

Headers carried by nhttp_lib:headers/0 are an ordered list of {Name, Value} binary pairs. The codec layers write names in lowercase at parse time. This module stores the name that the caller gives and never rewrites it. append/3 and set/3 store the lowercase form of the name that they add, and leave every other entry alone.

Key invariants:

  • Header names compare case-insensitively (RFC 9110 §5.1). A lookup matches a stored name in any case, so get/2 with <<"content-length">> finds a stored Content-Length.
  • Multi-valued headers keep insertion order. get/2,3 returns the first match. delete/2 removes every occurrence. append/3 appends and keeps existing entries.

The module also holds one definition of the field validity rule that RFC 9113 §8.2.1 and RFC 9114 §4.1.2 state for a receiver: validate_field_name/1, validate_field_value/1 and the wire lowercasing of lower_field_name/1.

Summary

Functions

Append {Name, Value} to the end of the headers list, preserving any existing occurrences. Useful for multi-valued headers such as set-cookie. The name is stored lowercase.

Remove every header whose name matches Name. Case-insensitive.

The compiled pattern that matches every octet that RFC 9110 §5.5 forbids in a field value: 0x00-0x1F except 0x09, and 0x7F. A caller that scans many field values reads the pattern once and passes it to binary:match/2 for each one.

Keep only the headers for which Pred(Name, Value) returns true. Order is preserved.

Get the first value for Name, or undefined if absent. Case-insensitive.

Get the first value for Name, or Default if absent. Case-insensitive.

True iff a header with the given name exists. Case-insensitive.

True iff Value holds an octet that RFC 9110 §5.5 forbids in a field value: 0x00-0x1F except 0x09, or 0x7F. The scan reads seven octets at a time and charges one reduction per call, so it holds a constant cost per octet at every length. field_value_bad_pattern/0 with binary:match/2 is cheaper below about a hundred octets, because binary:match/2 charges one reduction per ten octets up to its trap. A caller that scans values of any length picks between the two on byte_size/1.

True iff C is a tchar, the character set that RFC 9110 §5.6.2 allows in a token. RFC 6265 §4.1.1 defines cookie-name in terms of the RFC 2616 §2.2 token, which admits the same octets, so cookie names are checked with this predicate too.

True iff Bin is a token per RFC 9110 §5.6.2: token = 1*tchar. An empty binary is not a token.

Lowercase a field name for the wire, without a copy when the name already holds no uppercase octet. RFC 9114 §4.2 requires that characters in field names are converted to lowercase before their encoding, and RFC 9113 §8.2.1 forbids 0x41-0x5A in a field name on the wire. The scan runs before the copy, so a name that is already lowercase comes back as the very binary that the caller passed in. Non-ASCII upper-half octets pass through unchanged, as they do in to_lower/1. This function does not validate the name. Pair it with validate_field_name/1 when the name arrives from a peer.

True iff two field names name the same field. Field names compare case-insensitively (RFC 9110 §5.1), so <<"Content-Length">> and <<"content-length">> name the same field. A caller that asks about a fixed set of names walks the list once and compares each stored name with this function.

The compiled pattern that matches every octet that is not a tchar. A caller that scans many tokens reads the pattern once and passes it to binary:match/2 for each one.

Replace every occurrence of Name with a single {Name, Value} entry. The name is stored lowercase. The replacement is appended to the end of the headers list when no prior occurrence exists.

Lowercase an ASCII binary using HTTP header semantics. RFC 9110 §5.1: field names are ASCII, so non-ASCII upper-half bytes pass through unchanged.

Validate a field name against the minimal rule that RFC 9113 §8.2.1 states as a MUST, and that RFC 9114 §4.1.2 repeats for HTTP/3. A field name must not hold an octet in 0x00-0x20, 0x41-0x5A or 0x7F-0xFF. A field name must not hold a colon, except the single leading colon of a pseudo-header field (RFC 9113 §8.3). An empty name, and a name that is a bare colon and therefore names no pseudo-header field, are refused as empty_field_name. uppercase_field_name names the case that RFC 9114 §4.1.2 lists apart from the other invalid characters. It is reported when the first offending octet is in 0x41-0x5A. This is the minimal rule, not the token rule of RFC 9110 §5.6.2, which RFC 9113 §8.2.1 states as a SHOULD. Use is_token/1 for the stricter test.

Validate a field value against RFC 9110 §5.5 and the edge whitespace rule that RFC 9113 §8.2.1 states as a MUST. The refused octet set is 0x00-0x1F except 0x09, plus 0x7F. That set is a superset of the NUL, LF and CR that RFC 9113 §8.2.1 names, because RFC 9113 §8.2.1 asks for validation against RFC 9110 §5.5. A value that starts or ends with SP or HTAB is refused as field_value_edge_whitespace, which field-content of RFC 9110 §5.5 forbids and RFC 9113 §8.2.1 states again. An empty value is accepted, because field-value of RFC 9110 §5.5 is *field-content.

Types

field_name_error()

-type field_name_error() :: empty_field_name | uppercase_field_name | invalid_field_name_char.

field_value_error()

-type field_value_error() :: invalid_field_value_char | field_value_edge_whitespace.

Functions

append(Name, Value, Headers)

-spec append(binary(), binary(), nhttp_lib:headers()) -> nhttp_lib:headers().

Append {Name, Value} to the end of the headers list, preserving any existing occurrences. Useful for multi-valued headers such as set-cookie. The name is stored lowercase.

delete(Name, Headers)

-spec delete(binary(), nhttp_lib:headers()) -> nhttp_lib:headers().

Remove every header whose name matches Name. Case-insensitive.

field_value_bad_pattern()

-spec field_value_bad_pattern() -> binary:cp().

The compiled pattern that matches every octet that RFC 9110 §5.5 forbids in a field value: 0x00-0x1F except 0x09, and 0x7F. A caller that scans many field values reads the pattern once and passes it to binary:match/2 for each one.

filter(Pred, Headers)

-spec filter(fun((binary(), binary()) -> boolean()), nhttp_lib:headers()) -> nhttp_lib:headers().

Keep only the headers for which Pred(Name, Value) returns true. Order is preserved.

get(Name, Headers)

-spec get(binary(), nhttp_lib:headers()) -> binary() | undefined.

Get the first value for Name, or undefined if absent. Case-insensitive.

get(Name, Headers, Default)

-spec get(binary(), nhttp_lib:headers(), Default) -> binary() | Default.

Get the first value for Name, or Default if absent. Case-insensitive.

has(Name, Headers)

-spec has(binary(), nhttp_lib:headers()) -> boolean().

True iff a header with the given name exists. Case-insensitive.

has_forbidden_value_octet/1

-spec has_forbidden_value_octet(binary()) -> boolean().

True iff Value holds an octet that RFC 9110 §5.5 forbids in a field value: 0x00-0x1F except 0x09, or 0x7F. The scan reads seven octets at a time and charges one reduction per call, so it holds a constant cost per octet at every length. field_value_bad_pattern/0 with binary:match/2 is cheaper below about a hundred octets, because binary:match/2 charges one reduction per ten octets up to its trap. A caller that scans values of any length picks between the two on byte_size/1.

is_tchar/1

-spec is_tchar(byte()) -> boolean().

True iff C is a tchar, the character set that RFC 9110 §5.6.2 allows in a token. RFC 6265 §4.1.1 defines cookie-name in terms of the RFC 2616 §2.2 token, which admits the same octets, so cookie names are checked with this predicate too.

is_token/1

-spec is_token(binary()) -> boolean().

True iff Bin is a token per RFC 9110 §5.6.2: token = 1*tchar. An empty binary is not a token.

lower_field_name(Name)

-spec lower_field_name(binary()) -> binary().

Lowercase a field name for the wire, without a copy when the name already holds no uppercase octet. RFC 9114 §4.2 requires that characters in field names are converted to lowercase before their encoding, and RFC 9113 §8.2.1 forbids 0x41-0x5A in a field name on the wire. The scan runs before the copy, so a name that is already lowercase comes back as the very binary that the caller passed in. Non-ASCII upper-half octets pass through unchanged, as they do in to_lower/1. This function does not validate the name. Pair it with validate_field_name/1 when the name arrives from a peer.

name_eq/2

-spec name_eq(binary(), binary()) -> boolean().

True iff two field names name the same field. Field names compare case-insensitively (RFC 9110 §5.1), so <<"Content-Length">> and <<"content-length">> name the same field. A caller that asks about a fixed set of names walks the list once and compares each stored name with this function.

non_tchar_pattern()

-spec non_tchar_pattern() -> binary:cp().

The compiled pattern that matches every octet that is not a tchar. A caller that scans many tokens reads the pattern once and passes it to binary:match/2 for each one.

set(Name, Value, Headers)

Replace every occurrence of Name with a single {Name, Value} entry. The name is stored lowercase. The replacement is appended to the end of the headers list when no prior occurrence exists.

to_lower/1

-spec to_lower(binary()) -> binary().

Lowercase an ASCII binary using HTTP header semantics. RFC 9110 §5.1: field names are ASCII, so non-ASCII upper-half bytes pass through unchanged.

validate_field_name/1

-spec validate_field_name(binary()) -> ok | {error, field_name_error()}.

Validate a field name against the minimal rule that RFC 9113 §8.2.1 states as a MUST, and that RFC 9114 §4.1.2 repeats for HTTP/3. A field name must not hold an octet in 0x00-0x20, 0x41-0x5A or 0x7F-0xFF. A field name must not hold a colon, except the single leading colon of a pseudo-header field (RFC 9113 §8.3). An empty name, and a name that is a bare colon and therefore names no pseudo-header field, are refused as empty_field_name. uppercase_field_name names the case that RFC 9114 §4.1.2 lists apart from the other invalid characters. It is reported when the first offending octet is in 0x41-0x5A. This is the minimal rule, not the token rule of RFC 9110 §5.6.2, which RFC 9113 §8.2.1 states as a SHOULD. Use is_token/1 for the stricter test.

validate_field_value/1

-spec validate_field_value(binary()) -> ok | {error, field_value_error()}.

Validate a field value against RFC 9110 §5.5 and the edge whitespace rule that RFC 9113 §8.2.1 states as a MUST. The refused octet set is 0x00-0x1F except 0x09, plus 0x7F. That set is a superset of the NUL, LF and CR that RFC 9113 §8.2.1 names, because RFC 9113 §8.2.1 asks for validation against RFC 9110 §5.5. A value that starts or ends with SP or HTAB is refused as field_value_edge_whitespace, which field-content of RFC 9110 §5.5 forbids and RFC 9113 §8.2.1 states again. An empty value is accepted, because field-value of RFC 9110 §5.5 is *field-content.