cowboy_http.erl 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036
  1. %% Copyright (c) 2011, Loïc Hoguin <essen@dev-extend.eu>
  2. %% Copyright (c) 2011, Anthony Ramine <nox@dev-extend.eu>
  3. %%
  4. %% Permission to use, copy, modify, and/or distribute this software for any
  5. %% purpose with or without fee is hereby granted, provided that the above
  6. %% copyright notice and this permission notice appear in all copies.
  7. %%
  8. %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  9. %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  10. %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  11. %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  12. %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  13. %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  14. %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. %% @doc Core HTTP parsing API.
  16. -module(cowboy_http).
  17. %% Parsing.
  18. -export([list/2, nonempty_list/2, content_type/1, media_range/2, conneg/2,
  19. language_range/2, entity_tag_match/1, expectation/2, params/2,
  20. http_date/1, rfc1123_date/1, rfc850_date/1, asctime_date/1,
  21. whitespace/2, digits/1, token/2, token_ci/2, quoted_string/2]).
  22. %% Interpretation.
  23. -export([connection_to_atom/1, urldecode/1, urldecode/2, urlencode/1,
  24. urlencode/2, x_www_form_urlencoded/2]).
  25. -type method() :: 'OPTIONS' | 'GET' | 'HEAD'
  26. | 'POST' | 'PUT' | 'DELETE' | 'TRACE' | binary().
  27. -type uri() :: '*' | {absoluteURI, http | https, Host::binary(),
  28. Port::integer() | undefined, Path::binary()}
  29. | {scheme, Scheme::binary(), binary()}
  30. | {abs_path, binary()} | binary().
  31. -type version() :: {Major::non_neg_integer(), Minor::non_neg_integer()}.
  32. -type header() :: 'Cache-Control' | 'Connection' | 'Date' | 'Pragma'
  33. | 'Transfer-Encoding' | 'Upgrade' | 'Via' | 'Accept' | 'Accept-Charset'
  34. | 'Accept-Encoding' | 'Accept-Language' | 'Authorization' | 'From' | 'Host'
  35. | 'If-Modified-Since' | 'If-Match' | 'If-None-Match' | 'If-Range'
  36. | 'If-Unmodified-Since' | 'Max-Forwards' | 'Proxy-Authorization' | 'Range'
  37. | 'Referer' | 'User-Agent' | 'Age' | 'Location' | 'Proxy-Authenticate'
  38. | 'Public' | 'Retry-After' | 'Server' | 'Vary' | 'Warning'
  39. | 'Www-Authenticate' | 'Allow' | 'Content-Base' | 'Content-Encoding'
  40. | 'Content-Language' | 'Content-Length' | 'Content-Location'
  41. | 'Content-Md5' | 'Content-Range' | 'Content-Type' | 'Etag'
  42. | 'Expires' | 'Last-Modified' | 'Accept-Ranges' | 'Set-Cookie'
  43. | 'Set-Cookie2' | 'X-Forwarded-For' | 'Cookie' | 'Keep-Alive'
  44. | 'Proxy-Connection' | binary().
  45. -type headers() :: [{header(), iodata()}].
  46. -type status() :: non_neg_integer() | binary().
  47. -export_type([method/0, uri/0, version/0, header/0, headers/0, status/0]).
  48. -include_lib("eunit/include/eunit.hrl").
  49. %% Parsing.
  50. %% @doc Parse a non-empty list of the given type.
  51. -spec nonempty_list(binary(), fun()) -> [any(), ...] | {error, badarg}.
  52. nonempty_list(Data, Fun) ->
  53. case list(Data, Fun, []) of
  54. {error, badarg} -> {error, badarg};
  55. [] -> {error, badarg};
  56. L -> lists:reverse(L)
  57. end.
  58. %% @doc Parse a list of the given type.
  59. -spec list(binary(), fun()) -> list() | {error, badarg}.
  60. list(Data, Fun) ->
  61. case list(Data, Fun, []) of
  62. {error, badarg} -> {error, badarg};
  63. L -> lists:reverse(L)
  64. end.
  65. -spec list(binary(), fun(), [binary()]) -> [any()] | {error, badarg}.
  66. %% From the RFC:
  67. %% <blockquote>Wherever this construct is used, null elements are allowed,
  68. %% but do not contribute to the count of elements present.
  69. %% That is, "(element), , (element) " is permitted, but counts
  70. %% as only two elements. Therefore, where at least one element is required,
  71. %% at least one non-null element MUST be present.</blockquote>
  72. list(Data, Fun, Acc) ->
  73. whitespace(Data,
  74. fun (<<>>) -> Acc;
  75. (<< $,, Rest/binary >>) -> list(Rest, Fun, Acc);
  76. (Rest) -> Fun(Rest,
  77. fun (D, I) -> whitespace(D,
  78. fun (<<>>) -> [I|Acc];
  79. (<< $,, R/binary >>) -> list(R, Fun, [I|Acc]);
  80. (_Any) -> {error, badarg}
  81. end)
  82. end)
  83. end).
  84. %% @doc Parse a content type.
  85. -spec content_type(binary()) -> any().
  86. content_type(Data) ->
  87. media_type(Data,
  88. fun (Rest, Type, SubType) ->
  89. params(Rest,
  90. fun (<<>>, Params) -> {Type, SubType, Params};
  91. (_Rest2, _) -> {error, badarg}
  92. end)
  93. end).
  94. %% @doc Parse a media range.
  95. -spec media_range(binary(), fun()) -> any().
  96. media_range(Data, Fun) ->
  97. media_type(Data,
  98. fun (Rest, Type, SubType) ->
  99. media_range_params(Rest, Fun, Type, SubType, [])
  100. end).
  101. -spec media_range_params(binary(), fun(), binary(), binary(),
  102. [{binary(), binary()}]) -> any().
  103. media_range_params(Data, Fun, Type, SubType, Acc) ->
  104. whitespace(Data,
  105. fun (<< $;, Rest/binary >>) ->
  106. whitespace(Rest,
  107. fun (Rest2) ->
  108. media_range_param_attr(Rest2, Fun, Type, SubType, Acc)
  109. end);
  110. (Rest) -> Fun(Rest, {{Type, SubType, lists:reverse(Acc)}, 1000, []})
  111. end).
  112. -spec media_range_param_attr(binary(), fun(), binary(), binary(),
  113. [{binary(), binary()}]) -> any().
  114. media_range_param_attr(Data, Fun, Type, SubType, Acc) ->
  115. token_ci(Data,
  116. fun (_Rest, <<>>) -> {error, badarg};
  117. (<< $=, Rest/binary >>, Attr) ->
  118. media_range_param_value(Rest, Fun, Type, SubType, Acc, Attr)
  119. end).
  120. -spec media_range_param_value(binary(), fun(), binary(), binary(),
  121. [{binary(), binary()}], binary()) -> any().
  122. media_range_param_value(Data, Fun, Type, SubType, Acc, <<"q">>) ->
  123. qvalue(Data,
  124. fun (Rest, Quality) ->
  125. accept_ext(Rest, Fun, Type, SubType, Acc, Quality, [])
  126. end);
  127. media_range_param_value(Data, Fun, Type, SubType, Acc, Attr) ->
  128. word(Data,
  129. fun (Rest, Value) ->
  130. media_range_params(Rest, Fun,
  131. Type, SubType, [{Attr, Value}|Acc])
  132. end).
  133. %% @doc Parse a media type.
  134. -spec media_type(binary(), fun()) -> any().
  135. media_type(Data, Fun) ->
  136. token_ci(Data,
  137. fun (_Rest, <<>>) -> {error, badarg};
  138. (<< $/, Rest/binary >>, Type) ->
  139. token_ci(Rest,
  140. fun (_Rest2, <<>>) -> {error, badarg};
  141. (Rest2, SubType) -> Fun(Rest2, Type, SubType)
  142. end);
  143. %% This is a non-strict parsing clause required by some user agents
  144. %% that use * instead of */* in the list of media types.
  145. (Rest, <<"*">> = Type) ->
  146. token_ci(<<"*", Rest/binary>>,
  147. fun (_Rest2, <<>>) -> {error, badarg};
  148. (Rest2, SubType) -> Fun(Rest2, Type, SubType)
  149. end);
  150. (_Rest, _Type) -> {error, badarg}
  151. end).
  152. -spec accept_ext(binary(), fun(), binary(), binary(),
  153. [{binary(), binary()}], 0..1000,
  154. [{binary(), binary()} | binary()]) -> any().
  155. accept_ext(Data, Fun, Type, SubType, Params, Quality, Acc) ->
  156. whitespace(Data,
  157. fun (<< $;, Rest/binary >>) ->
  158. whitespace(Rest,
  159. fun (Rest2) ->
  160. accept_ext_attr(Rest2, Fun,
  161. Type, SubType, Params, Quality, Acc)
  162. end);
  163. (Rest) ->
  164. Fun(Rest, {{Type, SubType, lists:reverse(Params)},
  165. Quality, lists:reverse(Acc)})
  166. end).
  167. -spec accept_ext_attr(binary(), fun(), binary(), binary(),
  168. [{binary(), binary()}], 0..1000,
  169. [{binary(), binary()} | binary()]) -> any().
  170. accept_ext_attr(Data, Fun, Type, SubType, Params, Quality, Acc) ->
  171. token_ci(Data,
  172. fun (_Rest, <<>>) -> {error, badarg};
  173. (<< $=, Rest/binary >>, Attr) ->
  174. accept_ext_value(Rest, Fun, Type, SubType, Params,
  175. Quality, Acc, Attr);
  176. (Rest, Attr) ->
  177. accept_ext(Rest, Fun, Type, SubType, Params,
  178. Quality, [Attr|Acc])
  179. end).
  180. -spec accept_ext_value(binary(), fun(), binary(), binary(),
  181. [{binary(), binary()}], 0..1000,
  182. [{binary(), binary()} | binary()], binary()) -> any().
  183. accept_ext_value(Data, Fun, Type, SubType, Params, Quality, Acc, Attr) ->
  184. word(Data,
  185. fun (Rest, Value) ->
  186. accept_ext(Rest, Fun,
  187. Type, SubType, Params, Quality, [{Attr, Value}|Acc])
  188. end).
  189. %% @doc Parse a conneg header (Accept-Charset, Accept-Encoding),
  190. %% followed by an optional quality value.
  191. -spec conneg(binary(), fun()) -> any().
  192. conneg(Data, Fun) ->
  193. token_ci(Data,
  194. fun (_Rest, <<>>) -> {error, badarg};
  195. (Rest, Conneg) ->
  196. maybe_qparam(Rest,
  197. fun (Rest2, Quality) ->
  198. Fun(Rest2, {Conneg, Quality})
  199. end)
  200. end).
  201. %% @doc Parse a language range, followed by an optional quality value.
  202. -spec language_range(binary(), fun()) -> any().
  203. language_range(<< $*, Rest/binary >>, Fun) ->
  204. language_range_ret(Rest, Fun, '*');
  205. language_range(Data, Fun) ->
  206. language_tag(Data,
  207. fun (Rest, LanguageTag) ->
  208. language_range_ret(Rest, Fun, LanguageTag)
  209. end).
  210. -spec language_range_ret(binary(), fun(), '*' | {binary(), [binary()]}) -> any().
  211. language_range_ret(Data, Fun, LanguageTag) ->
  212. maybe_qparam(Data,
  213. fun (Rest, Quality) ->
  214. Fun(Rest, {LanguageTag, Quality})
  215. end).
  216. -spec language_tag(binary(), fun()) -> any().
  217. language_tag(Data, Fun) ->
  218. alpha(Data,
  219. fun (_Rest, Tag) when byte_size(Tag) =:= 0; byte_size(Tag) > 8 ->
  220. {error, badarg};
  221. (<< $-, Rest/binary >>, Tag) ->
  222. language_subtag(Rest, Fun, Tag, []);
  223. (Rest, Tag) ->
  224. Fun(Rest, Tag)
  225. end).
  226. -spec language_subtag(binary(), fun(), binary(), [binary()]) -> any().
  227. language_subtag(Data, Fun, Tag, Acc) ->
  228. alpha(Data,
  229. fun (_Rest, SubTag) when byte_size(SubTag) =:= 0;
  230. byte_size(SubTag) > 8 -> {error, badarg};
  231. (<< $-, Rest/binary >>, SubTag) ->
  232. language_subtag(Rest, Fun, Tag, [SubTag|Acc]);
  233. (Rest, SubTag) ->
  234. %% Rebuild the full tag now that we know it's correct
  235. Sub = << << $-, S/binary >> || S <- lists:reverse([SubTag|Acc]) >>,
  236. Fun(Rest, << Tag/binary, Sub/binary >>)
  237. end).
  238. -spec maybe_qparam(binary(), fun()) -> any().
  239. maybe_qparam(Data, Fun) ->
  240. whitespace(Data,
  241. fun (<< $;, Rest/binary >>) ->
  242. whitespace(Rest,
  243. fun (Rest2) ->
  244. qparam(Rest2, Fun)
  245. end);
  246. (Rest) ->
  247. Fun(Rest, 1000)
  248. end).
  249. %% @doc Parse a quality parameter string (for example q=0.500).
  250. -spec qparam(binary(), fun()) -> any().
  251. qparam(<< Q, $=, Data/binary >>, Fun) when Q =:= $q; Q =:= $Q ->
  252. qvalue(Data, Fun).
  253. %% @doc Parse either a list of entity tags or a "*".
  254. -spec entity_tag_match(binary()) -> any().
  255. entity_tag_match(<< $*, Rest/binary >>) ->
  256. whitespace(Rest,
  257. fun (<<>>) -> '*';
  258. (_Any) -> {error, badarg}
  259. end);
  260. entity_tag_match(Data) ->
  261. nonempty_list(Data, fun entity_tag/2).
  262. %% @doc Parse an entity-tag.
  263. -spec entity_tag(binary(), fun()) -> any().
  264. entity_tag(<< "W/", Rest/binary >>, Fun) ->
  265. opaque_tag(Rest, Fun, weak);
  266. entity_tag(Data, Fun) ->
  267. opaque_tag(Data, Fun, strong).
  268. -spec opaque_tag(binary(), fun(), weak | strong) -> any().
  269. opaque_tag(Data, Fun, Strength) ->
  270. quoted_string(Data,
  271. fun (_Rest, <<>>) -> {error, badarg};
  272. (Rest, OpaqueTag) -> Fun(Rest, {Strength, OpaqueTag})
  273. end).
  274. %% @doc Parse an expectation.
  275. -spec expectation(binary(), fun()) -> any().
  276. expectation(Data, Fun) ->
  277. token_ci(Data,
  278. fun (_Rest, <<>>) -> {error, badarg};
  279. (<< $=, Rest/binary >>, Expectation) ->
  280. word(Rest,
  281. fun (Rest2, ExtValue) ->
  282. params(Rest2, fun (Rest3, ExtParams) ->
  283. Fun(Rest3, {Expectation, ExtValue, ExtParams})
  284. end)
  285. end);
  286. (Rest, Expectation) ->
  287. Fun(Rest, Expectation)
  288. end).
  289. %% @doc Parse a list of parameters (a=b;c=d).
  290. -spec params(binary(), fun()) -> any().
  291. params(Data, Fun) ->
  292. params(Data, Fun, []).
  293. -spec params(binary(), fun(), [{binary(), binary()}]) -> any().
  294. params(Data, Fun, Acc) ->
  295. whitespace(Data,
  296. fun (<< $;, Rest/binary >>) -> param(Rest, Fun, Acc);
  297. (Rest) -> Fun(Rest, lists:reverse(Acc))
  298. end).
  299. -spec param(binary(), fun(), [{binary(), binary()}]) -> any().
  300. param(Data, Fun, Acc) ->
  301. whitespace(Data,
  302. fun (Rest) ->
  303. token_ci(Rest,
  304. fun (_Rest2, <<>>) -> {error, badarg};
  305. (<< $=, Rest2/binary >>, Attr) ->
  306. word(Rest2,
  307. fun (Rest3, Value) ->
  308. params(Rest3, Fun,
  309. [{Attr, Value}|Acc])
  310. end);
  311. (_Rest2, _Attr) -> {error, badarg}
  312. end)
  313. end).
  314. %% @doc Parse an HTTP date (RFC1123, RFC850 or asctime date).
  315. %% @end
  316. %%
  317. %% While this may not be the most efficient date parsing we can do,
  318. %% it should work fine for our purposes because all HTTP dates should
  319. %% be sent as RFC1123 dates in HTTP/1.1.
  320. -spec http_date(binary()) -> any().
  321. http_date(Data) ->
  322. case rfc1123_date(Data) of
  323. {error, badarg} ->
  324. case rfc850_date(Data) of
  325. {error, badarg} ->
  326. case asctime_date(Data) of
  327. {error, badarg} ->
  328. {error, badarg};
  329. HTTPDate ->
  330. HTTPDate
  331. end;
  332. HTTPDate ->
  333. HTTPDate
  334. end;
  335. HTTPDate ->
  336. HTTPDate
  337. end.
  338. %% @doc Parse an RFC1123 date.
  339. -spec rfc1123_date(binary()) -> any().
  340. rfc1123_date(Data) ->
  341. wkday(Data,
  342. fun (<< ", ", Rest/binary >>, _WkDay) ->
  343. date1(Rest,
  344. fun (<< " ", Rest2/binary >>, Date) ->
  345. time(Rest2,
  346. fun (<< " GMT", Rest3/binary >>, Time) ->
  347. http_date_ret(Rest3, {Date, Time});
  348. (_Any, _Time) ->
  349. {error, badarg}
  350. end);
  351. (_Any, _Date) ->
  352. {error, badarg}
  353. end);
  354. (_Any, _WkDay) ->
  355. {error, badarg}
  356. end).
  357. %% @doc Parse an RFC850 date.
  358. -spec rfc850_date(binary()) -> any().
  359. %% From the RFC:
  360. %% HTTP/1.1 clients and caches SHOULD assume that an RFC-850 date
  361. %% which appears to be more than 50 years in the future is in fact
  362. %% in the past (this helps solve the "year 2000" problem).
  363. rfc850_date(Data) ->
  364. weekday(Data,
  365. fun (<< ", ", Rest/binary >>, _WeekDay) ->
  366. date2(Rest,
  367. fun (<< " ", Rest2/binary >>, Date) ->
  368. time(Rest2,
  369. fun (<< " GMT", Rest3/binary >>, Time) ->
  370. http_date_ret(Rest3, {Date, Time});
  371. (_Any, _Time) ->
  372. {error, badarg}
  373. end);
  374. (_Any, _Date) ->
  375. {error, badarg}
  376. end);
  377. (_Any, _WeekDay) ->
  378. {error, badarg}
  379. end).
  380. %% @doc Parse an asctime date.
  381. -spec asctime_date(binary()) -> any().
  382. asctime_date(Data) ->
  383. wkday(Data,
  384. fun (<< " ", Rest/binary >>, _WkDay) ->
  385. date3(Rest,
  386. fun (<< " ", Rest2/binary >>, PartialDate) ->
  387. time(Rest2,
  388. fun (<< " ", Rest3/binary >>, Time) ->
  389. asctime_year(Rest3,
  390. PartialDate, Time);
  391. (_Any, _Time) ->
  392. {error, badarg}
  393. end);
  394. (_Any, _PartialDate) ->
  395. {error, badarg}
  396. end);
  397. (_Any, _WkDay) ->
  398. {error, badarg1}
  399. end).
  400. -spec asctime_year(binary(), tuple(), tuple()) -> any().
  401. asctime_year(<< Y1, Y2, Y3, Y4, Rest/binary >>, {Month, Day}, Time)
  402. when Y1 >= $0, Y1 =< $9, Y2 >= $0, Y2 =< $9,
  403. Y3 >= $0, Y3 =< $9, Y4 >= $0, Y4 =< $9 ->
  404. Year = (Y1 - $0) * 1000 + (Y2 - $0) * 100 + (Y3 - $0) * 10 + (Y4 - $0),
  405. http_date_ret(Rest, {{Year, Month, Day}, Time}).
  406. -spec http_date_ret(binary(), tuple()) -> any().
  407. http_date_ret(Data, DateTime = {Date, _Time}) ->
  408. whitespace(Data,
  409. fun (<<>>) ->
  410. case calendar:valid_date(Date) of
  411. true -> DateTime;
  412. false -> {error, badarg}
  413. end;
  414. (_Any) ->
  415. {error, badarg}
  416. end).
  417. %% We never use it, pretty much just checks the wkday is right.
  418. -spec wkday(binary(), fun()) -> any().
  419. wkday(<< WkDay:3/binary, Rest/binary >>, Fun)
  420. when WkDay =:= <<"Mon">>; WkDay =:= <<"Tue">>; WkDay =:= <<"Wed">>;
  421. WkDay =:= <<"Thu">>; WkDay =:= <<"Fri">>; WkDay =:= <<"Sat">>;
  422. WkDay =:= <<"Sun">> ->
  423. Fun(Rest, WkDay);
  424. wkday(_Any, _Fun) ->
  425. {error, badarg}.
  426. %% We never use it, pretty much just checks the weekday is right.
  427. -spec weekday(binary(), fun()) -> any().
  428. weekday(<< "Monday", Rest/binary >>, Fun) ->
  429. Fun(Rest, <<"Monday">>);
  430. weekday(<< "Tuesday", Rest/binary >>, Fun) ->
  431. Fun(Rest, <<"Tuesday">>);
  432. weekday(<< "Wednesday", Rest/binary >>, Fun) ->
  433. Fun(Rest, <<"Wednesday">>);
  434. weekday(<< "Thursday", Rest/binary >>, Fun) ->
  435. Fun(Rest, <<"Thursday">>);
  436. weekday(<< "Friday", Rest/binary >>, Fun) ->
  437. Fun(Rest, <<"Friday">>);
  438. weekday(<< "Saturday", Rest/binary >>, Fun) ->
  439. Fun(Rest, <<"Saturday">>);
  440. weekday(<< "Sunday", Rest/binary >>, Fun) ->
  441. Fun(Rest, <<"Sunday">>);
  442. weekday(_Any, _Fun) ->
  443. {error, badarg}.
  444. -spec date1(binary(), fun()) -> any().
  445. date1(<< D1, D2, " ", M:3/binary, " ", Y1, Y2, Y3, Y4, Rest/binary >>, Fun)
  446. when D1 >= $0, D1 =< $9, D2 >= $0, D2 =< $9,
  447. Y1 >= $0, Y1 =< $9, Y2 >= $0, Y2 =< $9,
  448. Y3 >= $0, Y3 =< $9, Y4 >= $0, Y4 =< $9 ->
  449. case month(M) of
  450. {error, badarg} ->
  451. {error, badarg};
  452. Month ->
  453. Fun(Rest, {
  454. (Y1 - $0) * 1000 + (Y2 - $0) * 100 + (Y3 - $0) * 10 + (Y4 - $0),
  455. Month,
  456. (D1 - $0) * 10 + (D2 - $0)
  457. })
  458. end;
  459. date1(_Data, _Fun) ->
  460. {error, badarg}.
  461. -spec date2(binary(), fun()) -> any().
  462. date2(<< D1, D2, "-", M:3/binary, "-", Y1, Y2, Rest/binary >>, Fun)
  463. when D1 >= $0, D1 =< $9, D2 >= $0, D2 =< $9,
  464. Y1 >= $0, Y1 =< $9, Y2 >= $0, Y2 =< $9 ->
  465. case month(M) of
  466. {error, badarg} ->
  467. {error, badarg};
  468. Month ->
  469. Year = (Y1 - $0) * 10 + (Y2 - $0),
  470. Year2 = case Year > 50 of
  471. true -> Year + 1900;
  472. false -> Year + 2000
  473. end,
  474. Fun(Rest, {
  475. Year2,
  476. Month,
  477. (D1 - $0) * 10 + (D2 - $0)
  478. })
  479. end;
  480. date2(_Data, _Fun) ->
  481. {error, badarg}.
  482. -spec date3(binary(), fun()) -> any().
  483. date3(<< M:3/binary, " ", D1, D2, Rest/binary >>, Fun)
  484. when (D1 >= $0 andalso D1 =< $3) orelse D1 =:= $\s,
  485. D2 >= $0, D2 =< $9 ->
  486. case month(M) of
  487. {error, badarg} ->
  488. {error, badarg};
  489. Month ->
  490. Day = case D1 of
  491. $\s -> D2 - $0;
  492. D1 -> (D1 - $0) * 10 + (D2 - $0)
  493. end,
  494. Fun(Rest, {Month, Day})
  495. end;
  496. date3(_Data, _Fun) ->
  497. {error, badarg}.
  498. -spec month(<< _:24 >>) -> 1..12 | {error, badarg}.
  499. month(<<"Jan">>) -> 1;
  500. month(<<"Feb">>) -> 2;
  501. month(<<"Mar">>) -> 3;
  502. month(<<"Apr">>) -> 4;
  503. month(<<"May">>) -> 5;
  504. month(<<"Jun">>) -> 6;
  505. month(<<"Jul">>) -> 7;
  506. month(<<"Aug">>) -> 8;
  507. month(<<"Sep">>) -> 9;
  508. month(<<"Oct">>) -> 10;
  509. month(<<"Nov">>) -> 11;
  510. month(<<"Dec">>) -> 12;
  511. month(_Any) -> {error, badarg}.
  512. -spec time(binary(), fun()) -> any().
  513. time(<< H1, H2, ":", M1, M2, ":", S1, S2, Rest/binary >>, Fun)
  514. when H1 >= $0, H1 =< $2, H2 >= $0, H2 =< $9,
  515. M1 >= $0, M1 =< $5, M2 >= $0, M2 =< $9,
  516. S1 >= $0, S1 =< $5, S2 >= $0, S2 =< $9 ->
  517. Hour = (H1 - $0) * 10 + (H2 - $0),
  518. case Hour < 24 of
  519. true ->
  520. Time = {
  521. Hour,
  522. (M1 - $0) * 10 + (M2 - $0),
  523. (S1 - $0) * 10 + (S2 - $0)
  524. },
  525. Fun(Rest, Time);
  526. false ->
  527. {error, badarg}
  528. end.
  529. %% @doc Skip whitespace.
  530. -spec whitespace(binary(), fun()) -> any().
  531. whitespace(<< C, Rest/binary >>, Fun)
  532. when C =:= $\s; C =:= $\t ->
  533. whitespace(Rest, Fun);
  534. whitespace(Data, Fun) ->
  535. Fun(Data).
  536. %% @doc Parse a list of digits as a non negative integer.
  537. -spec digits(binary()) -> non_neg_integer() | {error, badarg}.
  538. digits(Data) ->
  539. digits(Data,
  540. fun (Rest, I) ->
  541. whitespace(Rest,
  542. fun (<<>>) ->
  543. I;
  544. (_Rest2) ->
  545. {error, badarg}
  546. end)
  547. end).
  548. -spec digits(binary(), fun()) -> any().
  549. digits(<< C, Rest/binary >>, Fun)
  550. when C >= $0, C =< $9 ->
  551. digits(Rest, Fun, C - $0);
  552. digits(_Data, _Fun) ->
  553. {error, badarg}.
  554. -spec digits(binary(), fun(), non_neg_integer()) -> any().
  555. digits(<< C, Rest/binary >>, Fun, Acc)
  556. when C >= $0, C =< $9 ->
  557. digits(Rest, Fun, Acc * 10 + (C - $0));
  558. digits(Data, Fun, Acc) ->
  559. Fun(Data, Acc).
  560. %% @doc Parse a list of case-insensitive alpha characters.
  561. %%
  562. %% Changes all characters to lowercase.
  563. -spec alpha(binary(), fun()) -> any().
  564. alpha(Data, Fun) ->
  565. alpha(Data, Fun, <<>>).
  566. -spec alpha(binary(), fun(), binary()) -> any().
  567. alpha(<<>>, Fun, Acc) ->
  568. Fun(<<>>, Acc);
  569. alpha(<< C, Rest/binary >>, Fun, Acc)
  570. when C >= $a andalso C =< $z;
  571. C >= $A andalso C =< $Z ->
  572. C2 = cowboy_bstr:char_to_lower(C),
  573. alpha(Rest, Fun, << Acc/binary, C2 >>);
  574. alpha(Data, Fun, Acc) ->
  575. Fun(Data, Acc).
  576. %% @doc Parse either a token or a quoted string.
  577. -spec word(binary(), fun()) -> any().
  578. word(Data = << $", _/binary >>, Fun) ->
  579. quoted_string(Data, Fun);
  580. word(Data, Fun) ->
  581. token(Data,
  582. fun (_Rest, <<>>) -> {error, badarg};
  583. (Rest, Token) -> Fun(Rest, Token)
  584. end).
  585. %% @doc Parse a case-insensitive token.
  586. %%
  587. %% Changes all characters to lowercase.
  588. -spec token_ci(binary(), fun()) -> any().
  589. token_ci(Data, Fun) ->
  590. token(Data, Fun, ci, <<>>).
  591. %% @doc Parse a token.
  592. -spec token(binary(), fun()) -> any().
  593. token(Data, Fun) ->
  594. token(Data, Fun, cs, <<>>).
  595. -spec token(binary(), fun(), ci | cs, binary()) -> any().
  596. token(<<>>, Fun, _Case, Acc) ->
  597. Fun(<<>>, Acc);
  598. token(Data = << C, _Rest/binary >>, Fun, _Case, Acc)
  599. when C =:= $(; C =:= $); C =:= $<; C =:= $>; C =:= $@;
  600. C =:= $,; C =:= $;; C =:= $:; C =:= $\\; C =:= $";
  601. C =:= $/; C =:= $[; C =:= $]; C =:= $?; C =:= $=;
  602. C =:= ${; C =:= $}; C =:= $\s; C =:= $\t;
  603. C < 32; C =:= 127 ->
  604. Fun(Data, Acc);
  605. token(<< C, Rest/binary >>, Fun, Case = ci, Acc) ->
  606. C2 = cowboy_bstr:char_to_lower(C),
  607. token(Rest, Fun, Case, << Acc/binary, C2 >>);
  608. token(<< C, Rest/binary >>, Fun, Case, Acc) ->
  609. token(Rest, Fun, Case, << Acc/binary, C >>).
  610. %% @doc Parse a quoted string.
  611. -spec quoted_string(binary(), fun()) -> any().
  612. quoted_string(<< $", Rest/binary >>, Fun) ->
  613. quoted_string(Rest, Fun, <<>>).
  614. -spec quoted_string(binary(), fun(), binary()) -> any().
  615. quoted_string(<<>>, _Fun, _Acc) ->
  616. {error, badarg};
  617. quoted_string(<< $", Rest/binary >>, Fun, Acc) ->
  618. Fun(Rest, Acc);
  619. quoted_string(<< $\\, C, Rest/binary >>, Fun, Acc) ->
  620. quoted_string(Rest, Fun, << Acc/binary, C >>);
  621. quoted_string(<< C, Rest/binary >>, Fun, Acc) ->
  622. quoted_string(Rest, Fun, << Acc/binary, C >>).
  623. %% @doc Parse a quality value.
  624. -spec qvalue(binary(), fun()) -> any().
  625. qvalue(<< $0, $., Rest/binary >>, Fun) ->
  626. qvalue(Rest, Fun, 0, 100);
  627. %% Some user agents use q=.x instead of q=0.x
  628. qvalue(<< $., Rest/binary >>, Fun) ->
  629. qvalue(Rest, Fun, 0, 100);
  630. qvalue(<< $0, Rest/binary >>, Fun) ->
  631. Fun(Rest, 0);
  632. qvalue(<< $1, $., $0, $0, $0, Rest/binary >>, Fun) ->
  633. Fun(Rest, 1000);
  634. qvalue(<< $1, $., $0, $0, Rest/binary >>, Fun) ->
  635. Fun(Rest, 1000);
  636. qvalue(<< $1, $., $0, Rest/binary >>, Fun) ->
  637. Fun(Rest, 1000);
  638. qvalue(<< $1, Rest/binary >>, Fun) ->
  639. Fun(Rest, 1000);
  640. qvalue(_Data, _Fun) ->
  641. {error, badarg}.
  642. -spec qvalue(binary(), fun(), integer(), 1 | 10 | 100) -> any().
  643. qvalue(Data, Fun, Q, 0) ->
  644. Fun(Data, Q);
  645. qvalue(<< C, Rest/binary >>, Fun, Q, M)
  646. when C >= $0, C =< $9 ->
  647. qvalue(Rest, Fun, Q + (C - $0) * M, M div 10);
  648. qvalue(Data, Fun, Q, _M) ->
  649. Fun(Data, Q).
  650. %% Interpretation.
  651. %% @doc Walk through a tokens list and return whether
  652. %% the connection is keepalive or closed.
  653. %%
  654. %% The connection token is expected to be lower-case.
  655. -spec connection_to_atom([binary()]) -> keepalive | close.
  656. connection_to_atom([]) ->
  657. keepalive;
  658. connection_to_atom([<<"keep-alive">>|_Tail]) ->
  659. keepalive;
  660. connection_to_atom([<<"close">>|_Tail]) ->
  661. close;
  662. connection_to_atom([_Any|Tail]) ->
  663. connection_to_atom(Tail).
  664. %% @doc Decode a URL encoded binary.
  665. %% @equiv urldecode(Bin, crash)
  666. -spec urldecode(binary()) -> binary().
  667. urldecode(Bin) when is_binary(Bin) ->
  668. urldecode(Bin, <<>>, crash).
  669. %% @doc Decode a URL encoded binary.
  670. %% The second argument specifies how to handle percent characters that are not
  671. %% followed by two valid hex characters. Use `skip' to ignore such errors,
  672. %% if `crash' is used the function will fail with the reason `badarg'.
  673. -spec urldecode(binary(), crash | skip) -> binary().
  674. urldecode(Bin, OnError) when is_binary(Bin) ->
  675. urldecode(Bin, <<>>, OnError).
  676. -spec urldecode(binary(), binary(), crash | skip) -> binary().
  677. urldecode(<<$%, H, L, Rest/binary>>, Acc, OnError) ->
  678. G = unhex(H),
  679. M = unhex(L),
  680. if G =:= error; M =:= error ->
  681. case OnError of skip -> ok; crash -> erlang:error(badarg) end,
  682. urldecode(<<H, L, Rest/binary>>, <<Acc/binary, $%>>, OnError);
  683. true ->
  684. urldecode(Rest, <<Acc/binary, (G bsl 4 bor M)>>, OnError)
  685. end;
  686. urldecode(<<$%, Rest/binary>>, Acc, OnError) ->
  687. case OnError of skip -> ok; crash -> erlang:error(badarg) end,
  688. urldecode(Rest, <<Acc/binary, $%>>, OnError);
  689. urldecode(<<$+, Rest/binary>>, Acc, OnError) ->
  690. urldecode(Rest, <<Acc/binary, $ >>, OnError);
  691. urldecode(<<C, Rest/binary>>, Acc, OnError) ->
  692. urldecode(Rest, <<Acc/binary, C>>, OnError);
  693. urldecode(<<>>, Acc, _OnError) ->
  694. Acc.
  695. -spec unhex(byte()) -> byte() | error.
  696. unhex(C) when C >= $0, C =< $9 -> C - $0;
  697. unhex(C) when C >= $A, C =< $F -> C - $A + 10;
  698. unhex(C) when C >= $a, C =< $f -> C - $a + 10;
  699. unhex(_) -> error.
  700. %% @doc URL encode a string binary.
  701. %% @equiv urlencode(Bin, [])
  702. -spec urlencode(binary()) -> binary().
  703. urlencode(Bin) ->
  704. urlencode(Bin, []).
  705. %% @doc URL encode a string binary.
  706. %% The `noplus' option disables the default behaviour of quoting space
  707. %% characters, `\s', as `+'. The `upper' option overrides the default behaviour
  708. %% of writing hex numbers using lowecase letters to using uppercase letters
  709. %% instead.
  710. -spec urlencode(binary(), [noplus|upper]) -> binary().
  711. urlencode(Bin, Opts) ->
  712. Plus = not proplists:get_value(noplus, Opts, false),
  713. Upper = proplists:get_value(upper, Opts, false),
  714. urlencode(Bin, <<>>, Plus, Upper).
  715. -spec urlencode(binary(), binary(), boolean(), boolean()) -> binary().
  716. urlencode(<<C, Rest/binary>>, Acc, P=Plus, U=Upper) ->
  717. if C >= $0, C =< $9 -> urlencode(Rest, <<Acc/binary, C>>, P, U);
  718. C >= $A, C =< $Z -> urlencode(Rest, <<Acc/binary, C>>, P, U);
  719. C >= $a, C =< $z -> urlencode(Rest, <<Acc/binary, C>>, P, U);
  720. C =:= $.; C =:= $-; C =:= $~; C =:= $_ ->
  721. urlencode(Rest, <<Acc/binary, C>>, P, U);
  722. C =:= $ , Plus ->
  723. urlencode(Rest, <<Acc/binary, $+>>, P, U);
  724. true ->
  725. H = C band 16#F0 bsr 4, L = C band 16#0F,
  726. H1 = if Upper -> tohexu(H); true -> tohexl(H) end,
  727. L1 = if Upper -> tohexu(L); true -> tohexl(L) end,
  728. urlencode(Rest, <<Acc/binary, $%, H1, L1>>, P, U)
  729. end;
  730. urlencode(<<>>, Acc, _Plus, _Upper) ->
  731. Acc.
  732. -spec tohexu(byte()) -> byte().
  733. tohexu(C) when C < 10 -> $0 + C;
  734. tohexu(C) when C < 17 -> $A + C - 10.
  735. -spec tohexl(byte()) -> byte().
  736. tohexl(C) when C < 10 -> $0 + C;
  737. tohexl(C) when C < 17 -> $a + C - 10.
  738. -spec x_www_form_urlencoded(binary(), fun((binary()) -> binary())) ->
  739. list({binary(), binary() | true}).
  740. x_www_form_urlencoded(<<>>, _URLDecode) ->
  741. [];
  742. x_www_form_urlencoded(Qs, URLDecode) ->
  743. Tokens = binary:split(Qs, <<"&">>, [global, trim]),
  744. [case binary:split(Token, <<"=">>) of
  745. [Token] -> {URLDecode(Token), true};
  746. [Name, Value] -> {URLDecode(Name), URLDecode(Value)}
  747. end || Token <- Tokens].
  748. %% Tests.
  749. -ifdef(TEST).
  750. nonempty_charset_list_test_() ->
  751. %% {Value, Result}
  752. Tests = [
  753. {<<>>, {error, badarg}},
  754. {<<"iso-8859-5, unicode-1-1;q=0.8">>, [
  755. {<<"iso-8859-5">>, 1000},
  756. {<<"unicode-1-1">>, 800}
  757. ]}
  758. ],
  759. [{V, fun() -> R = nonempty_list(V, fun conneg/2) end} || {V, R} <- Tests].
  760. nonempty_language_range_list_test_() ->
  761. %% {Value, Result}
  762. Tests = [
  763. {<<"da, en-gb;q=0.8, en;q=0.7">>, [
  764. {<<"da">>, 1000},
  765. {<<"en-gb">>, 800},
  766. {<<"en">>, 700}
  767. ]},
  768. {<<"en, en-US, en-cockney, i-cherokee, x-pig-latin">>, [
  769. {<<"en">>, 1000},
  770. {<<"en-us">>, 1000},
  771. {<<"en-cockney">>, 1000},
  772. {<<"i-cherokee">>, 1000},
  773. {<<"x-pig-latin">>, 1000}
  774. ]}
  775. ],
  776. [{V, fun() -> R = nonempty_list(V, fun language_range/2) end}
  777. || {V, R} <- Tests].
  778. nonempty_token_list_test_() ->
  779. %% {Value, Result}
  780. Tests = [
  781. {<<>>, {error, badarg}},
  782. {<<" ">>, {error, badarg}},
  783. {<<" , ">>, {error, badarg}},
  784. {<<",,,">>, {error, badarg}},
  785. {<<"a b">>, {error, badarg}},
  786. {<<"a , , , ">>, [<<"a">>]},
  787. {<<" , , , a">>, [<<"a">>]},
  788. {<<"a, , b">>, [<<"a">>, <<"b">>]},
  789. {<<"close">>, [<<"close">>]},
  790. {<<"keep-alive, upgrade">>, [<<"keep-alive">>, <<"upgrade">>]}
  791. ],
  792. [{V, fun() -> R = nonempty_list(V, fun token/2) end} || {V, R} <- Tests].
  793. media_range_list_test_() ->
  794. %% {Tokens, Result}
  795. Tests = [
  796. {<<"audio/*; q=0.2, audio/basic">>, [
  797. {{<<"audio">>, <<"*">>, []}, 200, []},
  798. {{<<"audio">>, <<"basic">>, []}, 1000, []}
  799. ]},
  800. {<<"text/plain; q=0.5, text/html, "
  801. "text/x-dvi; q=0.8, text/x-c">>, [
  802. {{<<"text">>, <<"plain">>, []}, 500, []},
  803. {{<<"text">>, <<"html">>, []}, 1000, []},
  804. {{<<"text">>, <<"x-dvi">>, []}, 800, []},
  805. {{<<"text">>, <<"x-c">>, []}, 1000, []}
  806. ]},
  807. {<<"text/*, text/html, text/html;level=1, */*">>, [
  808. {{<<"text">>, <<"*">>, []}, 1000, []},
  809. {{<<"text">>, <<"html">>, []}, 1000, []},
  810. {{<<"text">>, <<"html">>, [{<<"level">>, <<"1">>}]}, 1000, []},
  811. {{<<"*">>, <<"*">>, []}, 1000, []}
  812. ]},
  813. {<<"text/*;q=0.3, text/html;q=0.7, text/html;level=1, "
  814. "text/html;level=2;q=0.4, */*;q=0.5">>, [
  815. {{<<"text">>, <<"*">>, []}, 300, []},
  816. {{<<"text">>, <<"html">>, []}, 700, []},
  817. {{<<"text">>, <<"html">>, [{<<"level">>, <<"1">>}]}, 1000, []},
  818. {{<<"text">>, <<"html">>, [{<<"level">>, <<"2">>}]}, 400, []},
  819. {{<<"*">>, <<"*">>, []}, 500, []}
  820. ]},
  821. {<<"text/html;level=1;quoted=\"hi hi hi\";"
  822. "q=0.123;standalone;complex=gits, text/plain">>, [
  823. {{<<"text">>, <<"html">>,
  824. [{<<"level">>, <<"1">>}, {<<"quoted">>, <<"hi hi hi">>}]}, 123,
  825. [<<"standalone">>, {<<"complex">>, <<"gits">>}]},
  826. {{<<"text">>, <<"plain">>, []}, 1000, []}
  827. ]},
  828. {<<"text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2">>, [
  829. {{<<"text">>, <<"html">>, []}, 1000, []},
  830. {{<<"image">>, <<"gif">>, []}, 1000, []},
  831. {{<<"image">>, <<"jpeg">>, []}, 1000, []},
  832. {{<<"*">>, <<"*">>, []}, 200, []},
  833. {{<<"*">>, <<"*">>, []}, 200, []}
  834. ]}
  835. ],
  836. [{V, fun() -> R = list(V, fun media_range/2) end} || {V, R} <- Tests].
  837. entity_tag_match_test_() ->
  838. %% {Tokens, Result}
  839. Tests = [
  840. {<<"\"xyzzy\"">>, [{strong, <<"xyzzy">>}]},
  841. {<<"\"xyzzy\", W/\"r2d2xxxx\", \"c3piozzzz\"">>,
  842. [{strong, <<"xyzzy">>},
  843. {weak, <<"r2d2xxxx">>},
  844. {strong, <<"c3piozzzz">>}]},
  845. {<<"*">>, '*'}
  846. ],
  847. [{V, fun() -> R = entity_tag_match(V) end} || {V, R} <- Tests].
  848. http_date_test_() ->
  849. %% {Tokens, Result}
  850. Tests = [
  851. {<<"Sun, 06 Nov 1994 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}},
  852. {<<"Sunday, 06-Nov-94 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}},
  853. {<<"Sun Nov 6 08:49:37 1994">>, {{1994, 11, 6}, {8, 49, 37}}}
  854. ],
  855. [{V, fun() -> R = http_date(V) end} || {V, R} <- Tests].
  856. rfc1123_date_test_() ->
  857. %% {Tokens, Result}
  858. Tests = [
  859. {<<"Sun, 06 Nov 1994 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}}
  860. ],
  861. [{V, fun() -> R = rfc1123_date(V) end} || {V, R} <- Tests].
  862. rfc850_date_test_() ->
  863. %% {Tokens, Result}
  864. Tests = [
  865. {<<"Sunday, 06-Nov-94 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}}
  866. ],
  867. [{V, fun() -> R = rfc850_date(V) end} || {V, R} <- Tests].
  868. asctime_date_test_() ->
  869. %% {Tokens, Result}
  870. Tests = [
  871. {<<"Sun Nov 6 08:49:37 1994">>, {{1994, 11, 6}, {8, 49, 37}}}
  872. ],
  873. [{V, fun() -> R = asctime_date(V) end} || {V, R} <- Tests].
  874. connection_to_atom_test_() ->
  875. %% {Tokens, Result}
  876. Tests = [
  877. {[<<"close">>], close},
  878. {[<<"keep-alive">>], keepalive},
  879. {[<<"keep-alive">>, <<"upgrade">>], keepalive}
  880. ],
  881. [{lists:flatten(io_lib:format("~p", [T])),
  882. fun() -> R = connection_to_atom(T) end} || {T, R} <- Tests].
  883. content_type_test_() ->
  884. %% {ContentType, Result}
  885. Tests = [
  886. {<<"text/plain; charset=iso-8859-4">>,
  887. {<<"text">>, <<"plain">>, [{<<"charset">>, <<"iso-8859-4">>}]}},
  888. {<<"multipart/form-data \t;Boundary=\"MultipartIsUgly\"">>,
  889. {<<"multipart">>, <<"form-data">>, [
  890. {<<"boundary">>, <<"MultipartIsUgly">>}
  891. ]}},
  892. {<<"foo/bar; one=FirstParam; two=SecondParam">>,
  893. {<<"foo">>, <<"bar">>, [
  894. {<<"one">>, <<"FirstParam">>},
  895. {<<"two">>, <<"SecondParam">>}
  896. ]}}
  897. ],
  898. [{V, fun () -> R = content_type(V) end} || {V, R} <- Tests].
  899. digits_test_() ->
  900. %% {Digits, Result}
  901. Tests = [
  902. {<<"42 ">>, 42},
  903. {<<"69\t">>, 69},
  904. {<<"1337">>, 1337}
  905. ],
  906. [{V, fun() -> R = digits(V) end} || {V, R} <- Tests].
  907. x_www_form_urlencoded_test_() ->
  908. %% {Qs, Result}
  909. Tests = [
  910. {<<"">>, []},
  911. {<<"a=b">>, [{<<"a">>, <<"b">>}]},
  912. {<<"aaa=bbb">>, [{<<"aaa">>, <<"bbb">>}]},
  913. {<<"a&b">>, [{<<"a">>, true}, {<<"b">>, true}]},
  914. {<<"a=b&c&d=e">>, [{<<"a">>, <<"b">>},
  915. {<<"c">>, true}, {<<"d">>, <<"e">>}]},
  916. {<<"a=b=c=d=e&f=g">>, [{<<"a">>, <<"b=c=d=e">>}, {<<"f">>, <<"g">>}]},
  917. {<<"a+b=c+d">>, [{<<"a b">>, <<"c d">>}]}
  918. ],
  919. URLDecode = fun urldecode/1,
  920. [{Qs, fun() -> R = x_www_form_urlencoded(
  921. Qs, URLDecode) end} || {Qs, R} <- Tests].
  922. urldecode_test_() ->
  923. U = fun urldecode/2,
  924. [?_assertEqual(<<" ">>, U(<<"%20">>, crash)),
  925. ?_assertEqual(<<" ">>, U(<<"+">>, crash)),
  926. ?_assertEqual(<<0>>, U(<<"%00">>, crash)),
  927. ?_assertEqual(<<255>>, U(<<"%fF">>, crash)),
  928. ?_assertEqual(<<"123">>, U(<<"123">>, crash)),
  929. ?_assertEqual(<<"%i5">>, U(<<"%i5">>, skip)),
  930. ?_assertEqual(<<"%5">>, U(<<"%5">>, skip)),
  931. ?_assertError(badarg, U(<<"%i5">>, crash)),
  932. ?_assertError(badarg, U(<<"%5">>, crash))
  933. ].
  934. urlencode_test_() ->
  935. U = fun urlencode/2,
  936. [?_assertEqual(<<"%ff%00">>, U(<<255,0>>, [])),
  937. ?_assertEqual(<<"%FF%00">>, U(<<255,0>>, [upper])),
  938. ?_assertEqual(<<"+">>, U(<<" ">>, [])),
  939. ?_assertEqual(<<"%20">>, U(<<" ">>, [noplus])),
  940. ?_assertEqual(<<"aBc">>, U(<<"aBc">>, [])),
  941. ?_assertEqual(<<".-~_">>, U(<<".-~_">>, [])),
  942. ?_assertEqual(<<"%ff+">>, urlencode(<<255, " ">>))
  943. ].
  944. -endif.