cow_qs.erl 19 KB

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