Browse Source

In content-types, the charset parameter is converted to lowercase

We know this specific parameter is case insensitive so we
automatically lowercase it to make things simpler to the
developer.
Loïc Hoguin 12 years ago
parent
commit
4fde6cba94

+ 5 - 4
manual/cowboy_req.md

@@ -176,7 +176,7 @@ Request related exports
 > | accept-language        | `[{LanguageTag, Quality}]`                        |
 > | authorization          | `{AuthType, Credentials}`                         |
 > | content-length         | `non_neg_integer()`                               |
-> | content-type           | `{Type, SubType, Params}`                         |
+> | content-type           | `{Type, SubType, ContentTypeParams}`              |
 > | cookie                 | `[{binary(), binary()}]`                          |
 > | expect                 | `[Expect | {Expect, ExpectValue, Params}]`        |
 > | if-match               | `'*' | [{weak | strong, OpaqueTag}]`              |
@@ -192,7 +192,7 @@ Request related exports
 > Types for the above table:
 >  *  Type = SubType = Charset = Encoding = LanguageTag = binary()
 >  *  AuthType = Expect = OpaqueTag = Unit = binary()
->  *  Params = [{binary(), binary()}]
+>  *  Params = ContentTypeParams = [{binary(), binary()}]
 >  *  Quality = 0..1000
 >  *  AcceptExt = [{binary(), binary()} | binary()]
 >  *  Credentials - see below
@@ -201,8 +201,9 @@ Request related exports
 > The cookie names and values, the values of the sec-websocket-protocol
 > and x-forwarded-for headers, the values in `AcceptExt` and `Params`,
 > the authorization `Credentials`, the `ExpectValue` and `OpaqueTag`
-> are case sensitive. All other values are case insensitive and
-> will be returned as lowercase.
+> are case sensitive. All values in `ContentTypeParams` are case sensitive
+> except the value of the charset parameter, which is case insensitive.
+> All other values are case insensitive and will be returned as lowercase.
 >
 > The headers accept, accept-encoding and cookie headers can return
 > an empty list. Others will return `{error, badarg}` if the header

+ 6 - 2
manual/cowboy_rest.md

@@ -168,7 +168,9 @@ REST callbacks description
 > Cowboy will select the most appropriate content-type from the list.
 > If any parameter is acceptable, then the tuple form should be used
 > with parameters set to `'*'`. If the parameters value is set to `[]`
-> only content-type values with no parameters will be accepted.
+> only content-type values with no parameters will be accepted. All
+> parameter values are treated in a case sensitive manner except the
+> `charset` parameter, if present, which is case insensitive.
 >
 > This function will be called for POST, PUT and PATCH requests.
 > It is entirely possible to define different callbacks for different
@@ -219,7 +221,9 @@ REST callbacks description
 > Cowboy will select the most appropriate content-type from the list.
 > If any parameter is acceptable, then the tuple form should be used
 > with parameters set to `'*'`. If the parameters value is set to `[]`
-> only content-type values with no parameters will be accepted.
+> only content-type values with no parameters will be accepted. All
+> parameter values are treated in a case sensitive manner except the
+> `charset` parameter, if present, which is case insensitive.
 >
 > The `ProvideResource` value is the name of the callback that will
 > be called if the content-type matches. It is defined as follow.

+ 16 - 4
src/cowboy_http.erl

@@ -162,14 +162,26 @@ cookie_value(<< C, Rest/binary >>, Fun, Acc) ->
 	cookie_value(Rest, Fun, << Acc/binary, C >>).
 
 %% @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) ->
-				params(Rest,
-					fun (<<>>, Params) -> {Type, SubType, Params};
-						(_Rest2, _) -> {error, badarg}
-					end)
+			params(Rest,
+				fun (<<>>, Params) ->
+						case lists:keyfind(<<"charset">>, 1, Params) of
+							false ->
+								{Type, SubType, Params};
+							{_, Charset} ->
+								Charset2 = cowboy_bstr:to_lower(Charset),
+								Params2 = lists:keyreplace(<<"charset">>,
+									1, Params, {<<"charset">>, Charset2}),
+								{Type, SubType, Params2}
+						end;
+					(_Rest2, _) ->
+						{error, badarg}
+				end)
 		end).
 
 %% @doc Parse a media range.

+ 12 - 0
test/http_SUITE.erl

@@ -64,6 +64,7 @@
 -export([rest_options_default/1]).
 -export([rest_param_all/1]).
 -export([rest_patch/1]).
+-export([rest_post_charset/1]).
 -export([rest_postonly/1]).
 -export([rest_resource_etags/1]).
 -export([rest_resource_etags_if_none_match/1]).
@@ -138,6 +139,7 @@ groups() ->
 		rest_options_default,
 		rest_param_all,
 		rest_patch,
+		rest_post_charset,
 		rest_postonly,
 		rest_resource_etags,
 		rest_resource_etags_if_none_match,
@@ -370,6 +372,7 @@ init_dispatch(Config) ->
 			{"/missing_get_callbacks", rest_missing_callbacks, []},
 			{"/missing_put_callbacks", rest_missing_callbacks, []},
 			{"/nodelete", rest_nodelete_resource, []},
+			{"/post_charset", rest_post_charset_resource, []},
 			{"/postonly", rest_postonly_resource, []},
 			{"/patch", rest_patch_resource, []},
 			{"/resetags", rest_resource_etags, []},
@@ -999,6 +1002,15 @@ rest_patch(Config) ->
 		ok
 	end || {Status, Headers, Body} <- Tests].
 
+rest_post_charset(Config) ->
+	Client = ?config(client, Config),
+	Headers = [
+		{<<"content-type">>, <<"text/plain;charset=UTF-8">>}
+	],
+	{ok, Client2} = cowboy_client:request(<<"POST">>,
+		build_url("/post_charset", Config), Headers, "12345", Client),
+	{ok, 204, _, _} = cowboy_client:response(Client2).
+
 rest_postonly(Config) ->
 	Client = ?config(client, Config),
 	Headers = [

+ 15 - 0
test/http_SUITE_data/rest_post_charset_resource.erl

@@ -0,0 +1,15 @@
+-module(rest_post_charset_resource).
+-export([init/3, allowed_methods/2, content_types_accepted/2, from_text/2]).
+
+init(_Transport, _Req, _Opts) ->
+	{upgrade, protocol, cowboy_rest}.
+
+allowed_methods(Req, State) ->
+	{[<<"POST">>], Req, State}.
+
+content_types_accepted(Req, State) ->
+	{[{{<<"text">>, <<"plain">>, [{<<"charset">>, <<"utf-8">>}]},
+		from_text}], Req, State}.
+
+from_text(Req, State) ->
+	{true, Req, State}.