Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

169 lines (147 sloc) 6.29 kB
-module(yaws_content_negotiation).
-export([compute_rsva/2, parse_accept_field/1, match_media_type/2]).
-include("../include/yaws_api.hrl").
%% TODO: analyze impact of spaces in headers; when/where to strip?
%% RFC 2296
compute_rsva(Arg, VariantList) ->
Headers = Arg#arg.headers,
Accept = parse_accept_field(Headers#headers.accept),
% move these into #headers{} !
AcceptCharset = parse_accept_field(
get_other_header('Accept-Charset', Headers)),
AcceptLanguage = parse_accept_field(
get_other_header('Accept-Language', Headers)),
AcceptFeatures = [], % yet to be implemented
RankedVariants = overall_quality(VariantList, Accept,
AcceptCharset, AcceptLanguage,
AcceptFeatures),
case determine_result(RankedVariants) of
none ->
%% TODO: build Alternates: header field
VariantList;
URI ->
%% TODO: must be neighbour variant (same root as request)
URI
end.
get_other_header(Name, Headers) ->
case lists:keysearch(Name, 3, Headers#headers.other) of
{value, {_,_,Name,_,Field}} -> Field;
false -> undefined
end.
determine_result(V) ->
determine_result(V, {[], {0,1}}).
determine_result([{URI,{Q,X}}|T], {_,{AccQ,_}}) when Q > AccQ ->
determine_result(T, {URI,{Q,X}});
determine_result([_|T], Acc) ->
determine_result(T, Acc);
determine_result([], {URI,{_,0}}) -> URI;
determine_result([], _) -> none.
overall_quality(VariantList, Accept, AcceptCharset, AcceptLanguage,
AcceptFeatures) ->
overall_quality(VariantList, Accept, AcceptCharset,
AcceptLanguage, AcceptFeatures, []).
overall_quality([{URI,Qs,Attributes}|T], Accept, AcceptCharset,
AcceptLanguage, AcceptFeatures, Acc) ->
%% QX = {Qvalue, [0=definitive, >0=speculative]}
Qt = case {lists:keysearch(type, 1, Attributes), Accept} of
{false, _} -> {1000, 0};
{_, []} -> {1000, 1};
{{value, {type, MediaType}}, Accept} ->
match_media_type(MediaType, Accept)
end,
Qc = case {lists:keysearch(charset, 1, Attributes), AcceptCharset} of
{false, _} -> {1000, 0};
{_, []} -> {1000, 1};
{{value, {charset, Charset}}, AcceptCharset} ->
match_charset(Charset, AcceptCharset)
end,
Ql = case {lists:keysearch(language, 1, Attributes), AcceptLanguage} of
{false, _} -> {1000, 0};
{_, []} -> {1000, 1};
{{value, {language, Languages}}, AcceptLanguage} ->
match_language(Languages, AcceptLanguage)
end,
Qf = {1000, 0}, % Accept-Features: not yet implemented
Q = round5(Qs, Qt, Qc, Ql, Qf),
overall_quality(T, Accept, AcceptCharset, AcceptLanguage,
AcceptFeatures, [{URI, Q}|Acc]);
%% fallback-variant
overall_quality([URI|T], Accept, AcceptCharset, AcceptLanguage,
AcceptFeatures, Acc)
when is_list(URI) ->
Q = {1, 0}, % {0.000001, definitive}
overall_quality(T, Accept, AcceptCharset, AcceptLanguage,
AcceptFeatures, [{URI, Q}|Acc]);
overall_quality([], _Accept, _AcceptCharset, _AcceptLanguage,
_AcceptFeatures, Acc) -> Acc.
%% this functions rounds to five decimal places but to avoid
%% using floats we have 1 represent 0.000001 and 1000000 = 1.0
round5(Qs, {Qt, A}, {Qc, B}, {Ql, C}, {Qf, D}) ->
Q = ((Qs * 1000) * (Qt * 1000) * (Qc * 1000) * (Ql * 1000) * (Qf * 1000)),
{((Q div 10000000000000000000000000) * 10), (A+B+C+D)}.
match_media_type(MediaType, AcceptRanges) ->
case lists:keysearch(MediaType, 1, AcceptRanges) of
{ok, {_, Q}} -> {Q, 0};
false ->
[SimpleMediaType|_] = yaws:split_sep(MediaType, $;),
match_media_type_simple(SimpleMediaType, AcceptRanges)
end.
match_media_type_simple(SimpleMediaType, AcceptRanges) ->
case lists:keysearch(SimpleMediaType, 1, AcceptRanges) of
{ok, {_, Q}} -> {Q, 0};
false ->
[Type|_] = yaws:split_sep(SimpleMediaType, $/),
match_media_type_only(Type, AcceptRanges)
end.
match_media_type_only(Type, AcceptRanges) ->
case lists:keysearch(Type++"/*", 1, AcceptRanges) of
{ok, {_, Q}} -> {Q, 1};
false ->
case lists:keysearch("*/*", 1, AcceptRanges) of
{ok, {_, Q}} -> {Q, 1};
false -> {0, 0}
end
end.
match_charset(Charset, AcceptCharset) ->
case lists:keysearch(Charset, 1, AcceptCharset) of
{ok, {_, Q}} -> {Q, 0};
false -> {0, 0}
end.
match_language(Languages, AcceptLanguage) ->
match_language(Languages, AcceptLanguage, 0).
match_language([Language|T], AcceptLanguage, Qacc) ->
Qacc1 = case lists:keysearch(Language, 1, AcceptLanguage) of
{ok, {_, Q1}} when Q1 > Qacc -> Q1;
_ -> Qacc
end,
Qacc2 = case yaws:split_sep(Language, $-) of
[H|T] when length(T) > 0 ->
case lists:keysearch(H, 1, AcceptLanguage) of
{ok, {_, Q2}} when Q2 > Qacc1 -> Q2;
_ -> Qacc1
end;
[Language] -> Qacc1
end,
match_language(T, AcceptLanguage, Qacc2);
match_language([], AcceptLanguage, Qacc) ->
case lists:keysearch("*", 1, AcceptLanguage) of
{ok, {_, Q}} when Q > Qacc -> {Q, 1};
_ -> {Qacc, 0}
end.
parse_accept_field(undefined) -> [];
parse_accept_field(Field) ->
parse_accept_field(yaws:split_sep(Field, $,), []).
parse_accept_field([H|T], Acc) ->
R = parse_accept_element(yaws:split_sep(H, $;)),
parse_accept_field(T, Acc ++ [R]);
parse_accept_field([], Acc) -> Acc.
parse_accept_element(L) ->
parse_accept_element(L, []).
parse_accept_element(["q="++Q|_], Acc) ->
{Acc, yaws:parse_qvalue(Q)};
parse_accept_element([H|T], []) ->
parse_accept_element(T, H);
parse_accept_element([H|T], Acc) ->
parse_accept_element(T, Acc++";"++H);
parse_accept_element([], Acc) ->
{Acc, 1000}.
Jump to Line
Something went wrong with that request. Please try again.