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.