zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

cow_qs.erl (19554B)


      1 %% Copyright (c) 2013-2018, 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 
     15 -module(cow_qs).
     16 
     17 -export([parse_qs/1]).
     18 -export([qs/1]).
     19 -export([urldecode/1]).
     20 -export([urlencode/1]).
     21 
     22 -type qs_vals() :: [{binary(), binary() | true}].
     23 
     24 %% @doc Parse an application/x-www-form-urlencoded string.
     25 %%
     26 %% The percent decoding is inlined to greatly improve the performance
     27 %% by avoiding copying binaries twice (once for extracting, once for
     28 %% decoding) instead of just extracting the proper representation.
     29 
     30 -spec parse_qs(binary()) -> qs_vals().
     31 parse_qs(B) ->
     32 	parse_qs_name(B, [], <<>>).
     33 
     34 parse_qs_name(<< $%, H, L, Rest/bits >>, Acc, Name) ->
     35 	C = (unhex(H) bsl 4 bor unhex(L)),
     36 	parse_qs_name(Rest, Acc, << Name/bits, C >>);
     37 parse_qs_name(<< $+, Rest/bits >>, Acc, Name) ->
     38 	parse_qs_name(Rest, Acc, << Name/bits, " " >>);
     39 parse_qs_name(<< $=, Rest/bits >>, Acc, Name) when Name =/= <<>> ->
     40 	parse_qs_value(Rest, Acc, Name, <<>>);
     41 parse_qs_name(<< $&, Rest/bits >>, Acc, Name) ->
     42 	case Name of
     43 		<<>> -> parse_qs_name(Rest, Acc, <<>>);
     44 		_ -> parse_qs_name(Rest, [{Name, true}|Acc], <<>>)
     45 	end;
     46 parse_qs_name(<< C, Rest/bits >>, Acc, Name) when C =/= $%, C =/= $= ->
     47 	parse_qs_name(Rest, Acc, << Name/bits, C >>);
     48 parse_qs_name(<<>>, Acc, Name) ->
     49 	case Name of
     50 		<<>> -> lists:reverse(Acc);
     51 		_ -> lists:reverse([{Name, true}|Acc])
     52 	end.
     53 
     54 parse_qs_value(<< $%, H, L, Rest/bits >>, Acc, Name, Value) ->
     55 	C = (unhex(H) bsl 4 bor unhex(L)),
     56 	parse_qs_value(Rest, Acc, Name, << Value/bits, C >>);
     57 parse_qs_value(<< $+, Rest/bits >>, Acc, Name, Value) ->
     58 	parse_qs_value(Rest, Acc, Name, << Value/bits, " " >>);
     59 parse_qs_value(<< $&, Rest/bits >>, Acc, Name, Value) ->
     60 	parse_qs_name(Rest, [{Name, Value}|Acc], <<>>);
     61 parse_qs_value(<< C, Rest/bits >>, Acc, Name, Value) when C =/= $% ->
     62 	parse_qs_value(Rest, Acc, Name, << Value/bits, C >>);
     63 parse_qs_value(<<>>, Acc, Name, Value) ->
     64 	lists:reverse([{Name, Value}|Acc]).
     65 
     66 -ifdef(TEST).
     67 parse_qs_test_() ->
     68 	Tests = [
     69 		{<<>>, []},
     70 		{<<"&">>, []},
     71 		{<<"a">>, [{<<"a">>, true}]},
     72 		{<<"a&">>, [{<<"a">>, true}]},
     73 		{<<"&a">>, [{<<"a">>, true}]},
     74 		{<<"a&b">>, [{<<"a">>, true}, {<<"b">>, true}]},
     75 		{<<"a&&b">>, [{<<"a">>, true}, {<<"b">>, true}]},
     76 		{<<"a&b&">>, [{<<"a">>, true}, {<<"b">>, true}]},
     77 		{<<"=">>, error},
     78 		{<<"=b">>, error},
     79 		{<<"a=">>, [{<<"a">>, <<>>}]},
     80 		{<<"a=b">>, [{<<"a">>, <<"b">>}]},
     81 		{<<"a=&b=">>, [{<<"a">>, <<>>}, {<<"b">>, <<>>}]},
     82 		{<<"a=b&c&d=e">>, [{<<"a">>, <<"b">>},
     83 			{<<"c">>, true}, {<<"d">>, <<"e">>}]},
     84 		{<<"a=b=c&d=e=f&g=h=i">>, [{<<"a">>, <<"b=c">>},
     85 			{<<"d">>, <<"e=f">>}, {<<"g">>, <<"h=i">>}]},
     86 		{<<"+">>, [{<<" ">>, true}]},
     87 		{<<"+=+">>, [{<<" ">>, <<" ">>}]},
     88 		{<<"a+b=c+d">>, [{<<"a b">>, <<"c d">>}]},
     89 		{<<"+a+=+b+&+c+=+d+">>, [{<<" a ">>, <<" b ">>},
     90 			{<<" c ">>, <<" d ">>}]},
     91 		{<<"a%20b=c%20d">>, [{<<"a b">>, <<"c d">>}]},
     92 		{<<"%25%26%3D=%25%26%3D&_-.=.-_">>, [{<<"%&=">>, <<"%&=">>},
     93 			{<<"_-.">>, <<".-_">>}]},
     94 		{<<"for=extend%2Franch">>, [{<<"for">>, <<"extend/ranch">>}]}
     95 	],
     96 	[{Qs, fun() ->
     97 		E = try parse_qs(Qs) of
     98 			R -> R
     99 		catch _:_ ->
    100 			error
    101 		end
    102 	end} || {Qs, E} <- Tests].
    103 
    104 parse_qs_identity_test_() ->
    105 	Tests = [
    106 		<<"+">>,
    107 		<<"hl=en&q=erlang+cowboy">>,
    108 		<<"direction=desc&for=extend%2Franch&sort=updated&state=open">>,
    109 		<<"i=EWiIXmPj5gl6&v=QowBp0oDLQXdd4x_GwiywA&ip=98.20.31.81&"
    110 			"la=en&pg=New8.undertonebrandsafe.com%2F698a2525065ee2"
    111 			"60c0b2f2aaad89ab82&re=&sz=1&fc=1&fr=140&br=3&bv=11.0."
    112 			"696.16&os=3&ov=&rs=vpl&k=cookies%7Csale%7Cbrowser%7Cm"
    113 			"ore%7Cprivacy%7Cstatistics%7Cactivities%7Cauction%7Ce"
    114 			"mail%7Cfree%7Cin...&t=112373&xt=5%7C61%7C0&tz=-1&ev=x"
    115 			"&tk=&za=1&ortb-za=1&zu=&zl=&ax=U&ay=U&ortb-pid=536454"
    116 			".55&ortb-sid=112373.8&seats=999&ortb-xt=IAB24&ortb-ugc=">>,
    117 		<<"i=9pQNskA&v=0ySQQd1F&ev=12345678&t=12345&sz=3&ip=67.58."
    118 			"236.89&la=en&pg=http%3A%2F%2Fwww.yahoo.com%2Fpage1.ht"
    119 			"m&re=http%3A%2F%2Fsearch.google.com&fc=1&fr=1&br=2&bv"
    120 			"=3.0.14&os=1&ov=XP&k=cars%2Cford&rs=js&xt=5%7C22%7C23"
    121 			"4&tz=%2B180&tk=key1%3Dvalue1%7Ckey2%3Dvalue2&zl=4%2C5"
    122 			"%2C6&za=4&zu=competitor.com&ua=Mozilla%2F5.0+%28Windo"
    123 			"ws%3B+U%3B+Windows+NT+6.1%3B+en-US%29+AppleWebKit%2F5"
    124 			"34.13+%28KHTML%2C+like+Gecko%29+Chrome%2F9.0.597.98+S"
    125 			"afari%2F534.13&ortb-za=1%2C6%2C13&ortb-pid=521732&ort"
    126 			"b-sid=521732&ortb-xt=IAB3&ortb-ugc=">>
    127 	],
    128 	[{V, fun() -> V = qs(parse_qs(V)) end} || V <- Tests].
    129 
    130 horse_parse_qs_shorter() ->
    131 	horse:repeat(20000,
    132 		parse_qs(<<"hl=en&q=erlang%20cowboy">>)
    133 	).
    134 
    135 horse_parse_qs_short() ->
    136 	horse:repeat(20000,
    137 		parse_qs(
    138 			<<"direction=desc&for=extend%2Franch&sort=updated&state=open">>)
    139 	).
    140 
    141 horse_parse_qs_long() ->
    142 	horse:repeat(20000,
    143 		parse_qs(<<"i=EWiIXmPj5gl6&v=QowBp0oDLQXdd4x_GwiywA&ip=98.20.31.81&"
    144 			"la=en&pg=New8.undertonebrandsafe.com%2F698a2525065ee260c0b2f2a"
    145 			"aad89ab82&re=&sz=1&fc=1&fr=140&br=3&bv=11.0.696.16&os=3&ov=&rs"
    146 			"=vpl&k=cookies%7Csale%7Cbrowser%7Cmore%7Cprivacy%7Cstatistics%"
    147 			"7Cactivities%7Cauction%7Cemail%7Cfree%7Cin...&t=112373&xt=5%7C"
    148 			"61%7C0&tz=-1&ev=x&tk=&za=1&ortb-za=1&zu=&zl=&ax=U&ay=U&ortb-pi"
    149 			"d=536454.55&ortb-sid=112373.8&seats=999&ortb-xt=IAB24&ortb-ugc"
    150 			"=">>)
    151 	).
    152 
    153 horse_parse_qs_longer() ->
    154 	horse:repeat(20000,
    155 		parse_qs(<<"i=9pQNskA&v=0ySQQd1F&ev=12345678&t=12345&sz=3&ip=67.58."
    156 			"236.89&la=en&pg=http%3A%2F%2Fwww.yahoo.com%2Fpage1.htm&re=http"
    157 			"%3A%2F%2Fsearch.google.com&fc=1&fr=1&br=2&bv=3.0.14&os=1&ov=XP"
    158 			"&k=cars%2cford&rs=js&xt=5%7c22%7c234&tz=%2b180&tk=key1%3Dvalue"
    159 			"1%7Ckey2%3Dvalue2&zl=4,5,6&za=4&zu=competitor.com&ua=Mozilla%2"
    160 			"F5.0%20(Windows%3B%20U%3B%20Windows%20NT%206.1%3B%20en-US)%20A"
    161 			"ppleWebKit%2F534.13%20(KHTML%2C%20like%20Gecko)%20Chrome%2F9.0"
    162 			".597.98%20Safari%2F534.13&ortb-za=1%2C6%2C13&ortb-pid=521732&o"
    163 			"rtb-sid=521732&ortb-xt=IAB3&ortb-ugc=">>)
    164 	).
    165 -endif.
    166 
    167 %% @doc Build an application/x-www-form-urlencoded string.
    168 
    169 -spec qs(qs_vals()) -> binary().
    170 qs([]) ->
    171 	<<>>;
    172 qs(L) ->
    173 	qs(L, <<>>).
    174 
    175 qs([], Acc) ->
    176 	<< $&, Qs/bits >> = Acc,
    177 	Qs;
    178 qs([{Name, true}|Tail], Acc) ->
    179 	Acc2 = urlencode(Name, << Acc/bits, $& >>),
    180 	qs(Tail, Acc2);
    181 qs([{Name, Value}|Tail], Acc) ->
    182 	Acc2 = urlencode(Name, << Acc/bits, $& >>),
    183 	Acc3 = urlencode(Value, << Acc2/bits, $= >>),
    184 	qs(Tail, Acc3).
    185 
    186 -define(QS_SHORTER, [
    187 	{<<"hl">>, <<"en">>},
    188 	{<<"q">>, <<"erlang cowboy">>}
    189 ]).
    190 
    191 -define(QS_SHORT, [
    192 	{<<"direction">>, <<"desc">>},
    193 	{<<"for">>, <<"extend/ranch">>},
    194 	{<<"sort">>, <<"updated">>},
    195 	{<<"state">>, <<"open">>}
    196 ]).
    197 
    198 -define(QS_LONG, [
    199 	{<<"i">>, <<"EWiIXmPj5gl6">>},
    200 	{<<"v">>, <<"QowBp0oDLQXdd4x_GwiywA">>},
    201 	{<<"ip">>, <<"98.20.31.81">>},
    202 	{<<"la">>, <<"en">>},
    203 	{<<"pg">>, <<"New8.undertonebrandsafe.com/"
    204 		"698a2525065ee260c0b2f2aaad89ab82">>},
    205 	{<<"re">>, <<>>},
    206 	{<<"sz">>, <<"1">>},
    207 	{<<"fc">>, <<"1">>},
    208 	{<<"fr">>, <<"140">>},
    209 	{<<"br">>, <<"3">>},
    210 	{<<"bv">>, <<"11.0.696.16">>},
    211 	{<<"os">>, <<"3">>},
    212 	{<<"ov">>, <<>>},
    213 	{<<"rs">>, <<"vpl">>},
    214 	{<<"k">>, <<"cookies|sale|browser|more|privacy|statistics|"
    215 		"activities|auction|email|free|in...">>},
    216 	{<<"t">>, <<"112373">>},
    217 	{<<"xt">>, <<"5|61|0">>},
    218 	{<<"tz">>, <<"-1">>},
    219 	{<<"ev">>, <<"x">>},
    220 	{<<"tk">>, <<>>},
    221 	{<<"za">>, <<"1">>},
    222 	{<<"ortb-za">>, <<"1">>},
    223 	{<<"zu">>, <<>>},
    224 	{<<"zl">>, <<>>},
    225 	{<<"ax">>, <<"U">>},
    226 	{<<"ay">>, <<"U">>},
    227 	{<<"ortb-pid">>, <<"536454.55">>},
    228 	{<<"ortb-sid">>, <<"112373.8">>},
    229 	{<<"seats">>, <<"999">>},
    230 	{<<"ortb-xt">>, <<"IAB24">>},
    231 	{<<"ortb-ugc">>, <<>>}
    232 ]).
    233 
    234 -define(QS_LONGER, [
    235 	{<<"i">>, <<"9pQNskA">>},
    236 	{<<"v">>, <<"0ySQQd1F">>},
    237 	{<<"ev">>, <<"12345678">>},
    238 	{<<"t">>, <<"12345">>},
    239 	{<<"sz">>, <<"3">>},
    240 	{<<"ip">>, <<"67.58.236.89">>},
    241 	{<<"la">>, <<"en">>},
    242 	{<<"pg">>, <<"http://www.yahoo.com/page1.htm">>},
    243 	{<<"re">>, <<"http://search.google.com">>},
    244 	{<<"fc">>, <<"1">>},
    245 	{<<"fr">>, <<"1">>},
    246 	{<<"br">>, <<"2">>},
    247 	{<<"bv">>, <<"3.0.14">>},
    248 	{<<"os">>, <<"1">>},
    249 	{<<"ov">>, <<"XP">>},
    250 	{<<"k">>, <<"cars,ford">>},
    251 	{<<"rs">>, <<"js">>},
    252 	{<<"xt">>, <<"5|22|234">>},
    253 	{<<"tz">>, <<"+180">>},
    254 	{<<"tk">>, <<"key1=value1|key2=value2">>},
    255 	{<<"zl">>, <<"4,5,6">>},
    256 	{<<"za">>, <<"4">>},
    257 	{<<"zu">>, <<"competitor.com">>},
    258 	{<<"ua">>, <<"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) "
    259 		"AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.98 "
    260 		"Safari/534.13">>},
    261 	{<<"ortb-za">>, <<"1,6,13">>},
    262 	{<<"ortb-pid">>, <<"521732">>},
    263 	{<<"ortb-sid">>, <<"521732">>},
    264 	{<<"ortb-xt">>, <<"IAB3">>},
    265 	{<<"ortb-ugc">>, <<>>}
    266 ]).
    267 
    268 -ifdef(TEST).
    269 qs_test_() ->
    270 	Tests = [
    271 		{[<<"a">>], error},
    272 		{[{<<"a">>, <<"b">>, <<"c">>}], error},
    273 		{[], <<>>},
    274 		{[{<<"a">>, true}], <<"a">>},
    275 		{[{<<"a">>, true}, {<<"b">>, true}], <<"a&b">>},
    276 		{[{<<"a">>, <<>>}], <<"a=">>},
    277 		{[{<<"a">>, <<"b">>}], <<"a=b">>},
    278 		{[{<<"a">>, <<>>}, {<<"b">>, <<>>}], <<"a=&b=">>},
    279 		{[{<<"a">>, <<"b">>}, {<<"c">>, true}, {<<"d">>, <<"e">>}],
    280 			<<"a=b&c&d=e">>},
    281 		{[{<<"a">>, <<"b=c">>}, {<<"d">>, <<"e=f">>}, {<<"g">>, <<"h=i">>}],
    282 			<<"a=b%3Dc&d=e%3Df&g=h%3Di">>},
    283 		{[{<<" ">>, true}], <<"+">>},
    284 		{[{<<" ">>, <<" ">>}], <<"+=+">>},
    285 		{[{<<"a b">>, <<"c d">>}], <<"a+b=c+d">>},
    286 		{[{<<" a ">>, <<" b ">>}, {<<" c ">>, <<" d ">>}],
    287 			<<"+a+=+b+&+c+=+d+">>},
    288 		{[{<<"%&=">>, <<"%&=">>}, {<<"_-.">>, <<".-_">>}],
    289 			<<"%25%26%3D=%25%26%3D&_-.=.-_">>},
    290 		{[{<<"for">>, <<"extend/ranch">>}], <<"for=extend%2Franch">>}
    291 	],
    292 	[{lists:flatten(io_lib:format("~p", [Vals])), fun() ->
    293 		E = try qs(Vals) of
    294 			R -> R
    295 		catch _:_ ->
    296 			error
    297 		end
    298 	end} || {Vals, E} <- Tests].
    299 
    300 qs_identity_test_() ->
    301 	Tests = [
    302 		[{<<"+">>, true}],
    303 		?QS_SHORTER,
    304 		?QS_SHORT,
    305 		?QS_LONG,
    306 		?QS_LONGER
    307 	],
    308 	[{lists:flatten(io_lib:format("~p", [V])), fun() ->
    309 		V = parse_qs(qs(V))
    310 	end} || V <- Tests].
    311 
    312 horse_qs_shorter() ->
    313 	horse:repeat(20000, qs(?QS_SHORTER)).
    314 
    315 horse_qs_short() ->
    316 	horse:repeat(20000, qs(?QS_SHORT)).
    317 
    318 horse_qs_long() ->
    319 	horse:repeat(20000, qs(?QS_LONG)).
    320 
    321 horse_qs_longer() ->
    322 	horse:repeat(20000, qs(?QS_LONGER)).
    323 -endif.
    324 
    325 %% @doc Decode a percent encoded string (x-www-form-urlencoded rules).
    326 
    327 -spec urldecode(B) -> B when B::binary().
    328 urldecode(B) ->
    329 	urldecode(B, <<>>).
    330 
    331 urldecode(<< $%, H, L, Rest/bits >>, Acc) ->
    332 	C = (unhex(H) bsl 4 bor unhex(L)),
    333 	urldecode(Rest, << Acc/bits, C >>);
    334 urldecode(<< $+, Rest/bits >>, Acc) ->
    335 	urldecode(Rest, << Acc/bits, " " >>);
    336 urldecode(<< C, Rest/bits >>, Acc) when C =/= $% ->
    337 	urldecode(Rest, << Acc/bits, C >>);
    338 urldecode(<<>>, Acc) ->
    339 	Acc.
    340 
    341 unhex($0) ->  0;
    342 unhex($1) ->  1;
    343 unhex($2) ->  2;
    344 unhex($3) ->  3;
    345 unhex($4) ->  4;
    346 unhex($5) ->  5;
    347 unhex($6) ->  6;
    348 unhex($7) ->  7;
    349 unhex($8) ->  8;
    350 unhex($9) ->  9;
    351 unhex($A) -> 10;
    352 unhex($B) -> 11;
    353 unhex($C) -> 12;
    354 unhex($D) -> 13;
    355 unhex($E) -> 14;
    356 unhex($F) -> 15;
    357 unhex($a) -> 10;
    358 unhex($b) -> 11;
    359 unhex($c) -> 12;
    360 unhex($d) -> 13;
    361 unhex($e) -> 14;
    362 unhex($f) -> 15.
    363 
    364 -ifdef(TEST).
    365 urldecode_test_() ->
    366 	Tests = [
    367 		{<<"%20">>, <<" ">>},
    368 		{<<"+">>, <<" ">>},
    369 		{<<"%00">>, <<0>>},
    370 		{<<"%fF">>, <<255>>},
    371 		{<<"123">>, <<"123">>},
    372 		{<<"%i5">>, error},
    373 		{<<"%5">>, error}
    374 	],
    375 	[{Qs, fun() ->
    376 		E = try urldecode(Qs) of
    377 			R -> R
    378 		catch _:_ ->
    379 			error
    380 		end
    381 	end} || {Qs, E} <- Tests].
    382 
    383 urldecode_identity_test_() ->
    384 	Tests = [
    385 		<<"+">>,
    386 		<<"nothingnothingnothingnothing">>,
    387 		<<"Small+fast+modular+HTTP+server">>,
    388 		<<"Small%2C+fast%2C+modular+HTTP+server.">>,
    389 		<<"%E3%83%84%E3%82%A4%E3%83%B3%E3%82%BD%E3%82%A6%E3%83"
    390 			"%AB%E3%80%9C%E8%BC%AA%E5%BB%BB%E3%81%99%E3%82%8B%E6%97%8B%E5"
    391 			"%BE%8B%E3%80%9C">>
    392 	],
    393 	[{V, fun() -> V = urlencode(urldecode(V)) end} || V <- Tests].
    394 
    395 horse_urldecode() ->
    396 	horse:repeat(100000,
    397 		urldecode(<<"nothingnothingnothingnothing">>)
    398 	).
    399 
    400 horse_urldecode_plus() ->
    401 	horse:repeat(100000,
    402 		urldecode(<<"Small+fast+modular+HTTP+server">>)
    403 	).
    404 
    405 horse_urldecode_hex() ->
    406 	horse:repeat(100000,
    407 		urldecode(<<"Small%2C%20fast%2C%20modular%20HTTP%20server.">>)
    408 	).
    409 
    410 horse_urldecode_jp_hex() ->
    411 	horse:repeat(100000,
    412 		urldecode(<<"%E3%83%84%E3%82%A4%E3%83%B3%E3%82%BD%E3%82%A6%E3%83"
    413 			"%AB%E3%80%9C%E8%BC%AA%E5%BB%BB%E3%81%99%E3%82%8B%E6%97%8B%E5"
    414 			"%BE%8B%E3%80%9C">>)
    415 	).
    416 
    417 horse_urldecode_mix() ->
    418 	horse:repeat(100000,
    419 		urldecode(<<"Small%2C+fast%2C+modular+HTTP+server.">>)
    420 	).
    421 -endif.
    422 
    423 %% @doc Percent encode a string (x-www-form-urlencoded rules).
    424 
    425 -spec urlencode(B) -> B when B::binary().
    426 urlencode(B) ->
    427 	urlencode(B, <<>>).
    428 
    429 urlencode(<< $\s, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $+ >>);
    430 urlencode(<< $-, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $- >>);
    431 urlencode(<< $., Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $. >>);
    432 urlencode(<< $0, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $0 >>);
    433 urlencode(<< $1, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $1 >>);
    434 urlencode(<< $2, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $2 >>);
    435 urlencode(<< $3, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $3 >>);
    436 urlencode(<< $4, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $4 >>);
    437 urlencode(<< $5, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $5 >>);
    438 urlencode(<< $6, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $6 >>);
    439 urlencode(<< $7, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $7 >>);
    440 urlencode(<< $8, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $8 >>);
    441 urlencode(<< $9, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $9 >>);
    442 urlencode(<< $A, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $A >>);
    443 urlencode(<< $B, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $B >>);
    444 urlencode(<< $C, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $C >>);
    445 urlencode(<< $D, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $D >>);
    446 urlencode(<< $E, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $E >>);
    447 urlencode(<< $F, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $F >>);
    448 urlencode(<< $G, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $G >>);
    449 urlencode(<< $H, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $H >>);
    450 urlencode(<< $I, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $I >>);
    451 urlencode(<< $J, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $J >>);
    452 urlencode(<< $K, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $K >>);
    453 urlencode(<< $L, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $L >>);
    454 urlencode(<< $M, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $M >>);
    455 urlencode(<< $N, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $N >>);
    456 urlencode(<< $O, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $O >>);
    457 urlencode(<< $P, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $P >>);
    458 urlencode(<< $Q, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $Q >>);
    459 urlencode(<< $R, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $R >>);
    460 urlencode(<< $S, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $S >>);
    461 urlencode(<< $T, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $T >>);
    462 urlencode(<< $U, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $U >>);
    463 urlencode(<< $V, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $V >>);
    464 urlencode(<< $W, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $W >>);
    465 urlencode(<< $X, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $X >>);
    466 urlencode(<< $Y, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $Y >>);
    467 urlencode(<< $Z, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $Z >>);
    468 urlencode(<< $_, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $_ >>);
    469 urlencode(<< $a, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $a >>);
    470 urlencode(<< $b, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $b >>);
    471 urlencode(<< $c, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $c >>);
    472 urlencode(<< $d, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $d >>);
    473 urlencode(<< $e, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $e >>);
    474 urlencode(<< $f, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $f >>);
    475 urlencode(<< $g, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $g >>);
    476 urlencode(<< $h, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $h >>);
    477 urlencode(<< $i, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $i >>);
    478 urlencode(<< $j, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $j >>);
    479 urlencode(<< $k, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $k >>);
    480 urlencode(<< $l, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $l >>);
    481 urlencode(<< $m, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $m >>);
    482 urlencode(<< $n, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $n >>);
    483 urlencode(<< $o, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $o >>);
    484 urlencode(<< $p, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $p >>);
    485 urlencode(<< $q, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $q >>);
    486 urlencode(<< $r, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $r >>);
    487 urlencode(<< $s, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $s >>);
    488 urlencode(<< $t, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $t >>);
    489 urlencode(<< $u, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $u >>);
    490 urlencode(<< $v, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $v >>);
    491 urlencode(<< $w, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $w >>);
    492 urlencode(<< $x, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $x >>);
    493 urlencode(<< $y, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $y >>);
    494 urlencode(<< $z, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $z >>);
    495 urlencode(<< C, Rest/bits >>, Acc) ->
    496 	H = hex(C bsr 4),
    497 	L = hex(C band 16#0f),
    498 	urlencode(Rest, << Acc/bits, $%, H, L >>);
    499 urlencode(<<>>, Acc) ->
    500 	Acc.
    501 
    502 hex( 0) -> $0;
    503 hex( 1) -> $1;
    504 hex( 2) -> $2;
    505 hex( 3) -> $3;
    506 hex( 4) -> $4;
    507 hex( 5) -> $5;
    508 hex( 6) -> $6;
    509 hex( 7) -> $7;
    510 hex( 8) -> $8;
    511 hex( 9) -> $9;
    512 hex(10) -> $A;
    513 hex(11) -> $B;
    514 hex(12) -> $C;
    515 hex(13) -> $D;
    516 hex(14) -> $E;
    517 hex(15) -> $F.
    518 
    519 -ifdef(TEST).
    520 urlencode_test_() ->
    521 	Tests = [
    522 		{<<255, 0>>, <<"%FF%00">>},
    523 		{<<255, " ">>, <<"%FF+">>},
    524 		{<<" ">>, <<"+">>},
    525 		{<<"aBc123">>, <<"aBc123">>},
    526 		{<<".-_">>, <<".-_">>}
    527 	],
    528 	[{V, fun() -> E = urlencode(V) end} || {V, E} <- Tests].
    529 
    530 urlencode_identity_test_() ->
    531 	Tests = [
    532 		<<"+">>,
    533 		<<"nothingnothingnothingnothing">>,
    534 		<<"Small fast modular HTTP server">>,
    535 		<<"Small, fast, modular HTTP server.">>,
    536 		<<227,131,132,227,130,164,227,131,179,227,130,189,227,
    537 			130,166,227,131,171,227,128,156,232,188,170,229,187,187,227,
    538 			129,153,227,130,139,230,151,139,229,190,139,227,128,156>>
    539 	],
    540 	[{V, fun() -> V = urldecode(urlencode(V)) end} || V <- Tests].
    541 
    542 horse_urlencode() ->
    543 	horse:repeat(100000,
    544 		urlencode(<<"nothingnothingnothingnothing">>)
    545 	).
    546 
    547 horse_urlencode_plus() ->
    548 	horse:repeat(100000,
    549 		urlencode(<<"Small fast modular HTTP server">>)
    550 	).
    551 
    552 horse_urlencode_jp() ->
    553 	horse:repeat(100000,
    554 		urlencode(<<227,131,132,227,130,164,227,131,179,227,130,189,227,
    555 			130,166,227,131,171,227,128,156,232,188,170,229,187,187,227,
    556 			129,153,227,130,139,230,151,139,229,190,139,227,128,156>>)
    557 	).
    558 
    559 horse_urlencode_mix() ->
    560 	horse:repeat(100000,
    561 		urlencode(<<"Small, fast, modular HTTP server.">>)
    562 	).
    563 -endif.