diff --git a/src/mochiweb_request.erl b/src/mochiweb_request.erl index 70e95569..e35ac252 100644 --- a/src/mochiweb_request.erl +++ b/src/mochiweb_request.erl @@ -21,7 +21,7 @@ -export([parse_cookie/0, get_cookie_value/1]). -export([serve_file/2, serve_file/3]). -export([accepted_encodings/1]). --export([accepts_content_type/1]). +-export([accepts_content_type/1, accepted_content_types/1]). -define(SAVE_QS, mochiweb_request_qs). -define(SAVE_PATH, mochiweb_request_path). @@ -729,12 +729,7 @@ accepted_encodings(SupportedEncodings) -> %% accepts_content_type(ContentType1) -> ContentType = re:replace(ContentType1, "\\s", "", [global, {return, list}]), - AcceptHeader = case get_header_value("Accept") of - undefined -> - "*/*"; - Value -> - Value - end, + AcceptHeader = accept_header(), case mochiweb_util:parse_qvalues(AcceptHeader) of invalid_qvalue_string -> bad_accept_header; @@ -755,6 +750,80 @@ accepts_content_type(ContentType1) -> (not lists:member({SuperType, 0.0}, QList)) end. +%% @spec accepted_content_types([string() | binary()]) -> [string()] | bad_accept_header +%% +%% @doc Filters which of the given media types this request accepts. This filtering +%% is performed by analyzing the "Accept" header. The returned list is sorted +%% according to the preferences specified in the "Accept" header (higher Q values +%% first). If two or more types have the same preference (Q value), they're order +%% in the returned list is the same as they're order in the input list. +%% +%% Examples +%% +%% 1) For a missing "Accept" header: +%% accepted_content_types(["text/html", "application/json"]) -> +%% ["text/html", "application/json"] +%% +%% 2) For an "Accept" header with value "text/html, application/*": +%% accepted_content_types(["application/json", "text/html"]) -> +%% ["application/json", "text/html"] +%% +%% 3) For an "Accept" header with value "text/html, */*; q=0.0": +%% accepted_content_types(["text/html", "application/json"]) -> +%% ["text/html"] +%% +%% 4) For an "Accept" header with value "text/html; q=0.5, */*; q=0.1": +%% accepts_content_types(["application/json", "text/html"]) -> +%% ["text/html", "application/json"] +%% +accepted_content_types(Types1) -> + Types = lists:map( + fun(T) -> re:replace(T, "\\s", "", [global, {return, list}]) end, + Types1), + AcceptHeader = accept_header(), + case mochiweb_util:parse_qvalues(AcceptHeader) of + invalid_qvalue_string -> + bad_accept_header; + QList -> + TypesQ = lists:foldr( + fun(T, Acc) -> + case proplists:get_value(T, QList) of + undefined -> + [MainType, _SubType] = string:tokens(T, "/"), + case proplists:get_value(MainType ++ "/*", QList) of + undefined -> + case proplists:get_value("*/*", QList) of + Q when is_float(Q), Q > 0.0 -> + [{Q, T} | Acc]; + _ -> + Acc + end; + Q when Q > 0.0 -> + [{Q, T} | Acc]; + _ -> + Acc + end; + Q when Q > 0.0 -> + [{Q, T} | Acc]; + _ -> + Acc + end + end, + [], Types), + % Note: Stable sort. If 2 types have the same Q value we leave them in the + % same order as in the input list. + SortFun = fun({Q1, _}, {Q2, _}) -> Q1 >= Q2 end, + [Type || {_Q, Type} <- lists:sort(SortFun, TypesQ)] + end. + +accept_header() -> + case get_header_value("Accept") of + undefined -> + "*/*"; + Value -> + Value + end. + %% %% Tests %% diff --git a/src/mochiweb_request_tests.erl b/src/mochiweb_request_tests.erl index 25b07401..73512863 100644 --- a/src/mochiweb_request_tests.erl +++ b/src/mochiweb_request_tests.erl @@ -102,4 +102,51 @@ accepted_encodings_test() -> ?assertEqual([], Req8:accepted_encodings(["gzip", "identity"])). +accepted_content_types_test() -> + Req1 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "text/html"}])), + ?assertEqual(["text/html"], + Req1:accepted_content_types(["text/html", "application/json"])), + + Req2 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "text/html, */*;q=0"}])), + ?assertEqual(["text/html"], + Req2:accepted_content_types(["text/html", "application/json"])), + + Req3 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "text/*, */*;q=0"}])), + ?assertEqual(["text/html"], + Req3:accepted_content_types(["text/html", "application/json"])), + + Req4 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "text/*;q=0.8, */*;q=0.5"}])), + ?assertEqual(["text/html", "application/json"], + Req4:accepted_content_types(["application/json", "text/html"])), + + Req5 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "text/*;q=0.8, */*;q=0.5"}])), + ?assertEqual(["text/html", "application/json"], + Req5:accepted_content_types(["text/html", "application/json"])), + + Req6 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "text/*;q=0.5, */*;q=0.5"}])), + ?assertEqual(["application/json", "text/html"], + Req6:accepted_content_types(["application/json", "text/html"])), + + Req7 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make( + [{"Accept", "text/html;q=0.5, application/json;q=0.5"}])), + ?assertEqual(["application/json", "text/html"], + Req7:accepted_content_types(["application/json", "text/html"])), + + Req8 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "text/html"}])), + ?assertEqual([], + Req8:accepted_content_types(["application/json"])), + + Req9 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1}, + mochiweb_headers:make([{"Accept", "text/*;q=0.9, text/html;q=0.5, */*;q=0.7"}])), + ?assertEqual(["application/json", "text/html"], + Req9:accepted_content_types(["text/html", "application/json"])). + -endif.