cowboy_req.erl 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950
  1. %% Copyright (c) 2011-2017, 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. -module(cowboy_req).
  16. -ifdef(OTP_RELEASE).
  17. -compile({nowarn_deprecated_function, [{erlang, get_stacktrace, 0}]}).
  18. -endif.
  19. %% Request.
  20. -export([method/1]).
  21. -export([version/1]).
  22. -export([peer/1]).
  23. -export([sock/1]).
  24. -export([cert/1]).
  25. -export([scheme/1]).
  26. -export([host/1]).
  27. -export([host_info/1]).
  28. -export([port/1]).
  29. -export([path/1]).
  30. -export([path_info/1]).
  31. -export([qs/1]).
  32. -export([parse_qs/1]).
  33. -export([match_qs/2]).
  34. -export([uri/1]).
  35. -export([uri/2]).
  36. -export([binding/2]).
  37. -export([binding/3]).
  38. -export([bindings/1]).
  39. -export([header/2]).
  40. -export([header/3]).
  41. -export([headers/1]).
  42. -export([parse_header/2]).
  43. -export([parse_header/3]).
  44. -export([parse_cookies/1]).
  45. -export([match_cookies/2]).
  46. %% Request body.
  47. -export([has_body/1]).
  48. -export([body_length/1]).
  49. -export([read_body/1]).
  50. -export([read_body/2]).
  51. -export([read_urlencoded_body/1]).
  52. -export([read_urlencoded_body/2]).
  53. -export([read_and_match_urlencoded_body/2]).
  54. -export([read_and_match_urlencoded_body/3]).
  55. %% Multipart.
  56. -export([read_part/1]).
  57. -export([read_part/2]).
  58. -export([read_part_body/1]).
  59. -export([read_part_body/2]).
  60. %% Response.
  61. -export([set_resp_cookie/3]).
  62. -export([set_resp_cookie/4]).
  63. -export([resp_header/2]).
  64. -export([resp_header/3]).
  65. -export([resp_headers/1]).
  66. -export([set_resp_header/3]).
  67. -export([set_resp_headers/2]).
  68. -export([has_resp_header/2]).
  69. -export([delete_resp_header/2]).
  70. -export([set_resp_body/2]).
  71. %% @todo set_resp_body/3 with a ContentType or even Headers argument, to set content headers.
  72. -export([has_resp_body/1]).
  73. -export([inform/2]).
  74. -export([inform/3]).
  75. -export([reply/2]).
  76. -export([reply/3]).
  77. -export([reply/4]).
  78. -export([stream_reply/2]).
  79. -export([stream_reply/3]).
  80. %% @todo stream_body/2 (nofin)
  81. -export([stream_body/3]).
  82. %% @todo stream_events/2 (nofin)
  83. -export([stream_events/3]).
  84. -export([stream_trailers/2]).
  85. -export([push/3]).
  86. -export([push/4]).
  87. %% Internal.
  88. -export([response_headers/2]).
  89. %% @todo Get rid of this type, use cow_cookie directly.
  90. -type cookie_opts() :: map().
  91. -export_type([cookie_opts/0]).
  92. -type read_body_opts() :: #{
  93. length => non_neg_integer() | infinity,
  94. period => non_neg_integer(),
  95. timeout => timeout()
  96. }.
  97. -export_type([read_body_opts/0]).
  98. %% While sendfile allows a Len of 0 that means "everything past Offset",
  99. %% Cowboy expects the real length as it is used as metadata.
  100. %% @todo We should probably explicitly reject it.
  101. -type resp_body() :: iodata()
  102. | {sendfile, non_neg_integer(), non_neg_integer(), file:name_all()}.
  103. -export_type([resp_body/0]).
  104. -type push_opts() :: #{
  105. method => binary(),
  106. scheme => binary(),
  107. host => binary(),
  108. port => inet:port_number(),
  109. qs => binary()
  110. }.
  111. -export_type([push_opts/0]).
  112. -type req() :: #{
  113. %% Public interface.
  114. method := binary(),
  115. version := cowboy:http_version() | atom(),
  116. scheme := binary(),
  117. host := binary(),
  118. port := inet:port_number(),
  119. path := binary(),
  120. qs := binary(),
  121. headers := cowboy:http_headers(),
  122. peer := {inet:ip_address(), inet:port_number()},
  123. sock := {inet:ip_address(), inet:port_number()},
  124. cert := binary() | undefined,
  125. %% Private interface.
  126. ref := ranch:ref(),
  127. pid := pid(),
  128. streamid := cowboy_stream:streamid(),
  129. host_info => cowboy_router:tokens(),
  130. path_info => cowboy_router:tokens(),
  131. bindings => cowboy_router:bindings(),
  132. has_body := boolean(),
  133. body_length := non_neg_integer() | undefined,
  134. has_read_body => true,
  135. multipart => {binary(), binary()} | done,
  136. has_sent_resp => headers | true,
  137. resp_cookies => #{iodata() => iodata()},
  138. resp_headers => #{binary() => iodata()},
  139. resp_body => resp_body(),
  140. proxy_header => ranch_proxy_header:proxy_info(),
  141. media_type => {binary(), binary(), [{binary(), binary()}]},
  142. language => binary() | undefined,
  143. charset => binary() | undefined,
  144. range => {binary(), binary()
  145. | [{non_neg_integer(), non_neg_integer() | infinity} | neg_integer()]},
  146. websocket_version => 7 | 8 | 13
  147. }.
  148. -export_type([req/0]).
  149. %% Request.
  150. -spec method(req()) -> binary().
  151. method(#{method := Method}) ->
  152. Method.
  153. -spec version(req()) -> cowboy:http_version().
  154. version(#{version := Version}) ->
  155. Version.
  156. -spec peer(req()) -> {inet:ip_address(), inet:port_number()}.
  157. peer(#{peer := Peer}) ->
  158. Peer.
  159. -spec sock(req()) -> {inet:ip_address(), inet:port_number()}.
  160. sock(#{sock := Sock}) ->
  161. Sock.
  162. -spec cert(req()) -> binary() | undefined.
  163. cert(#{cert := Cert}) ->
  164. Cert.
  165. -spec scheme(req()) -> binary().
  166. scheme(#{scheme := Scheme}) ->
  167. Scheme.
  168. -spec host(req()) -> binary().
  169. host(#{host := Host}) ->
  170. Host.
  171. %% @todo The host_info is undefined if cowboy_router isn't used. Do we want to crash?
  172. -spec host_info(req()) -> cowboy_router:tokens() | undefined.
  173. host_info(#{host_info := HostInfo}) ->
  174. HostInfo.
  175. -spec port(req()) -> inet:port_number().
  176. port(#{port := Port}) ->
  177. Port.
  178. -spec path(req()) -> binary().
  179. path(#{path := Path}) ->
  180. Path.
  181. %% @todo The path_info is undefined if cowboy_router isn't used. Do we want to crash?
  182. -spec path_info(req()) -> cowboy_router:tokens() | undefined.
  183. path_info(#{path_info := PathInfo}) ->
  184. PathInfo.
  185. -spec qs(req()) -> binary().
  186. qs(#{qs := Qs}) ->
  187. Qs.
  188. %% @todo Might be useful to limit the number of keys.
  189. -spec parse_qs(req()) -> [{binary(), binary() | true}].
  190. parse_qs(#{qs := Qs}) ->
  191. try
  192. cow_qs:parse_qs(Qs)
  193. catch _:_ ->
  194. erlang:raise(exit, {request_error, qs,
  195. 'Malformed query string; application/x-www-form-urlencoded expected.'
  196. }, erlang:get_stacktrace())
  197. end.
  198. -spec match_qs(cowboy:fields(), req()) -> map().
  199. match_qs(Fields, Req) ->
  200. case filter(Fields, kvlist_to_map(Fields, parse_qs(Req))) of
  201. {ok, Map} ->
  202. Map;
  203. {error, Errors} ->
  204. exit({request_error, {match_qs, Errors},
  205. 'Query string validation constraints failed for the reasons provided.'})
  206. end.
  207. -spec uri(req()) -> iodata().
  208. uri(Req) ->
  209. uri(Req, #{}).
  210. -spec uri(req(), map()) -> iodata().
  211. uri(#{scheme := Scheme0, host := Host0, port := Port0,
  212. path := Path0, qs := Qs0}, Opts) ->
  213. Scheme = case maps:get(scheme, Opts, Scheme0) of
  214. S = undefined -> S;
  215. S -> iolist_to_binary(S)
  216. end,
  217. Host = maps:get(host, Opts, Host0),
  218. Port = maps:get(port, Opts, Port0),
  219. {Path, Qs} = case maps:get(path, Opts, Path0) of
  220. <<"*">> -> {<<>>, <<>>};
  221. P -> {P, maps:get(qs, Opts, Qs0)}
  222. end,
  223. Fragment = maps:get(fragment, Opts, undefined),
  224. [uri_host(Scheme, Scheme0, Port, Host), uri_path(Path), uri_qs(Qs), uri_fragment(Fragment)].
  225. uri_host(_, _, _, undefined) -> <<>>;
  226. uri_host(Scheme, Scheme0, Port, Host) ->
  227. case iolist_size(Host) of
  228. 0 -> <<>>;
  229. _ -> [uri_scheme(Scheme), <<"//">>, Host, uri_port(Scheme, Scheme0, Port)]
  230. end.
  231. uri_scheme(undefined) -> <<>>;
  232. uri_scheme(Scheme) ->
  233. case iolist_size(Scheme) of
  234. 0 -> Scheme;
  235. _ -> [Scheme, $:]
  236. end.
  237. uri_port(_, _, undefined) -> <<>>;
  238. uri_port(undefined, <<"http">>, 80) -> <<>>;
  239. uri_port(undefined, <<"https">>, 443) -> <<>>;
  240. uri_port(<<"http">>, _, 80) -> <<>>;
  241. uri_port(<<"https">>, _, 443) -> <<>>;
  242. uri_port(_, _, Port) ->
  243. [$:, integer_to_binary(Port)].
  244. uri_path(undefined) -> <<>>;
  245. uri_path(Path) -> Path.
  246. uri_qs(undefined) -> <<>>;
  247. uri_qs(Qs) ->
  248. case iolist_size(Qs) of
  249. 0 -> Qs;
  250. _ -> [$?, Qs]
  251. end.
  252. uri_fragment(undefined) -> <<>>;
  253. uri_fragment(Fragment) ->
  254. case iolist_size(Fragment) of
  255. 0 -> Fragment;
  256. _ -> [$#, Fragment]
  257. end.
  258. -ifdef(TEST).
  259. uri1_test() ->
  260. <<"http://localhost/path">> = iolist_to_binary(uri(#{
  261. scheme => <<"http">>, host => <<"localhost">>, port => 80,
  262. path => <<"/path">>, qs => <<>>})),
  263. <<"http://localhost:443/path">> = iolist_to_binary(uri(#{
  264. scheme => <<"http">>, host => <<"localhost">>, port => 443,
  265. path => <<"/path">>, qs => <<>>})),
  266. <<"http://localhost:8080/path">> = iolist_to_binary(uri(#{
  267. scheme => <<"http">>, host => <<"localhost">>, port => 8080,
  268. path => <<"/path">>, qs => <<>>})),
  269. <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(#{
  270. scheme => <<"http">>, host => <<"localhost">>, port => 8080,
  271. path => <<"/path">>, qs => <<"dummy=2785">>})),
  272. <<"https://localhost/path">> = iolist_to_binary(uri(#{
  273. scheme => <<"https">>, host => <<"localhost">>, port => 443,
  274. path => <<"/path">>, qs => <<>>})),
  275. <<"https://localhost:8443/path">> = iolist_to_binary(uri(#{
  276. scheme => <<"https">>, host => <<"localhost">>, port => 8443,
  277. path => <<"/path">>, qs => <<>>})),
  278. <<"https://localhost:8443/path?dummy=2785">> = iolist_to_binary(uri(#{
  279. scheme => <<"https">>, host => <<"localhost">>, port => 8443,
  280. path => <<"/path">>, qs => <<"dummy=2785">>})),
  281. ok.
  282. uri2_test() ->
  283. Req = #{
  284. scheme => <<"http">>, host => <<"localhost">>, port => 8080,
  285. path => <<"/path">>, qs => <<"dummy=2785">>
  286. },
  287. <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{})),
  288. %% Disable individual components.
  289. <<"//localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => undefined})),
  290. <<"/path?dummy=2785">> = iolist_to_binary(uri(Req, #{host => undefined})),
  291. <<"http://localhost/path?dummy=2785">> = iolist_to_binary(uri(Req, #{port => undefined})),
  292. <<"http://localhost:8080?dummy=2785">> = iolist_to_binary(uri(Req, #{path => undefined})),
  293. <<"http://localhost:8080/path">> = iolist_to_binary(uri(Req, #{qs => undefined})),
  294. <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{fragment => undefined})),
  295. <<"http://localhost:8080">> = iolist_to_binary(uri(Req, #{path => undefined, qs => undefined})),
  296. <<>> = iolist_to_binary(uri(Req, #{host => undefined, path => undefined, qs => undefined})),
  297. %% Empty values.
  298. <<"//localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => <<>>})),
  299. <<"//localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => ""})),
  300. <<"//localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => [<<>>]})),
  301. <<"/path?dummy=2785">> = iolist_to_binary(uri(Req, #{host => <<>>})),
  302. <<"/path?dummy=2785">> = iolist_to_binary(uri(Req, #{host => ""})),
  303. <<"/path?dummy=2785">> = iolist_to_binary(uri(Req, #{host => [<<>>]})),
  304. <<"http://localhost:8080?dummy=2785">> = iolist_to_binary(uri(Req, #{path => <<>>})),
  305. <<"http://localhost:8080?dummy=2785">> = iolist_to_binary(uri(Req, #{path => ""})),
  306. <<"http://localhost:8080?dummy=2785">> = iolist_to_binary(uri(Req, #{path => [<<>>]})),
  307. <<"http://localhost:8080/path">> = iolist_to_binary(uri(Req, #{qs => <<>>})),
  308. <<"http://localhost:8080/path">> = iolist_to_binary(uri(Req, #{qs => ""})),
  309. <<"http://localhost:8080/path">> = iolist_to_binary(uri(Req, #{qs => [<<>>]})),
  310. <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{fragment => <<>>})),
  311. <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{fragment => ""})),
  312. <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{fragment => [<<>>]})),
  313. %% Port is integer() | undefined.
  314. {'EXIT', _} = (catch iolist_to_binary(uri(Req, #{port => <<>>}))),
  315. {'EXIT', _} = (catch iolist_to_binary(uri(Req, #{port => ""}))),
  316. {'EXIT', _} = (catch iolist_to_binary(uri(Req, #{port => [<<>>]}))),
  317. %% Update components.
  318. <<"https://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => "https"})),
  319. <<"http://example.org:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{host => "example.org"})),
  320. <<"http://localhost:123/path?dummy=2785">> = iolist_to_binary(uri(Req, #{port => 123})),
  321. <<"http://localhost:8080/custom?dummy=2785">> = iolist_to_binary(uri(Req, #{path => "/custom"})),
  322. <<"http://localhost:8080/path?smart=42">> = iolist_to_binary(uri(Req, #{qs => "smart=42"})),
  323. <<"http://localhost:8080/path?dummy=2785#intro">> = iolist_to_binary(uri(Req, #{fragment => "intro"})),
  324. %% Interesting combinations.
  325. <<"http://localhost/path?dummy=2785">> = iolist_to_binary(uri(Req, #{port => 80})),
  326. <<"https://localhost/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => "https", port => 443})),
  327. ok.
  328. -endif.
  329. -spec binding(atom(), req()) -> any() | undefined.
  330. binding(Name, Req) ->
  331. binding(Name, Req, undefined).
  332. -spec binding(atom(), req(), Default) -> any() | Default when Default::any().
  333. binding(Name, #{bindings := Bindings}, Default) when is_atom(Name) ->
  334. case Bindings of
  335. #{Name := Value} -> Value;
  336. _ -> Default
  337. end;
  338. binding(Name, _, Default) when is_atom(Name) ->
  339. Default.
  340. -spec bindings(req()) -> cowboy_router:bindings().
  341. bindings(#{bindings := Bindings}) ->
  342. Bindings;
  343. bindings(_) ->
  344. #{}.
  345. -spec header(binary(), req()) -> binary() | undefined.
  346. header(Name, Req) ->
  347. header(Name, Req, undefined).
  348. -spec header(binary(), req(), Default) -> binary() | Default when Default::any().
  349. header(Name, #{headers := Headers}, Default) ->
  350. maps:get(Name, Headers, Default).
  351. -spec headers(req()) -> cowboy:http_headers().
  352. headers(#{headers := Headers}) ->
  353. Headers.
  354. -spec parse_header(binary(), Req) -> any() when Req::req().
  355. parse_header(Name = <<"content-length">>, Req) ->
  356. parse_header(Name, Req, 0);
  357. parse_header(Name = <<"cookie">>, Req) ->
  358. parse_header(Name, Req, []);
  359. parse_header(Name, Req) ->
  360. parse_header(Name, Req, undefined).
  361. -spec parse_header(binary(), Req, any()) -> any() when Req::req().
  362. parse_header(Name, Req, Default) ->
  363. try
  364. parse_header(Name, Req, Default, parse_header_fun(Name))
  365. catch _:_ ->
  366. erlang:raise(exit, {request_error, {header, Name},
  367. 'Malformed header. Please consult the relevant specification.'
  368. }, erlang:get_stacktrace())
  369. end.
  370. parse_header_fun(<<"accept">>) -> fun cow_http_hd:parse_accept/1;
  371. parse_header_fun(<<"accept-charset">>) -> fun cow_http_hd:parse_accept_charset/1;
  372. parse_header_fun(<<"accept-encoding">>) -> fun cow_http_hd:parse_accept_encoding/1;
  373. parse_header_fun(<<"accept-language">>) -> fun cow_http_hd:parse_accept_language/1;
  374. parse_header_fun(<<"authorization">>) -> fun cow_http_hd:parse_authorization/1;
  375. parse_header_fun(<<"connection">>) -> fun cow_http_hd:parse_connection/1;
  376. parse_header_fun(<<"content-length">>) -> fun cow_http_hd:parse_content_length/1;
  377. parse_header_fun(<<"content-type">>) -> fun cow_http_hd:parse_content_type/1;
  378. parse_header_fun(<<"cookie">>) -> fun cow_cookie:parse_cookie/1;
  379. parse_header_fun(<<"expect">>) -> fun cow_http_hd:parse_expect/1;
  380. parse_header_fun(<<"if-match">>) -> fun cow_http_hd:parse_if_match/1;
  381. parse_header_fun(<<"if-modified-since">>) -> fun cow_http_hd:parse_if_modified_since/1;
  382. parse_header_fun(<<"if-none-match">>) -> fun cow_http_hd:parse_if_none_match/1;
  383. parse_header_fun(<<"if-range">>) -> fun cow_http_hd:parse_if_range/1;
  384. parse_header_fun(<<"if-unmodified-since">>) -> fun cow_http_hd:parse_if_unmodified_since/1;
  385. parse_header_fun(<<"range">>) -> fun cow_http_hd:parse_range/1;
  386. parse_header_fun(<<"sec-websocket-extensions">>) -> fun cow_http_hd:parse_sec_websocket_extensions/1;
  387. parse_header_fun(<<"sec-websocket-protocol">>) -> fun cow_http_hd:parse_sec_websocket_protocol_req/1;
  388. parse_header_fun(<<"sec-websocket-version">>) -> fun cow_http_hd:parse_sec_websocket_version_req/1;
  389. parse_header_fun(<<"upgrade">>) -> fun cow_http_hd:parse_upgrade/1;
  390. parse_header_fun(<<"x-forwarded-for">>) -> fun cow_http_hd:parse_x_forwarded_for/1.
  391. parse_header(Name, Req, Default, ParseFun) ->
  392. case header(Name, Req) of
  393. undefined -> Default;
  394. Value -> ParseFun(Value)
  395. end.
  396. -spec parse_cookies(req()) -> [{binary(), binary()}].
  397. parse_cookies(Req) ->
  398. parse_header(<<"cookie">>, Req).
  399. -spec match_cookies(cowboy:fields(), req()) -> map().
  400. match_cookies(Fields, Req) ->
  401. case filter(Fields, kvlist_to_map(Fields, parse_cookies(Req))) of
  402. {ok, Map} ->
  403. Map;
  404. {error, Errors} ->
  405. exit({request_error, {match_cookies, Errors},
  406. 'Cookie validation constraints failed for the reasons provided.'})
  407. end.
  408. %% Request body.
  409. -spec has_body(req()) -> boolean().
  410. has_body(#{has_body := HasBody}) ->
  411. HasBody.
  412. %% The length may not be known if HTTP/1.1 with a transfer-encoding;
  413. %% or HTTP/2 with no content-length header. The length is always
  414. %% known once the body has been completely read.
  415. -spec body_length(req()) -> undefined | non_neg_integer().
  416. body_length(#{body_length := Length}) ->
  417. Length.
  418. -spec read_body(Req) -> {ok, binary(), Req} | {more, binary(), Req} when Req::req().
  419. read_body(Req) ->
  420. read_body(Req, #{}).
  421. -spec read_body(Req, read_body_opts()) -> {ok, binary(), Req} | {more, binary(), Req} when Req::req().
  422. read_body(Req=#{has_body := false}, _) ->
  423. {ok, <<>>, Req};
  424. read_body(Req=#{has_read_body := true}, _) ->
  425. {ok, <<>>, Req};
  426. read_body(Req=#{pid := Pid, streamid := StreamID}, Opts) ->
  427. Length = maps:get(length, Opts, 8000000),
  428. Period = maps:get(period, Opts, 15000),
  429. Timeout = maps:get(timeout, Opts, Period + 1000),
  430. Ref = make_ref(),
  431. Pid ! {{Pid, StreamID}, {read_body, Ref, Length, Period}},
  432. receive
  433. {request_body, Ref, nofin, Body} ->
  434. {more, Body, Req};
  435. {request_body, Ref, fin, BodyLength, Body} ->
  436. {ok, Body, set_body_length(Req, BodyLength)}
  437. after Timeout ->
  438. exit(timeout)
  439. end.
  440. set_body_length(Req=#{headers := Headers}, BodyLength) ->
  441. Req#{
  442. headers => Headers#{<<"content-length">> => integer_to_binary(BodyLength)},
  443. body_length => BodyLength,
  444. has_read_body => true
  445. }.
  446. -spec read_urlencoded_body(Req) -> {ok, [{binary(), binary() | true}], Req} when Req::req().
  447. read_urlencoded_body(Req) ->
  448. read_urlencoded_body(Req, #{length => 64000, period => 5000}).
  449. -spec read_urlencoded_body(Req, read_body_opts()) -> {ok, [{binary(), binary() | true}], Req} when Req::req().
  450. read_urlencoded_body(Req0, Opts) ->
  451. case read_body(Req0, Opts) of
  452. {ok, Body, Req} ->
  453. try
  454. {ok, cow_qs:parse_qs(Body), Req}
  455. catch _:_ ->
  456. erlang:raise(exit, {request_error, urlencoded_body,
  457. 'Malformed body; application/x-www-form-urlencoded expected.'
  458. }, erlang:get_stacktrace())
  459. end;
  460. {more, Body, _} ->
  461. Length = maps:get(length, Opts, 64000),
  462. if
  463. byte_size(Body) < Length ->
  464. exit({request_error, timeout,
  465. 'The request body was not received within the configured time.'});
  466. true ->
  467. exit({request_error, payload_too_large,
  468. 'The request body is larger than allowed by configuration.'})
  469. end
  470. end.
  471. -spec read_and_match_urlencoded_body(cowboy:fields(), Req)
  472. -> {ok, map(), Req} when Req::req().
  473. read_and_match_urlencoded_body(Fields, Req) ->
  474. read_and_match_urlencoded_body(Fields, Req, #{length => 64000, period => 5000}).
  475. -spec read_and_match_urlencoded_body(cowboy:fields(), Req, read_body_opts())
  476. -> {ok, map(), Req} when Req::req().
  477. read_and_match_urlencoded_body(Fields, Req0, Opts) ->
  478. {ok, Qs, Req} = read_urlencoded_body(Req0, Opts),
  479. case filter(Fields, kvlist_to_map(Fields, Qs)) of
  480. {ok, Map} ->
  481. {ok, Map, Req};
  482. {error, Errors} ->
  483. exit({request_error, {read_and_match_urlencoded_body, Errors},
  484. 'Urlencoded request body validation constraints failed for the reasons provided.'})
  485. end.
  486. %% Multipart.
  487. -spec read_part(Req)
  488. -> {ok, cowboy:http_headers(), Req} | {done, Req}
  489. when Req::req().
  490. read_part(Req) ->
  491. read_part(Req, #{length => 64000, period => 5000}).
  492. -spec read_part(Req, read_body_opts())
  493. -> {ok, cowboy:http_headers(), Req} | {done, Req}
  494. when Req::req().
  495. read_part(Req, Opts) ->
  496. case maps:is_key(multipart, Req) of
  497. true ->
  498. {Data, Req2} = stream_multipart(Req, Opts, headers),
  499. read_part(Data, Opts, Req2);
  500. false ->
  501. read_part(init_multipart(Req), Opts)
  502. end.
  503. read_part(Buffer, Opts, Req=#{multipart := {Boundary, _}}) ->
  504. try cow_multipart:parse_headers(Buffer, Boundary) of
  505. more ->
  506. {Data, Req2} = stream_multipart(Req, Opts, headers),
  507. read_part(<< Buffer/binary, Data/binary >>, Opts, Req2);
  508. {more, Buffer2} ->
  509. {Data, Req2} = stream_multipart(Req, Opts, headers),
  510. read_part(<< Buffer2/binary, Data/binary >>, Opts, Req2);
  511. {ok, Headers0, Rest} ->
  512. Headers = maps:from_list(Headers0),
  513. %% Reject multipart content containing duplicate headers.
  514. true = map_size(Headers) =:= length(Headers0),
  515. {ok, Headers, Req#{multipart => {Boundary, Rest}}};
  516. %% Ignore epilogue.
  517. {done, _} ->
  518. {done, Req#{multipart => done}}
  519. catch _:_ ->
  520. erlang:raise(exit, {request_error, {multipart, headers},
  521. 'Malformed body; multipart expected.'
  522. }, erlang:get_stacktrace())
  523. end.
  524. -spec read_part_body(Req)
  525. -> {ok, binary(), Req} | {more, binary(), Req}
  526. when Req::req().
  527. read_part_body(Req) ->
  528. read_part_body(Req, #{}).
  529. -spec read_part_body(Req, read_body_opts())
  530. -> {ok, binary(), Req} | {more, binary(), Req}
  531. when Req::req().
  532. read_part_body(Req, Opts) ->
  533. case maps:is_key(multipart, Req) of
  534. true ->
  535. read_part_body(<<>>, Opts, Req, <<>>);
  536. false ->
  537. read_part_body(init_multipart(Req), Opts)
  538. end.
  539. read_part_body(Buffer, Opts, Req=#{multipart := {Boundary, _}}, Acc) ->
  540. Length = maps:get(length, Opts, 8000000),
  541. case byte_size(Acc) > Length of
  542. true ->
  543. {more, Acc, Req#{multipart => {Boundary, Buffer}}};
  544. false ->
  545. {Data, Req2} = stream_multipart(Req, Opts, body),
  546. case cow_multipart:parse_body(<< Buffer/binary, Data/binary >>, Boundary) of
  547. {ok, Body} ->
  548. read_part_body(<<>>, Opts, Req2, << Acc/binary, Body/binary >>);
  549. {ok, Body, Rest} ->
  550. read_part_body(Rest, Opts, Req2, << Acc/binary, Body/binary >>);
  551. done ->
  552. {ok, Acc, Req2};
  553. {done, Body} ->
  554. {ok, << Acc/binary, Body/binary >>, Req2};
  555. {done, Body, Rest} ->
  556. {ok, << Acc/binary, Body/binary >>,
  557. Req2#{multipart => {Boundary, Rest}}}
  558. end
  559. end.
  560. init_multipart(Req) ->
  561. {<<"multipart">>, _, Params} = parse_header(<<"content-type">>, Req),
  562. case lists:keyfind(<<"boundary">>, 1, Params) of
  563. {_, Boundary} ->
  564. Req#{multipart => {Boundary, <<>>}};
  565. false ->
  566. exit({request_error, {multipart, boundary},
  567. 'Missing boundary parameter for multipart media type.'})
  568. end.
  569. stream_multipart(Req=#{multipart := done}, _, _) ->
  570. {<<>>, Req};
  571. stream_multipart(Req=#{multipart := {_, <<>>}}, Opts, Type) ->
  572. case read_body(Req, Opts) of
  573. {more, Data, Req2} ->
  574. {Data, Req2};
  575. %% We crash when the data ends unexpectedly.
  576. {ok, <<>>, _} ->
  577. exit({request_error, {multipart, Type},
  578. 'Malformed body; multipart expected.'});
  579. {ok, Data, Req2} ->
  580. {Data, Req2}
  581. end;
  582. stream_multipart(Req=#{multipart := {Boundary, Buffer}}, _, _) ->
  583. {Buffer, Req#{multipart => {Boundary, <<>>}}}.
  584. %% Response.
  585. -spec set_resp_cookie(iodata(), iodata(), Req)
  586. -> Req when Req::req().
  587. set_resp_cookie(Name, Value, Req) ->
  588. set_resp_cookie(Name, Value, Req, #{}).
  589. %% The cookie name cannot contain any of the following characters:
  590. %% =,;\s\t\r\n\013\014
  591. %%
  592. %% The cookie value cannot contain any of the following characters:
  593. %% ,; \t\r\n\013\014
  594. %% @todo Fix the cookie_opts() type.
  595. -spec set_resp_cookie(binary(), iodata(), Req, cookie_opts())
  596. -> Req when Req::req().
  597. set_resp_cookie(Name, Value, Req, Opts) ->
  598. Cookie = cow_cookie:setcookie(Name, Value, maps:to_list(Opts)),
  599. RespCookies = maps:get(resp_cookies, Req, #{}),
  600. Req#{resp_cookies => RespCookies#{Name => Cookie}}.
  601. %% @todo We could add has_resp_cookie and delete_resp_cookie now.
  602. -spec set_resp_header(binary(), iodata(), Req)
  603. -> Req when Req::req().
  604. set_resp_header(Name, Value, Req=#{resp_headers := RespHeaders}) ->
  605. Req#{resp_headers => RespHeaders#{Name => Value}};
  606. set_resp_header(Name,Value, Req) ->
  607. Req#{resp_headers => #{Name => Value}}.
  608. -spec set_resp_headers(cowboy:http_headers(), Req)
  609. -> Req when Req::req().
  610. set_resp_headers(Headers, Req=#{resp_headers := RespHeaders}) ->
  611. Req#{resp_headers => maps:merge(RespHeaders, Headers)};
  612. set_resp_headers(Headers, Req) ->
  613. Req#{resp_headers => Headers}.
  614. -spec resp_header(binary(), req()) -> binary() | undefined.
  615. resp_header(Name, Req) ->
  616. resp_header(Name, Req, undefined).
  617. -spec resp_header(binary(), req(), Default)
  618. -> binary() | Default when Default::any().
  619. resp_header(Name, #{resp_headers := Headers}, Default) ->
  620. maps:get(Name, Headers, Default);
  621. resp_header(_, #{}, Default) ->
  622. Default.
  623. -spec resp_headers(req()) -> cowboy:http_headers().
  624. resp_headers(#{resp_headers := RespHeaders}) ->
  625. RespHeaders;
  626. resp_headers(#{}) ->
  627. #{}.
  628. -spec set_resp_body(resp_body(), Req) -> Req when Req::req().
  629. set_resp_body(Body, Req) ->
  630. Req#{resp_body => Body}.
  631. -spec has_resp_header(binary(), req()) -> boolean().
  632. has_resp_header(Name, #{resp_headers := RespHeaders}) ->
  633. maps:is_key(Name, RespHeaders);
  634. has_resp_header(_, _) ->
  635. false.
  636. -spec has_resp_body(req()) -> boolean().
  637. has_resp_body(#{resp_body := {sendfile, _, _, _}}) ->
  638. true;
  639. has_resp_body(#{resp_body := RespBody}) ->
  640. iolist_size(RespBody) > 0;
  641. has_resp_body(_) ->
  642. false.
  643. -spec delete_resp_header(binary(), Req)
  644. -> Req when Req::req().
  645. delete_resp_header(Name, Req=#{resp_headers := RespHeaders}) ->
  646. Req#{resp_headers => maps:remove(Name, RespHeaders)};
  647. %% There are no resp headers so we have nothing to delete.
  648. delete_resp_header(_, Req) ->
  649. Req.
  650. -spec inform(cowboy:http_status(), req()) -> ok.
  651. inform(Status, Req) ->
  652. inform(Status, #{}, Req).
  653. -spec inform(cowboy:http_status(), cowboy:http_headers(), req()) -> ok.
  654. inform(_, _, #{has_sent_resp := _}) ->
  655. error(function_clause); %% @todo Better error message.
  656. inform(Status, Headers, #{pid := Pid, streamid := StreamID})
  657. when is_integer(Status); is_binary(Status) ->
  658. Pid ! {{Pid, StreamID}, {inform, Status, Headers}},
  659. ok.
  660. -spec reply(cowboy:http_status(), Req) -> Req when Req::req().
  661. reply(Status, Req) ->
  662. reply(Status, #{}, Req).
  663. -spec reply(cowboy:http_status(), cowboy:http_headers(), Req)
  664. -> Req when Req::req().
  665. reply(Status, Headers, Req=#{resp_body := Body}) ->
  666. reply(Status, Headers, Body, Req);
  667. reply(Status, Headers, Req) ->
  668. reply(Status, Headers, <<>>, Req).
  669. -spec reply(cowboy:http_status(), cowboy:http_headers(), resp_body(), Req)
  670. -> Req when Req::req().
  671. reply(_, _, _, #{has_sent_resp := _}) ->
  672. error(function_clause); %% @todo Better error message.
  673. reply(Status, Headers, {sendfile, _, 0, _}, Req)
  674. when is_integer(Status); is_binary(Status) ->
  675. do_reply(Status, Headers#{
  676. <<"content-length">> => <<"0">>
  677. }, <<>>, Req);
  678. reply(Status, Headers, SendFile = {sendfile, _, Len, _}, Req)
  679. when is_integer(Status); is_binary(Status) ->
  680. do_reply(Status, Headers#{
  681. <<"content-length">> => integer_to_binary(Len)
  682. }, SendFile, Req);
  683. %% 204 responses must not include content-length. (RFC7230 3.3.1, RFC7230 3.3.2)
  684. reply(Status=204, Headers, Body, Req) ->
  685. do_reply(Status, Headers, Body, Req);
  686. reply(Status= <<"204",_/bits>>, Headers, Body, Req) ->
  687. do_reply(Status, Headers, Body, Req);
  688. reply(Status, Headers, Body, Req)
  689. when is_integer(Status); is_binary(Status) ->
  690. do_reply(Status, Headers#{
  691. <<"content-length">> => integer_to_binary(iolist_size(Body))
  692. }, Body, Req).
  693. %% Don't send any body for HEAD responses. While the protocol code is
  694. %% supposed to enforce this rule, we prefer to avoid copying too much
  695. %% data around if we can avoid it.
  696. do_reply(Status, Headers, _, Req=#{pid := Pid, streamid := StreamID, method := <<"HEAD">>}) ->
  697. Pid ! {{Pid, StreamID}, {response, Status, response_headers(Headers, Req), <<>>}},
  698. done_replying(Req, true);
  699. do_reply(Status, Headers, Body, Req=#{pid := Pid, streamid := StreamID}) ->
  700. Pid ! {{Pid, StreamID}, {response, Status, response_headers(Headers, Req), Body}},
  701. done_replying(Req, true).
  702. done_replying(Req, HasSentResp) ->
  703. maps:without([resp_cookies, resp_headers, resp_body], Req#{has_sent_resp => HasSentResp}).
  704. -spec stream_reply(cowboy:http_status(), Req) -> Req when Req::req().
  705. stream_reply(Status, Req) ->
  706. stream_reply(Status, #{}, Req).
  707. -spec stream_reply(cowboy:http_status(), cowboy:http_headers(), Req)
  708. -> Req when Req::req().
  709. stream_reply(_, _, #{has_sent_resp := _}) ->
  710. error(function_clause);
  711. stream_reply(Status, Headers=#{}, Req=#{pid := Pid, streamid := StreamID})
  712. when is_integer(Status); is_binary(Status) ->
  713. Pid ! {{Pid, StreamID}, {headers, Status, response_headers(Headers, Req)}},
  714. done_replying(Req, headers).
  715. -spec stream_body(iodata(), fin | nofin, req()) -> ok.
  716. %% Error out if headers were not sent.
  717. %% Don't send any body for HEAD responses.
  718. stream_body(_, _, #{method := <<"HEAD">>, has_sent_resp := headers}) ->
  719. ok;
  720. %% Don't send a message if the data is empty, except for the
  721. %% very last message with IsFin=fin.
  722. stream_body(Data, IsFin=nofin, #{pid := Pid, streamid := StreamID, has_sent_resp := headers}) ->
  723. case iolist_size(Data) of
  724. 0 -> ok;
  725. _ ->
  726. Pid ! {{Pid, StreamID}, {data, IsFin, Data}},
  727. ok
  728. end;
  729. stream_body(Data, IsFin, #{pid := Pid, streamid := StreamID, has_sent_resp := headers}) ->
  730. Pid ! {{Pid, StreamID}, {data, IsFin, Data}},
  731. ok.
  732. -spec stream_events(cow_sse:event() | [cow_sse:event()], fin | nofin, req()) -> ok.
  733. stream_events(Event, IsFin, Req) when is_map(Event) ->
  734. stream_events([Event], IsFin, Req);
  735. stream_events(Events, IsFin, #{pid := Pid, streamid := StreamID, has_sent_resp := headers}) ->
  736. Pid ! {{Pid, StreamID}, {data, IsFin, cow_sse:events(Events)}},
  737. ok.
  738. -spec stream_trailers(cowboy:http_headers(), req()) -> ok.
  739. stream_trailers(Trailers, #{pid := Pid, streamid := StreamID, has_sent_resp := headers}) ->
  740. Pid ! {{Pid, StreamID}, {trailers, Trailers}},
  741. ok.
  742. -spec push(iodata(), cowboy:http_headers(), req()) -> ok.
  743. push(Path, Headers, Req) ->
  744. push(Path, Headers, Req, #{}).
  745. %% @todo Optimization: don't send anything at all for HTTP/1.0 and HTTP/1.1.
  746. %% @todo Path, Headers, Opts, everything should be in proper binary,
  747. %% or normalized when creating the Req object.
  748. -spec push(iodata(), cowboy:http_headers(), req(), push_opts()) -> ok.
  749. push(Path, Headers, #{pid := Pid, streamid := StreamID,
  750. scheme := Scheme0, host := Host0, port := Port0}, Opts) ->
  751. Method = maps:get(method, Opts, <<"GET">>),
  752. Scheme = maps:get(scheme, Opts, Scheme0),
  753. Host = maps:get(host, Opts, Host0),
  754. Port = maps:get(port, Opts, Port0),
  755. Qs = maps:get(qs, Opts, <<>>),
  756. Pid ! {{Pid, StreamID}, {push, Method, Scheme, Host, Port, Path, Qs, Headers}},
  757. ok.
  758. %% Internal.
  759. %% @todo What about set-cookie headers set through set_resp_header or reply?
  760. -spec response_headers(Headers, req()) -> Headers when Headers::cowboy:http_headers().
  761. response_headers(Headers0, Req) ->
  762. RespHeaders = maps:get(resp_headers, Req, #{}),
  763. Headers = maps:merge(#{
  764. <<"date">> => cowboy_clock:rfc1123(),
  765. <<"server">> => <<"Cowboy">>
  766. }, maps:merge(RespHeaders, Headers0)),
  767. %% The set-cookie header is special; we can only send one cookie per header.
  768. %% We send the list of values for many cookies in one key of the map,
  769. %% and let the protocols deal with it directly.
  770. case maps:get(resp_cookies, Req, undefined) of
  771. undefined -> Headers;
  772. RespCookies -> Headers#{<<"set-cookie">> => maps:values(RespCookies)}
  773. end.
  774. %% Create map, convert keys to atoms and group duplicate keys into lists.
  775. %% Keys that are not found in the user provided list are entirely skipped.
  776. %% @todo Can probably be done directly while parsing.
  777. kvlist_to_map(Fields, KvList) ->
  778. Keys = [case K of
  779. {Key, _} -> Key;
  780. {Key, _, _} -> Key;
  781. Key -> Key
  782. end || K <- Fields],
  783. kvlist_to_map(Keys, KvList, #{}).
  784. kvlist_to_map(_, [], Map) ->
  785. Map;
  786. kvlist_to_map(Keys, [{Key, Value}|Tail], Map) ->
  787. try binary_to_existing_atom(Key, utf8) of
  788. Atom ->
  789. case lists:member(Atom, Keys) of
  790. true ->
  791. case maps:find(Atom, Map) of
  792. {ok, MapValue} when is_list(MapValue) ->
  793. kvlist_to_map(Keys, Tail,
  794. Map#{Atom => [Value|MapValue]});
  795. {ok, MapValue} ->
  796. kvlist_to_map(Keys, Tail,
  797. Map#{Atom => [Value, MapValue]});
  798. error ->
  799. kvlist_to_map(Keys, Tail,
  800. Map#{Atom => Value})
  801. end;
  802. false ->
  803. kvlist_to_map(Keys, Tail, Map)
  804. end
  805. catch error:badarg ->
  806. kvlist_to_map(Keys, Tail, Map)
  807. end.
  808. filter(Fields, Map0) ->
  809. filter(Fields, Map0, #{}).
  810. %% Loop through fields, if value is missing and no default,
  811. %% record the error; else if value is missing and has a
  812. %% default, set default; otherwise apply constraints. If
  813. %% constraint fails, record the error.
  814. %%
  815. %% When there is an error at the end, crash.
  816. filter([], Map, Errors) ->
  817. case maps:size(Errors) of
  818. 0 -> {ok, Map};
  819. _ -> {error, Errors}
  820. end;
  821. filter([{Key, Constraints}|Tail], Map, Errors) ->
  822. filter_constraints(Tail, Map, Errors, Key, maps:get(Key, Map), Constraints);
  823. filter([{Key, Constraints, Default}|Tail], Map, Errors) ->
  824. case maps:find(Key, Map) of
  825. {ok, Value} ->
  826. filter_constraints(Tail, Map, Errors, Key, Value, Constraints);
  827. error ->
  828. filter(Tail, Map#{Key => Default}, Errors)
  829. end;
  830. filter([Key|Tail], Map, Errors) ->
  831. case maps:is_key(Key, Map) of
  832. true ->
  833. filter(Tail, Map, Errors);
  834. false ->
  835. filter(Tail, Map, Errors#{Key => required})
  836. end.
  837. filter_constraints(Tail, Map, Errors, Key, Value0, Constraints) ->
  838. case cowboy_constraints:validate(Value0, Constraints) of
  839. {ok, Value} ->
  840. filter(Tail, Map#{Key => Value}, Errors);
  841. {error, Reason} ->
  842. filter(Tail, Map, Errors#{Key => Reason})
  843. end.