Packages
hackney
1.10.1
4.7.2
4.7.1
4.7.0
4.6.1
4.6.0
4.5.2
4.5.1
4.5.0
4.4.5
4.4.3
4.4.2
4.4.1
4.4.0
4.3.0
4.2.3
4.2.2
4.2.1
4.2.0
4.1.0
4.0.3
4.0.2
4.0.1
4.0.0
3.2.1
3.2.0
3.1.2
3.1.1
3.1.0
3.0.3
3.0.2
3.0.1
3.0.0
retired
2.0.1
2.0.0
2.0.0-beta.1
1.25.0
1.24.1
1.24.0
1.23.0
1.22.0
1.21.0
1.20.1
1.20.0
1.19.1
1.19.0
1.18.2
1.18.1
1.18.0
1.17.4
1.17.3
1.17.2
1.17.1
1.17.0
1.16.0
1.15.2
1.15.1
1.15.0
1.14.3
1.14.2
1.14.0
1.13.0
1.12.1
1.12.0
1.11.0
1.10.1
1.10.0
1.9.0
1.8.6
1.8.5
1.8.4
1.8.3
1.8.2
1.8.0
1.7.1
1.7.0
1.6.6
retired
1.6.5
1.6.4
retired
1.6.3
1.6.2
1.6.1
1.6.0
1.5.7
1.5.6
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.4.10
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.0
1.1.0
1.0.6
1.0.5
1.0.2
1.0.1
0.15.2
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.1
Simple HTTP client with HTTP/1.1, HTTP/2, and HTTP/3 support
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
src/hackney_headers.erl
%%% -*- erlang -*-
%%%
%%% This file is part of hackney_lib released under the Apache 2 license.
%%% See the NOTICE for more information.
%%%
%%% Copyright (c) 2012-2014 Benoît Chesneau <benoitc@e-engura.org>
%%% Copyright (c) 2011-2013, Loïc Hoguin <essen@ninenines.eu>
%%% Copyright (c) 2011, Anthony Ramine <nox@dev-extend.eu>
%% @doc module to manipulate HTTP headers
-module(hackney_headers).
-export([new/0, new/1,
update/2,
to_list/1,
get_value/2, get_value/3,
store/3,
insert/3, insert/4,
delete/2,
fold/3]).
-export([to_binary/1]).
-export([make_header/2, make_header/3]).
-export([header_value/2]).
-export([parse/2]).
-export([content_type/1]).
-export([content_disposition/1]).
-type disposition() :: {binary(), [{binary(), binary()}]}.
-type headers() :: any().
%% @doc initialise an header dict
-spec new() -> headers().
new() ->
dict:new().
-spec new(list()) -> headers().
new({dict, _}=D) ->
D;
new(Headers) when is_list(Headers) ->
lists:foldl(fun
({K, V}, D) ->
insert(hackney_bstr:to_binary(K), hackney_bstr:to_binary(V), D);
({K, V, P}, D) ->
insert(hackney_bstr:to_binary(K), header_value(V, P), D)
end, dict:new(), Headers).
%% @doc extend the headers with a new list of `{Key, Value}' pair.
update(Headers, KVs) ->
lists:foldl(fun
({K, V}, D) ->
insert(hackney_bstr:to_binary(K), hackney_bstr:to_binary(V), D);
({K, V, P}, D) ->
insert(hackney_bstr:to_binary(K), header_value(V, P), D)
end, Headers, KVs).
%% convert the header to a list
to_list(Headers) ->
lists:reverse(dict:fold(fun(_K, KV, Acc) ->
[KV | Acc]
end, [], Headers)).
%% @doc get the value of the header
get_value(Key, Headers) ->
get_value(Key, Headers, undefined).
get_value(Key, Headers, Default) ->
case dict:find(hackney_bstr:to_lower(Key), Headers) of
{ok, {_K, V}} ->
V;
_ ->
Default
end.
%% @doc store the pair into the headers, replacing any pre-existing key.
store(Key, Value, Headers) ->
dict:store(hackney_bstr:to_lower(Key), {Key, Value}, Headers).
%% @doc Insert the pair into the headers, merging with any pre-existing key.
%% A merge is done with Value = V0 ++ ", " ++ V1.
insert(Key, Value, Headers) ->
Key1 = hackney_bstr:to_lower(Key),
Value1 = case dict:find(Key1, Headers) of
{ok, {_, OldValue}} ->
<< OldValue/binary, ", ", Value/binary >>;
_ ->
Value
end,
dict:store(Key1, {Key, Value1}, Headers).
%% @doc same as `insert/3' but allows to add params to the header value.
insert(Key, Value, Params, Headers) ->
insert(Key, header_value(Value, Params), Headers).
%% @doc Delete the header corresponding to key if it is present.
delete(Key, Headers) ->
dict:erase(hackney_bstr:to_lower(Key), Headers).
%% @doc fold the list of headers
fold(Fun, Acc0, Headers) ->
Wrapper = fun(_K, KV, Acc) ->
Fun(KV, Acc)
end,
dict:fold(Wrapper, Acc0, Headers).
%% @doc return all the headers as a binary that can be sent over the
%% wire.
to_binary(Headers) when is_list(Headers) ->
HeadersList = lists:foldl(fun
({Name, Value}, Acc) ->
[make_header(Name, Value) | Acc];
({Name, Value, Params}, Acc) ->
[make_header(Name, Value, Params) | Acc]
end, [], Headers),
iolist_to_binary([
hackney_bstr:join(lists:reverse(HeadersList), <<"\r\n">>),
<<"\r\n\r\n">>]);
to_binary(Headers) ->
to_binary(to_list(Headers)).
%% @doc Create a binary header
make_header(Name, Value) ->
Value1 = hackney_bstr:to_binary(Value),
<< Name/binary, ": ", Value1/binary >>.
make_header(Name, Value, Params) ->
Value1 = header_value(hackney_bstr:to_binary(Value), Params),
<< Name/binary, ": ", Value1/binary >>.
%% @doc join value and params in a binary
header_value(Value, Params) when is_list(Value) ->
header_value(list_to_binary(Value), Params);
header_value(Value, Params) ->
Params1 = lists:foldl(fun({K, V}, Acc) ->
K1 = hackney_bstr:to_binary(K),
V1 = hackney_bstr:to_binary(V),
ParamStr = << K1/binary, "=", V1/binary >>,
[ParamStr | Acc]
end, [], Params),
hackney_bstr:join([Value] ++ lists:reverse(Params1), "; ").
%% @doc Semantically parse headers.
%%
%% When the value isn't found, a proper default value for the type
%% returned is used as a return value.
%% @see parse/3
-spec parse(binary(), list() | headers())
-> any() | undefined | {error, badarg}.
parse(Name, Headers) when is_list(Headers) ->
parse(Name, new(Headers));
parse(Name, Headers) ->
Name1 = hackney_bstr:to_lower(Name),
parse(Name1, Headers, header_default(Name1)).
%% @doc Semantically parse headers.
%%
%% When the header is unknown, the value is returned directly without parsing.
-spec parse(binary(), headers(), any())
-> any() | undefined | {error, badarg}.
parse(Name = <<"accept">>, Headers, Default) ->
parse(Name, Headers, Default,
fun (Value) ->
hackney_bstr:list(Value, fun media_range/2)
end);
parse(Name = <<"accept-charset">>, Headers, Default) ->
parse(Name, Headers, Default,
fun (Value) ->
hackney_bstr:nonempty_list(Value, fun conneg/2)
end);
parse(Name = <<"accept-encoding">>, Headers, Default) ->
parse(Name, Headers, Default,
fun (Value) ->
hackney_bstr:list(Value, fun conneg/2)
end);
parse(Name = <<"accept-language">>, Headers, Default) ->
parse(Name, Headers, Default,
fun (Value) ->
hackney_bstr:nonempty_list(Value, fun language_range/2)
end);
parse(Name = <<"authorization">>, Headers, Default) ->
parse(Name, Headers, Default,
fun (Value) ->
hackney_bstr:token_ci(Value, fun authorization/2)
end);
parse(Name = <<"content-length">>, Headers, Default) ->
parse(Name, Headers, Default, fun hackney_bstr:digits/1);
parse(Name = <<"content-type">>, Headers, Default) ->
parse(Name, Headers, Default, fun content_type/1);
parse(Name = <<"cookie">>, Headers, Default) ->
parse(Name, Headers, Default, fun hackney_cookie:parse_cookie/1);
parse(Name = <<"expect">>, Headers, Default) ->
parse(Name, Headers, Default,
fun (Value) ->
hackney_bstr:nonempty_list(Value, fun expectation/2)
end);
parse(Name, Headers, Default)
when Name =:= <<"if-match">>;
Name =:= <<"if-none-match">> ->
parse(Name, Headers, Default, fun entity_tag_match/1);
parse(Name, Headers, Default)
when Name =:= <<"if-modified-since">>;
Name =:= <<"if-unmodified-since">> ->
parse(Name, Headers, Default, fun hackney_date:parse_http_date/1);
parse(Name = <<"range">>, Headers, Default) ->
parse(Name, Headers, Default, fun range/1);
parse(Name, Headers, Default)
when Name =:= <<"sec-websocket-protocol">>;
Name =:= <<"x-forwarded-for">> ->
parse(Name, Headers, Default,
fun (Value) ->
hackney_bstr:nonempty_list(Value, fun hackney_bstr:token/2)
end);
%% @todo Extension parameters.
parse(Name, Headers, Default)
when Name =:= <<"transfer-encoding">>;
Name =:= <<"upgrade">> ->
parse(Name, Headers, Default,
fun (Value) ->
hackney_bstr:nonempty_list(Value, fun hackney_bstr:token_ci/2)
end);
parse(Name = <<"sec-websocket-extensions">>, Headers, Default) ->
parse(Name, Headers, Default, fun hackney_bstr:parameterized_tokens/1);
parse(Name, Headers, Default) ->
get_value(Name, Headers, Default).
parse(Name, Headers, Default, Fun) ->
case get_value(Name, Headers) of
undefined ->
Default;
Value ->
case Fun(Value) of
{error, badarg} ->
{error, badarg};
P ->
P
end
end.
%% @doc Parse a content type.
%%
%% We lowercase the charset header as we know it's case insensitive.
-spec content_type(binary()) -> any().
content_type(Data) ->
media_type(Data,
fun (Rest, Type, SubType) ->
hackney_bstr:params(Rest,
fun (<<>>, Params) ->
case lists:keyfind(<<"charset">>, 1, Params) of
false ->
{Type, SubType, Params};
{_, Charset} ->
Charset2 = hackney_bstr:to_lower(Charset),
Params2 = lists:keyreplace(<<"charset">>,
1, Params,
{<<"charset">>, Charset2}),
{Type, SubType, Params2}
end;
(_Rest2, _) ->
{error, badarg}
end)
end).
%% @doc Parse a content disposition.
%% @todo Parse the MIME header instead of the HTTP one.
-spec content_disposition(binary()) -> disposition().
content_disposition(Data) ->
hackney_bstr:token_ci(Data, fun
(_Rest, <<>>) ->
{error, badarg};
(Rest, Disposition) ->
hackney_bstr:params(Rest, fun
(<<>>, Params) -> {Disposition, Params};
(_Rest2, _) -> {error, badarg}
end)
end).
%% @doc Parse a media range.
-spec media_range(binary(), fun()) -> any().
media_range(Data, Fun) ->
media_type(Data,
fun (Rest, Type, SubType) ->
media_range_params(Rest, Fun, Type, SubType, [])
end).
-spec media_range_params(binary(), fun(), binary(), binary(),
[{binary(), binary()}]) -> any().
media_range_params(Data, Fun, Type, SubType, Acc) ->
hackney_bstr:whitespace(Data,
fun (<< $;, Rest/binary >>) ->
hackney_bstr:whitespace(Rest,
fun (Rest2) ->
media_range_param_attr(Rest2, Fun, Type, SubType,
Acc)
end);
(Rest) -> Fun(Rest, {{Type, SubType, lists:reverse(Acc)}, 1000,
[]})
end).
-spec media_range_param_attr(binary(), fun(), binary(), binary(),
[{binary(), binary()}]) -> any().
media_range_param_attr(Data, Fun, Type, SubType, Acc) ->
hackney_bstr:token_ci(Data,
fun (_Rest, <<>>) -> {error, badarg};
(<< $=, Rest/binary >>, Attr) ->
media_range_param_value(Rest, Fun, Type, SubType, Acc, Attr)
end).
-spec media_range_param_value(binary(), fun(), binary(), binary(),
[{binary(), binary()}], binary()) -> any().
media_range_param_value(Data, Fun, Type, SubType, Acc, <<"q">>) ->
qvalue(Data,
fun (Rest, Quality) ->
accept_ext(Rest, Fun, Type, SubType, Acc, Quality, [])
end);
media_range_param_value(Data, Fun, Type, SubType, Acc, Attr) ->
hackney_bstr:word(Data,
fun (Rest, Value) ->
media_range_params(Rest, Fun,
Type, SubType, [{Attr, Value}|Acc])
end).
%% @doc Parse a media type.
-spec media_type(binary(), fun()) -> any().
media_type(Data, Fun) ->
hackney_bstr:token_ci(Data,
fun (_Rest, <<>>) -> {error, badarg};
(<< $/, Rest/binary >>, Type) ->
hackney_bstr:token_ci(Rest,
fun (_Rest2, <<>>) -> {error, badarg};
(Rest2, SubType) -> Fun(Rest2, Type, SubType)
end);
%% This is a non-strict parsing clause required by some user agents
%% that use * instead of */* in the list of media types.
(Rest, <<"*">> = Type) ->
hackney_bstr:token_ci(<<"*", Rest/binary>>,
fun (_Rest2, <<>>) -> {error, badarg};
(Rest2, SubType) -> Fun(Rest2, Type, SubType)
end);
(_Rest, _Type) -> {error, badarg}
end).
-spec accept_ext(binary(), fun(), binary(), binary(),
[{binary(), binary()}], 0..1000,
[{binary(), binary()} | binary()]) -> any().
accept_ext(Data, Fun, Type, SubType, Params, Quality, Acc) ->
hackney_bstr:whitespace(Data,
fun (<< $;, Rest/binary >>) ->
hackney_bstr:whitespace(Rest,
fun (Rest2) ->
accept_ext_attr(Rest2, Fun,
Type, SubType, Params, Quality, Acc)
end);
(Rest) ->
Fun(Rest, {{Type, SubType, lists:reverse(Params)},
Quality, lists:reverse(Acc)})
end).
-spec accept_ext_attr(binary(), fun(), binary(), binary(),
[{binary(), binary()}], 0..1000,
[{binary(), binary()} | binary()]) -> any().
accept_ext_attr(Data, Fun, Type, SubType, Params, Quality, Acc) ->
hackney_bstr:token_ci(Data,
fun (_Rest, <<>>) -> {error, badarg};
(<< $=, Rest/binary >>, Attr) ->
accept_ext_value(Rest, Fun, Type, SubType, Params,
Quality, Acc, Attr);
(Rest, Attr) ->
accept_ext(Rest, Fun, Type, SubType, Params,
Quality, [Attr|Acc])
end).
-spec accept_ext_value(binary(), fun(), binary(), binary(),
[{binary(), binary()}], 0..1000,
[{binary(), binary()} | binary()], binary()) -> any().
accept_ext_value(Data, Fun, Type, SubType, Params, Quality, Acc, Attr) ->
hackney_bstr:word(Data,
fun (Rest, Value) ->
accept_ext(Rest, Fun,
Type, SubType, Params, Quality, [{Attr, Value}|Acc])
end).
%% @doc Parse a conneg header (Accept-Charset, Accept-Encoding),
%% followed by an optional quality value.
-spec conneg(binary(), fun()) -> any().
conneg(Data, Fun) ->
hackney_bstr:token_ci(Data,
fun (_Rest, <<>>) -> {error, badarg};
(Rest, Conneg) ->
maybe_qparam(Rest,
fun (Rest2, Quality) ->
Fun(Rest2, {Conneg, Quality})
end)
end).
%% @doc Parse a language range, followed by an optional quality value.
-spec language_range(binary(), fun()) -> any().
language_range(<< $*, Rest/binary >>, Fun) ->
language_range_ret(Rest, Fun, '*');
language_range(Data, Fun) ->
language_tag(Data,
fun (Rest, LanguageTag) ->
language_range_ret(Rest, Fun, LanguageTag)
end).
-spec language_range_ret(binary(), fun(), '*' | {binary(), [binary()]}) -> any().
language_range_ret(Data, Fun, LanguageTag) ->
maybe_qparam(Data,
fun (Rest, Quality) ->
Fun(Rest, {LanguageTag, Quality})
end).
-spec language_tag(binary(), fun()) -> any().
language_tag(Data, Fun) ->
hackney_bstr:alpha(Data,
fun (_Rest, Tag) when byte_size(Tag) =:= 0; byte_size(Tag) > 8 ->
{error, badarg};
(<< $-, Rest/binary >>, Tag) ->
language_subtag(Rest, Fun, Tag, []);
(Rest, Tag) ->
Fun(Rest, Tag)
end).
-spec language_subtag(binary(), fun(), binary(), [binary()]) -> any().
language_subtag(Data, Fun, Tag, Acc) ->
hackney_bstr:alpha(Data,
fun (_Rest, SubTag) when byte_size(SubTag) =:= 0;
byte_size(SubTag) > 8 -> {error, badarg};
(<< $-, Rest/binary >>, SubTag) ->
language_subtag(Rest, Fun, Tag, [SubTag|Acc]);
(Rest, SubTag) ->
%% Rebuild the full tag now that we know it's correct
Sub = << << $-, S/binary >> || S <- lists:reverse([SubTag|Acc]) >>,
Fun(Rest, << Tag/binary, Sub/binary >>)
end).
-spec maybe_qparam(binary(), fun()) -> any().
maybe_qparam(Data, Fun) ->
hackney_bstr:whitespace(Data,
fun (<< $;, Rest/binary >>) ->
hackney_bstr:whitespace(Rest,
fun (Rest2) ->
%% This is a non-strict parsing clause required by some user agents
%% that use the wrong delimiter putting a charset where a qparam is
%% expected.
try qparam(Rest2, Fun) of
Result -> Result
catch
error:function_clause ->
Fun(<<",", Rest2/binary>>, 1000)
end
end);
(Rest) ->
Fun(Rest, 1000)
end).
%% @doc Parse a quality parameter string (for example q=0.500).
-spec qparam(binary(), fun()) -> any().
qparam(<< Q, $=, Data/binary >>, Fun) when Q =:= $q; Q =:= $Q ->
qvalue(Data, Fun).
%% @doc Parse either a list of entity tags or a "*".
-spec entity_tag_match(binary()) -> any().
entity_tag_match(<< $*, Rest/binary >>) ->
hackney_bstr:whitespace(Rest,
fun (<<>>) -> '*';
(_Any) -> {error, badarg}
end);
entity_tag_match(Data) ->
hackney_bstr:nonempty_list(Data, fun entity_tag/2).
%% @doc Parse an entity-tag.
-spec entity_tag(binary(), fun()) -> any().
entity_tag(<< "W/", Rest/binary >>, Fun) ->
opaque_tag(Rest, Fun, weak);
entity_tag(Data, Fun) ->
opaque_tag(Data, Fun, strong).
-spec opaque_tag(binary(), fun(), weak | strong) -> any().
opaque_tag(Data, Fun, Strength) ->
hackney_bstr:quoted_string(Data,
fun (_Rest, <<>>) -> {error, badarg};
(Rest, OpaqueTag) -> Fun(Rest, {Strength, OpaqueTag})
end).
%% @doc Parse an expectation.
-spec expectation(binary(), fun()) -> any().
expectation(Data, Fun) ->
hackney_bstr:token_ci(Data,
fun (_Rest, <<>>) -> {error, badarg};
(<< $=, Rest/binary >>, Expectation) ->
hackney_bstr:word(Rest,
fun (Rest2, ExtValue) ->
hackney_bstr:params(Rest2, fun (Rest3, ExtParams) ->
Fun(Rest3, {Expectation, ExtValue, ExtParams})
end)
end);
(Rest, Expectation) ->
Fun(Rest, Expectation)
end).
%% @doc Parse a quality value.
-spec qvalue(binary(), fun()) -> any().
qvalue(<< $0, $., Rest/binary >>, Fun) ->
qvalue(Rest, Fun, 0, 100);
%% Some user agents use q=.x instead of q=0.x
qvalue(<< $., Rest/binary >>, Fun) ->
qvalue(Rest, Fun, 0, 100);
qvalue(<< $0, Rest/binary >>, Fun) ->
Fun(Rest, 0);
qvalue(<< $1, $., $0, $0, $0, Rest/binary >>, Fun) ->
Fun(Rest, 1000);
qvalue(<< $1, $., $0, $0, Rest/binary >>, Fun) ->
Fun(Rest, 1000);
qvalue(<< $1, $., $0, Rest/binary >>, Fun) ->
Fun(Rest, 1000);
qvalue(<< $1, Rest/binary >>, Fun) ->
Fun(Rest, 1000);
qvalue(_Data, _Fun) ->
{error, badarg}.
-spec qvalue(binary(), fun(), integer(), 1 | 10 | 100) -> any().
qvalue(Data, Fun, Q, 0) ->
Fun(Data, Q);
qvalue(<< C, Rest/binary >>, Fun, Q, M)
when C >= $0, C =< $9 ->
qvalue(Rest, Fun, Q + (C - $0) * M, M div 10);
qvalue(Data, Fun, Q, _M) ->
Fun(Data, Q).
%% @doc Parse authorization value according rfc 2617.
%% Only Basic authorization is supported so far.
-spec authorization(binary(), binary()) -> {binary(), any()} | {error, badarg}.
authorization(UserPass, Type = <<"basic">>) ->
hackney_bstr:whitespace(UserPass,
fun(D) ->
authorization_basic_userid(base64:mime_decode(D),
fun(Rest, Userid) ->
authorization_basic_password(Rest,
fun(Password) ->
{Type, {Userid, Password}}
end)
end)
end);
authorization(String, Type) ->
hackney_bstr:whitespace(String, fun(Rest) -> {Type, Rest} end).
%% @doc Parse user credentials.
-spec authorization_basic_userid(binary(), fun()) -> any().
authorization_basic_userid(Data, Fun) ->
authorization_basic_userid(Data, Fun, <<>>).
authorization_basic_userid(<<>>, _Fun, _Acc) ->
{error, badarg};
authorization_basic_userid(<<C, _Rest/binary>>, _Fun, Acc)
when C < 32; C =:= 127; (C =:=$: andalso Acc =:= <<>>) ->
{error, badarg};
authorization_basic_userid(<<$:, Rest/binary>>, Fun, Acc) ->
Fun(Rest, Acc);
authorization_basic_userid(<<C, Rest/binary>>, Fun, Acc) ->
authorization_basic_userid(Rest, Fun, <<Acc/binary, C>>).
-spec authorization_basic_password(binary(), fun()) -> any().
authorization_basic_password(Data, Fun) ->
authorization_basic_password(Data, Fun, <<>>).
authorization_basic_password(<<>>, _Fun, <<>>) ->
{error, badarg};
authorization_basic_password(<<C, _Rest/binary>>, _Fun, _Acc)
when C < 32; C=:= 127 ->
{error, badarg};
authorization_basic_password(<<>>, Fun, Acc) ->
Fun(Acc);
authorization_basic_password(<<C, Rest/binary>>, Fun, Acc) ->
authorization_basic_password(Rest, Fun, <<Acc/binary, C>>).
%% @doc Parse range header according rfc 2616.
-spec range(binary()) -> {Unit, [Range]} | {error, badarg} when
Unit :: binary(),
Range :: {non_neg_integer(), non_neg_integer() | infinity} | neg_integer().
range(Data) ->
hackney_bstr:token_ci(Data, fun range/2).
range(Data, Token) ->
hackney_bstr:whitespace(Data,
fun(<<"=", Rest/binary>>) ->
case hackney_bstr:list(Rest, fun range_beginning/2) of
{error, badarg} ->
{error, badarg};
Ranges ->
{Token, Ranges}
end;
(_) ->
{error, badarg}
end).
range_beginning(Data, Fun) ->
range_digits(Data, suffix,
fun(D, RangeBeginning) ->
range_ending(D, Fun, RangeBeginning)
end).
range_ending(Data, Fun, RangeBeginning) ->
hackney_bstr:whitespace(Data,
fun(<<"-", R/binary>>) ->
case RangeBeginning of
suffix ->
range_digits(R, fun(D, RangeEnding) ->
Fun(D, -RangeEnding) end);
_ ->
range_digits(R, infinity, fun(D, RangeEnding) ->
Fun(D, {RangeBeginning, RangeEnding})
end)
end;
(_) ->
{error, badarg}
end).
-spec range_digits(binary(), fun()) -> any().
range_digits(Data, Fun) ->
hackney_bstr:whitespace(Data,
fun(D) ->
hackney_bstr:digits(D, Fun)
end).
-spec range_digits(binary(), any(), fun()) -> any().
range_digits(Data, Default, Fun) ->
hackney_bstr:whitespace(Data,
fun(<< C, Rest/binary >>) when C >= $0, C =< $9 ->
hackney_bstr:digits(Rest, Fun, C - $0);
(_) ->
Fun(Data, Default)
end).
-spec header_default(binary()) -> any().
header_default(<<"transfer-encoding">>) ->
[<<"identity">>];
header_default(_Name) ->
undefined.