Browse Source

Add cowboy_bstr:capitalize_token/1

For optional header name capitalization. See the guide section about it.
Loïc Hoguin 12 years ago
parent
commit
1b996794ee
3 changed files with 71 additions and 3 deletions
  1. 24 0
      guide/internals.md
  2. 1 0
      guide/toc.md
  3. 46 3
      src/cowboy_bstr.erl

+ 24 - 0
guide/internals.md

@@ -6,6 +6,30 @@ Architecture
 
 
 @todo Describe.
 @todo Describe.
 
 
+Lowercase header names
+----------------------
+
+For consistency reasons it has been chosen to convert all header names
+to lowercase binary strings. This prevents the programmer from making
+case mistakes, and is possible because header names are case insensitive.
+
+This works fine for the large majority of clients. However, some badly
+implemented clients, especially ones found in corporate code or closed
+source products, may not handle header names in a case insensitive manner.
+This means that when Cowboy returns lowercase header names, these clients
+will not find the headers they are looking for.
+
+A simple way to solve this is to create an `onresponse` hook that will
+format the header names with the expected case.
+
+``` erlang
+capitalize_hook(Status, Headers, Body, Req) ->
+    Headers2 = [{cowboy_bstr:capitalize_token(N), V}
+        || {N, V} <- Headers],
+    {ok, Req2} = cowboy_req:reply(State, Headers2, Body, Req),
+    Req2.
+```
+
 Efficiency considerations
 Efficiency considerations
 -------------------------
 -------------------------
 
 

+ 1 - 0
guide/toc.md

@@ -51,4 +51,5 @@ Cowboy User Guide
    *  Handler middleware
    *  Handler middleware
  *  [Internals](internals.md)
  *  [Internals](internals.md)
    *  Architecture
    *  Architecture
+   *  Lowercase header names
    *  Efficiency considerations
    *  Efficiency considerations

+ 46 - 3
src/cowboy_bstr.erl

@@ -16,16 +16,40 @@
 -module(cowboy_bstr).
 -module(cowboy_bstr).
 
 
 %% Binary strings.
 %% Binary strings.
+-export([capitalize_token/1]).
 -export([to_lower/1]).
 -export([to_lower/1]).
 
 
 %% Characters.
 %% Characters.
 -export([char_to_lower/1]).
 -export([char_to_lower/1]).
 -export([char_to_upper/1]).
 -export([char_to_upper/1]).
 
 
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-endif.
+
+%% @doc Capitalize a token.
+%%
+%% The first letter and all letters after a dash are capitalized.
+%% This is the form seen for header names in the HTTP/1.1 RFC and
+%% others. Note that using this form isn't required, as header name
+%% are case insensitive, and it is only provided for use with eventual
+%% badly implemented clients.
+-spec capitalize_token(B) -> B when B::binary().
+capitalize_token(B) ->
+	capitalize_token(B, true, <<>>).
+capitalize_token(<<>>, _, Acc) ->
+	Acc;
+capitalize_token(<< $-, Rest/bits >>, _, Acc) ->
+	capitalize_token(Rest, true, << Acc/binary, $- >>);
+capitalize_token(<< C, Rest/bits >>, true, Acc) ->
+	capitalize_token(Rest, false, << Acc/binary, (char_to_upper(C)) >>);
+capitalize_token(<< C, Rest/bits >>, false, Acc) ->
+	capitalize_token(Rest, false, << Acc/binary, (char_to_lower(C)) >>).
+
 %% @doc Convert a binary string to lowercase.
 %% @doc Convert a binary string to lowercase.
--spec to_lower(binary()) -> binary().
-to_lower(L) ->
-	<< << (char_to_lower(C)) >> || << C >> <= L >>.
+-spec to_lower(B) -> B when B::binary().
+to_lower(B) ->
+	<< << (char_to_lower(C)) >> || << C >> <= B >>.
 
 
 %% @doc Convert [A-Z] characters to lowercase.
 %% @doc Convert [A-Z] characters to lowercase.
 %% @end
 %% @end
@@ -88,3 +112,22 @@ char_to_upper($x) -> $X;
 char_to_upper($y) -> $Y;
 char_to_upper($y) -> $Y;
 char_to_upper($z) -> $Z;
 char_to_upper($z) -> $Z;
 char_to_upper(Ch) -> Ch.
 char_to_upper(Ch) -> Ch.
+
+%% Tests.
+
+-ifdef(TEST).
+
+capitalize_token_test_() ->
+	%% {Header, Result}
+	Tests = [
+		{<<"heLLo-woRld">>, <<"Hello-World">>},
+		{<<"Sec-Websocket-Version">>, <<"Sec-Websocket-Version">>},
+		{<<"Sec-WebSocket-Version">>, <<"Sec-Websocket-Version">>},
+		{<<"sec-websocket-version">>, <<"Sec-Websocket-Version">>},
+		{<<"SEC-WEBSOCKET-VERSION">>, <<"Sec-Websocket-Version">>},
+		{<<"Sec-WebSocket--Version">>, <<"Sec-Websocket--Version">>},
+		{<<"Sec-WebSocket---Version">>, <<"Sec-Websocket---Version">>}
+	],
+	[{H, fun() -> R = capitalize_token(H) end} || {H, R} <- Tests].
+
+-endif.