summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFilipe David Borba Manana <fdmanana@apache.org>2010-11-12 20:15:25 +0000
committerFilipe David Borba Manana <fdmanana@apache.org>2010-11-12 20:15:25 +0000
commit0264c51de7f281bc3b01d51e43831da2bcc741df (patch)
tree7ee5b7831007a1abe59071d71e868c4e26901f1d
parentf3329af215151bc949214fda9d44f7b9995da367 (diff)
Backporting issue 21 from the official Mochiweb repository:
correctly determining (as specified by RFC2616) if a request accepts a specific media type for the Content-Type of the response. https://github.com/mochi/mochiweb/issues/closed#issue/21 git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@1034553 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r--src/mochiweb/mochiweb_request.erl53
-rw-r--r--src/mochiweb/mochiweb_util.erl79
2 files changed, 109 insertions, 23 deletions
diff --git a/src/mochiweb/mochiweb_request.erl b/src/mochiweb/mochiweb_request.erl
index 1cf96160..ffe4e9eb 100644
--- a/src/mochiweb/mochiweb_request.erl
+++ b/src/mochiweb/mochiweb_request.erl
@@ -21,6 +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]).
-define(SAVE_QS, mochiweb_request_qs).
-define(SAVE_PATH, mochiweb_request_path).
@@ -707,6 +708,58 @@ accepted_encodings(SupportedEncodings) ->
)
end.
+%% @spec accepts_content_type(string() | binary()) -> boolean() | bad_accept_header
+%%
+%% @doc Determines whether a request accepts a given media type by analyzing its
+%% "Accept" header.
+%%
+%% Examples
+%%
+%% 1) For a missing "Accept" header:
+%% accepts_content_type("application/json") -> true
+%%
+%% 2) For an "Accept" header with value "text/plain, application/*":
+%% accepts_content_type("application/json") -> true
+%%
+%% 3) For an "Accept" header with value "text/plain, */*; q=0.0":
+%% accepts_content_type("application/json") -> false
+%%
+%% 4) For an "Accept" header with value "text/plain; q=0.5, */*; q=0.1":
+%% accepts_content_type("application/json") -> true
+%%
+%% 5) For an "Accept" header with value "text/*; q=0.0, */*":
+%% accepts_content_type("text/plain") -> false
+%%
+accepts_content_type(ContentType) when is_binary(ContentType) ->
+ accepts_content_type(binary_to_list(ContentType));
+accepts_content_type(ContentType1) ->
+ ContentType = re:replace(ContentType1, "\\s", "", [global, {return, list}]),
+ AcceptHeader = case get_header_value("Accept") of
+ undefined ->
+ "*/*";
+ Value ->
+ Value
+ end,
+ case mochiweb_util:parse_qvalues(AcceptHeader) of
+ invalid_qvalue_string ->
+ bad_accept_header;
+ QList ->
+ [MainType, _SubType] = string:tokens(ContentType, "/"),
+ SuperType = MainType ++ "/*",
+ lists:any(
+ fun({"*/*", Q}) when Q > 0.0 ->
+ true;
+ ({Type, Q}) when Q > 0.0 ->
+ Type =:= ContentType orelse Type =:= SuperType;
+ (_) ->
+ false
+ end,
+ QList
+ ) andalso
+ (not lists:member({ContentType, 0.0}, QList)) andalso
+ (not lists:member({SuperType, 0.0}, QList))
+ end.
+
%%
%% Tests
%%
diff --git a/src/mochiweb/mochiweb_util.erl b/src/mochiweb/mochiweb_util.erl
index d1cc59de..62ff0d06 100644
--- a/src/mochiweb/mochiweb_util.erl
+++ b/src/mochiweb/mochiweb_util.erl
@@ -414,7 +414,8 @@ shell_quote([C | Rest], Acc) ->
shell_quote(Rest, [C | Acc]).
%% @spec parse_qvalues(string()) -> [qvalue()] | invalid_qvalue_string
-%% @type qvalue() = {encoding(), float()}.
+%% @type qvalue() = {media_type() | encoding() , float()}.
+%% @type media_type() = string().
%% @type encoding() = string().
%%
%% @doc Parses a list (given as a string) of elements with Q values associated
@@ -422,7 +423,7 @@ shell_quote([C | Rest], Acc) ->
%% from its Q value by a semicolon. Q values are optional but when missing
%% the value of an element is considered as 1.0. A Q value is always in the
%% range [0.0, 1.0]. A Q value list is used for example as the value of the
-%% HTTP "Accept-Encoding" header.
+%% HTTP "Accept" and "Accept-Encoding" headers.
%%
%% Q values are described in section 2.9 of the RFC 2616 (HTTP 1.1).
%%
@@ -433,29 +434,12 @@ shell_quote([C | Rest], Acc) ->
%%
parse_qvalues(QValuesStr) ->
try
- {ok, Re} = re:compile("^\\s*q\\s*=\\s*((?:0|1)(?:\\.\\d{1,3})?)\\s*$"),
lists:map(
fun(Pair) ->
- case string:tokens(Pair, ";") of
- [Enc] ->
- {string:strip(Enc), 1.0};
- [Enc, QStr] ->
- case re:run(QStr, Re, [{capture, [1], list}]) of
- {match, [Q]} ->
- QVal = case Q of
- "0" ->
- 0.0;
- "1" ->
- 1.0;
- Else ->
- list_to_float(Else)
- end,
- case QVal < 0.0 orelse QVal > 1.0 of
- false ->
- {string:strip(Enc), QVal}
- end
- end
- end
+ [Type | Params] = string:tokens(Pair, ";"),
+ NormParams = normalize_media_params(Params),
+ {Q, NonQParams} = extract_q(NormParams),
+ {string:join([string:strip(Type) | NonQParams], ";"), Q}
end,
string:tokens(string:to_lower(QValuesStr), ",")
)
@@ -464,6 +448,46 @@ parse_qvalues(QValuesStr) ->
invalid_qvalue_string
end.
+normalize_media_params(Params) ->
+ {ok, Re} = re:compile("\\s"),
+ normalize_media_params(Re, Params, []).
+
+normalize_media_params(_Re, [], Acc) ->
+ lists:reverse(Acc);
+normalize_media_params(Re, [Param | Rest], Acc) ->
+ NormParam = re:replace(Param, Re, "", [global, {return, list}]),
+ normalize_media_params(Re, Rest, [NormParam | Acc]).
+
+extract_q(NormParams) ->
+ {ok, KVRe} = re:compile("^([^=]+)=([^=]+)$"),
+ {ok, QRe} = re:compile("^((?:0|1)(?:\\.\\d{1,3})?)$"),
+ extract_q(KVRe, QRe, NormParams, []).
+
+extract_q(_KVRe, _QRe, [], Acc) ->
+ {1.0, lists:reverse(Acc)};
+extract_q(KVRe, QRe, [Param | Rest], Acc) ->
+ case re:run(Param, KVRe, [{capture, [1, 2], list}]) of
+ {match, [Name, Value]} ->
+ case Name of
+ "q" ->
+ {match, [Q]} = re:run(Value, QRe, [{capture, [1], list}]),
+ QVal = case Q of
+ "0" ->
+ 0.0;
+ "1" ->
+ 1.0;
+ Else ->
+ list_to_float(Else)
+ end,
+ case QVal < 0.0 orelse QVal > 1.0 of
+ false ->
+ {QVal, lists:reverse(Acc) ++ Rest}
+ end;
+ _ ->
+ extract_q(KVRe, QRe, Rest, [Param | Acc])
+ end
+ end.
+
%% @spec pick_accepted_encodings([qvalue()], [encoding()], encoding()) ->
%% [encoding()]
%%
@@ -822,11 +846,20 @@ parse_qvalues_test() ->
),
[{"gzip", 0.5}, {"deflate", 1.0}, {"identity", 1.0}, {"identity", 1.0}] =
parse_qvalues("gzip; q=0.5,deflate,identity, identity "),
+ [{"text/html;level=1", 1.0}, {"text/plain", 0.5}] =
+ parse_qvalues("text/html;level=1, text/plain;q=0.5"),
+ [{"text/html;level=1", 0.3}, {"text/plain", 1.0}] =
+ parse_qvalues("text/html;level=1;q=0.3, text/plain"),
+ [{"text/html;level=1", 0.3}, {"text/plain", 1.0}] =
+ parse_qvalues("text/html; level = 1; q = 0.3, text/plain"),
+ [{"text/html;level=1", 0.3}, {"text/plain", 1.0}] =
+ parse_qvalues("text/html;q=0.3;level=1, text/plain"),
invalid_qvalue_string = parse_qvalues("gzip; q=1.1, deflate"),
invalid_qvalue_string = parse_qvalues("gzip; q=0.5, deflate;q=2"),
invalid_qvalue_string = parse_qvalues("gzip, deflate;q=AB"),
invalid_qvalue_string = parse_qvalues("gzip; q=2.1, deflate"),
invalid_qvalue_string = parse_qvalues("gzip; q=0.1234, deflate"),
+ invalid_qvalue_string = parse_qvalues("text/html;level=1;q=0.3, text/html;level"),
ok.
pick_accepted_encodings_test() ->