cowboy_http.erl 35 KB

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