cow_http_hd.erl 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. %% Copyright (c) 2014, Loïc Hoguin <essen@ninenines.eu>
  2. %%
  3. %% Permission to use, copy, modify, and/or distribute this software for any
  4. %% purpose with or without fee is hereby granted, provided that the above
  5. %% copyright notice and this permission notice appear in all copies.
  6. %%
  7. %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  8. %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  9. %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  10. %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  11. %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  12. %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  13. %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  14. -module(cow_http_hd).
  15. -export([parse_connection/1]).
  16. -export([parse_content_length/1]).
  17. -export([parse_transfer_encoding/1]).
  18. -include("cow_inline.hrl").
  19. %% @doc Parse the Connection header.
  20. -spec parse_connection(binary()) -> [binary()].
  21. parse_connection(<<"close">>) ->
  22. [<<"close">>];
  23. parse_connection(<<"keep-alive">>) ->
  24. [<<"keep-alive">>];
  25. parse_connection(Connection) ->
  26. nonempty(token_ci_list(Connection, [])).
  27. -ifdef(TEST).
  28. parse_connection_test_() ->
  29. Tests = [
  30. {<<"close">>, [<<"close">>]},
  31. {<<"ClOsE">>, [<<"close">>]},
  32. {<<"Keep-Alive">>, [<<"keep-alive">>]},
  33. {<<"keep-alive, Upgrade">>, [<<"keep-alive">>, <<"upgrade">>]}
  34. ],
  35. [{V, fun() -> R = parse_connection(V) end} || {V, R} <- Tests].
  36. -endif.
  37. -ifdef(PERF).
  38. horse_parse_connection_close() ->
  39. horse:repeat(200000,
  40. parse_connection(<<"close">>)
  41. ).
  42. horse_parse_connection_keepalive() ->
  43. horse:repeat(200000,
  44. parse_connection(<<"keep-alive">>)
  45. ).
  46. horse_parse_connection_keepalive_upgrade() ->
  47. horse:repeat(200000,
  48. parse_connection(<<"keep-alive, upgrade">>)
  49. ).
  50. -endif.
  51. %% @doc Parse the Content-Length header.
  52. %%
  53. %% The value has at least one digit, and may be followed by whitespace.
  54. -spec parse_content_length(binary()) -> non_neg_integer().
  55. parse_content_length(<< $0 >>) -> 0;
  56. parse_content_length(<< $0, R/bits >>) -> number(R, 0);
  57. parse_content_length(<< $1, R/bits >>) -> number(R, 1);
  58. parse_content_length(<< $2, R/bits >>) -> number(R, 2);
  59. parse_content_length(<< $3, R/bits >>) -> number(R, 3);
  60. parse_content_length(<< $4, R/bits >>) -> number(R, 4);
  61. parse_content_length(<< $5, R/bits >>) -> number(R, 5);
  62. parse_content_length(<< $6, R/bits >>) -> number(R, 6);
  63. parse_content_length(<< $7, R/bits >>) -> number(R, 7);
  64. parse_content_length(<< $8, R/bits >>) -> number(R, 8);
  65. parse_content_length(<< $9, R/bits >>) -> number(R, 9).
  66. -ifdef(TEST).
  67. parse_content_length_test_() ->
  68. Tests = [
  69. {<<"0">>, 0},
  70. {<<"42 ">>, 42},
  71. {<<"69\t">>, 69},
  72. {<<"1337">>, 1337},
  73. {<<"1234567890">>, 1234567890},
  74. {<<"1234567890 ">>, 1234567890}
  75. ],
  76. [{V, fun() -> R = parse_content_length(V) end} || {V, R} <- Tests].
  77. -endif.
  78. -ifdef(PERF).
  79. horse_parse_content_length_zero() ->
  80. horse:repeat(100000,
  81. parse_content_length(<<"0">>)
  82. ).
  83. horse_parse_content_length_giga() ->
  84. horse:repeat(100000,
  85. parse_content_length(<<"1234567890">>)
  86. ).
  87. -endif.
  88. %% @doc Parse the Transfer-Encoding header.
  89. %%
  90. %% @todo Extension parameters.
  91. -spec parse_transfer_encoding(binary()) -> [binary()].
  92. parse_transfer_encoding(<<"chunked">>) ->
  93. [<<"chunked">>];
  94. parse_transfer_encoding(TransferEncoding) ->
  95. nonempty(token_ci_list(TransferEncoding, [])).
  96. -ifdef(TEST).
  97. parse_transfer_encoding_test_() ->
  98. Tests = [
  99. {<<"a , , , ">>, [<<"a">>]},
  100. {<<" , , , a">>, [<<"a">>]},
  101. {<<"a , , b">>, [<<"a">>, <<"b">>]},
  102. {<<"chunked">>, [<<"chunked">>]},
  103. {<<"chunked, something">>, [<<"chunked">>, <<"something">>]}
  104. ],
  105. [{V, fun() -> R = parse_transfer_encoding(V) end} || {V, R} <- Tests].
  106. parse_transfer_encoding_error_test_() ->
  107. Tests = [
  108. <<>>,
  109. <<" ">>,
  110. <<" , ">>,
  111. <<",,,">>,
  112. <<"a b">>
  113. ],
  114. [{V, fun() -> {'EXIT', _} = (catch parse_transfer_encoding(V)) end}
  115. || V <- Tests].
  116. -endif.
  117. -ifdef(PERF).
  118. horse_parse_transfer_encoding_chunked() ->
  119. horse:repeat(200000,
  120. parse_transfer_encoding(<<"chunked">>)
  121. ).
  122. horse_parse_transfer_encoding_custom() ->
  123. horse:repeat(200000,
  124. parse_transfer_encoding(<<"chunked, something">>)
  125. ).
  126. -endif.
  127. %% Internal.
  128. %% Only return if the list is not empty.
  129. nonempty(L) when L =/= [] -> L.
  130. %% Parse a number optionally followed by whitespace.
  131. number(<< $0, R/bits >>, Acc) -> number(R, Acc * 10);
  132. number(<< $1, R/bits >>, Acc) -> number(R, Acc * 10 + 1);
  133. number(<< $2, R/bits >>, Acc) -> number(R, Acc * 10 + 2);
  134. number(<< $3, R/bits >>, Acc) -> number(R, Acc * 10 + 3);
  135. number(<< $4, R/bits >>, Acc) -> number(R, Acc * 10 + 4);
  136. number(<< $5, R/bits >>, Acc) -> number(R, Acc * 10 + 5);
  137. number(<< $6, R/bits >>, Acc) -> number(R, Acc * 10 + 6);
  138. number(<< $7, R/bits >>, Acc) -> number(R, Acc * 10 + 7);
  139. number(<< $8, R/bits >>, Acc) -> number(R, Acc * 10 + 8);
  140. number(<< $9, R/bits >>, Acc) -> number(R, Acc * 10 + 9);
  141. number(<< $\s, R/bits >>, Acc) -> ws_end(R), Acc;
  142. number(<< $\t, R/bits >>, Acc) -> ws_end(R), Acc;
  143. number(<<>>, Acc) -> Acc.
  144. ws_end(<< $\s, R/bits >>) -> ws_end(R);
  145. ws_end(<< $\t, R/bits >>) -> ws_end(R);
  146. ws_end(<<>>) -> ok.
  147. %% Parse a list of case insensitive tokens.
  148. token_ci_list(<<>>, Acc) -> lists:reverse(Acc);
  149. token_ci_list(<< $\s, R/bits >>, Acc) -> token_ci_list(R, Acc);
  150. token_ci_list(<< $\t, R/bits >>, Acc) -> token_ci_list(R, Acc);
  151. token_ci_list(<< $,, R/bits >>, Acc) -> token_ci_list(R, Acc);
  152. token_ci_list(<< C, R/bits >>, Acc) ->
  153. case C of
  154. ?INLINE_LOWERCASE(token_ci_list, R, Acc, <<>>)
  155. end.
  156. token_ci_list(<<>>, Acc, T) -> lists:reverse([T|Acc]);
  157. token_ci_list(<< $\s, R/bits >>, Acc, T) -> token_ci_list_sep(R, Acc, T);
  158. token_ci_list(<< $\t, R/bits >>, Acc, T) -> token_ci_list_sep(R, Acc, T);
  159. token_ci_list(<< $,, R/bits >>, Acc, T) -> token_ci_list(R, [T|Acc]);
  160. token_ci_list(<< C, R/bits >>, Acc, T) ->
  161. case C of
  162. ?INLINE_LOWERCASE(token_ci_list, R, Acc, T)
  163. end.
  164. token_ci_list_sep(<<>>, Acc, T) -> lists:reverse([T|Acc]);
  165. token_ci_list_sep(<< $\s, R/bits >>, Acc, T) -> token_ci_list_sep(R, Acc, T);
  166. token_ci_list_sep(<< $\t, R/bits >>, Acc, T) -> token_ci_list_sep(R, Acc, T);
  167. token_ci_list_sep(<< $,, R/bits >>, Acc, T) -> token_ci_list(R, [T|Acc]).