cowboy_http.erl 34 KB

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