Current section

Files

Jump to
accept src accept_encoding_header.erl
Raw

src/accept_encoding_header.erl

-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-module(accept_encoding_header).
?MODULEDOC("For parsing and negotiating Accept-Encoding headers.").
-export([parse/1, negotiate/2]).
-include("accept.hrl").
?DOC("Accept options.").
-type accept_option() :: #accept_option{}.
?DOC("Media ranges.").
-type media_range() :: #media_range{}.
?DOC("Content codings.").
-type content_coding() :: #content_coding{}.
-export_type([accept_option/0, media_range/0, content_coding/0]).
%%====================================================================
%% Public API
%%====================================================================
?DOC("Parses Accept-Encoding header, returns a list of content_codings.").
-spec parse(AcceptString) -> Result when
AcceptString :: binary() | string(),
Result :: [content_coding()].
parse(AcceptString) ->
accept_parser:map_options(fun parse_content_coding/1, AcceptString).
?DOC("Negotiates the most appropriate content_coding given the accept header").
-spec negotiate(Header, Alternatives) -> Match when
Header :: undefined | binary() | string(),
Alternatives :: [Alternative],
Alternative :: binary() | {binary(), Tag},
Tag :: any(),
Match :: Tag | nomatch.
negotiate(undefined, Alternatives) ->
case Alternatives of
[H | _] -> H;
_ -> Alternatives
end;
negotiate([], _) ->
<<"identity">>;
negotiate(Header, Alternatives) ->
CCs = parse(Header),
case CCs of
[] -> <<"identity">>;
_ -> negotiate_(CCs, Alternatives)
end.
%%====================================================================
%% Private Parts
%%====================================================================
negotiate_(CCs, Alternatives) ->
Alts = lists:map(
fun(Alt) ->
{A, Tag} = accept_neg:alt_tag(Alt),
PA = parse_content_coding(accept_parser:ensure_string(A)),
%% list of Alt-CC scores
AltCCScores = lists:map(
fun(CC) ->
{score_alt(CC, PA), CC}
end,
CCs
),
%% best Content Coding match for this Alternative
[{Score, BCC} | _] = accept_neg:sort_scored(AltCCScores),
case Score of
0 ->
{-1, Tag};
_ ->
#content_coding{q = BCCQ} = BCC,
{BCCQ, Tag}
end
end,
Alternatives
),
{Q, Tag} = accept_neg:find_preferred_best(Alts),
case Q of
Q when Q =< 0 -> fallback_to_identity(CCs);
_ -> Tag
end.
parse_content_coding(#accept_option{
option = Coding,
q = Q,
params = Params
}) ->
%% RFC [https://tools.ietf.org/html/rfc7230#section-4] says
%% that params are allowed only for 'not standard' coding
%% this can be good TODO item
#content_coding{
coding = Coding,
q = Q,
params = Params
};
parse_content_coding(String) ->
parse_content_coding(accept_parser:parse_option(String)).
fallback_to_identity(CCs) ->
%% "identity is always valid
%% unless identity; q=0 or *;q=0
identity_fallback(CCs, ["identity", "*"]).
identity_fallback(_, []) ->
<<"identity">>;
identity_fallback(CCs, [C | R]) ->
case lists:keyfind(C, #content_coding.coding, CCs) of
#content_coding{q = 0} ->
undefined;
#content_coding{} ->
<<"identity">>;
false ->
identity_fallback(CCs, R)
end.
score_alt(
#content_coding{
coding = Coding,
params = CCParams
},
#content_coding{
coding = Coding,
params = AltParams
}
) ->
4 + accept_neg:score_params(CCParams, AltParams);
score_alt(
#content_coding{coding = "*"},
_
) ->
4;
score_alt(_, _) ->
0.