cowboy_req.erl 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754
  1. %% Copyright (c) 2011-2014, 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. %% Request.
  17. -export([method/1]).
  18. -export([version/1]).
  19. -export([peer/1]).
  20. -export([scheme/1]).
  21. -export([host/1]).
  22. -export([host_info/1]).
  23. -export([port/1]).
  24. -export([path/1]).
  25. -export([path_info/1]).
  26. -export([qs/1]).
  27. -export([parse_qs/1]).
  28. -export([match_qs/2]).
  29. -export([uri/1]).
  30. -export([uri/2]).
  31. -export([binding/2]).
  32. -export([binding/3]).
  33. -export([bindings/1]).
  34. -export([header/2]).
  35. -export([header/3]).
  36. -export([headers/1]).
  37. -export([parse_header/2]).
  38. -export([parse_header/3]).
  39. -export([parse_cookies/1]).
  40. -export([match_cookies/2]).
  41. %% Request body.
  42. -export([has_body/1]).
  43. -export([body_length/1]).
  44. -export([read_body/1]).
  45. -export([read_body/2]).
  46. -export([read_urlencoded_body/1]).
  47. -export([read_urlencoded_body/2]).
  48. %% Multipart.
  49. -export([read_part/1]).
  50. -export([read_part/2]).
  51. -export([read_part_body/1]).
  52. -export([read_part_body/2]).
  53. %% Response.
  54. -export([set_resp_cookie/3]).
  55. -export([set_resp_cookie/4]).
  56. -export([set_resp_header/3]).
  57. %% @todo set_resp_headers/2
  58. -export([has_resp_header/2]).
  59. %% @todo resp_header
  60. -export([delete_resp_header/2]).
  61. -export([set_resp_body/2]). %% @todo Use set_resp_body for iodata() | {sendfile ...}
  62. %% @todo set_resp_body/3 with a ContentType or even Headers argument, to set content headers.
  63. -export([has_resp_body/1]).
  64. -export([reply/2]).
  65. -export([reply/3]).
  66. -export([reply/4]).
  67. -export([stream_reply/2]).
  68. -export([stream_reply/3]).
  69. %% @todo stream_reply/2 (nofin)
  70. -export([stream_body/3]).
  71. %% @todo stream_event/2,3
  72. -export([push/3]).
  73. -export([push/4]).
  74. %% Internal.
  75. -export([response_headers/2]).
  76. -type cookie_opts() :: cow_cookie:cookie_opts().
  77. -export_type([cookie_opts/0]).
  78. -type body_opts() :: #{
  79. length => non_neg_integer(),
  80. period => non_neg_integer(),
  81. timeout => timeout()
  82. }.
  83. -export_type([body_opts/0]).
  84. %% While sendfile allows a Len of 0 that means "everything past Offset",
  85. %% Cowboy expects the real length as it is used as metadata.
  86. %% @todo We should probably explicitly reject it.
  87. -type resp_body() :: iodata()
  88. | {sendfile, non_neg_integer(), pos_integer(), file:name_all()}.
  89. -export_type([resp_body/0]).
  90. -type push_opts() :: map(). %% @todo
  91. -export_type([push_opts/0]).
  92. -type req() :: map(). %% @todo #{
  93. % ref := ranch:ref(),
  94. % pid := pid(),
  95. % streamid := cowboy_stream:streamid(),
  96. % peer := {inet:ip_address(), inet:port_number()},
  97. %
  98. % method := binary(), %% case sensitive
  99. % version := cowboy:http_version(),
  100. % scheme := binary(), %% <<"http">> or <<"https">>
  101. % host := binary(), %% lowercase; case insensitive
  102. % port := inet:port_number(),
  103. % path := binary(), %% case sensitive
  104. % qs := binary(), %% case sensitive
  105. % headers := cowboy:http_headers(),
  106. %
  107. % host_info => cowboy_router:tokens(),
  108. % path_info => cowboy_router:tokens(),
  109. % bindings => cowboy_router:bindings(),
  110. %
  111. % has_body := boolean(),
  112. % has_read_body => true,
  113. % body_length := undefined | non_neg_integer()
  114. %
  115. %% @todo resp_*
  116. %}.
  117. -export_type([req/0]).
  118. %% Request.
  119. -spec method(req()) -> binary().
  120. method(#{method := Method}) ->
  121. Method.
  122. -spec version(req()) -> cowboy:http_version().
  123. version(#{version := Version}) ->
  124. Version.
  125. -spec peer(req()) -> {inet:ip_address(), inet:port_number()}.
  126. peer(#{peer := Peer}) ->
  127. Peer.
  128. -spec scheme(req()) -> binary().
  129. scheme(#{scheme := Scheme}) ->
  130. Scheme.
  131. -spec host(req()) -> binary().
  132. host(#{host := Host}) ->
  133. Host.
  134. %% @todo The host_info is undefined if cowboy_router isn't used. Do we want to crash?
  135. -spec host_info(req()) -> cowboy_router:tokens() | undefined.
  136. host_info(#{host_info := HostInfo}) ->
  137. HostInfo.
  138. -spec port(req()) -> inet:port_number().
  139. port(#{port := Port}) ->
  140. Port.
  141. -spec path(req()) -> binary().
  142. path(#{path := Path}) ->
  143. Path.
  144. %% @todo The path_info is undefined if cowboy_router isn't used. Do we want to crash?
  145. -spec path_info(req()) -> cowboy_router:tokens() | undefined.
  146. path_info(#{path_info := PathInfo}) ->
  147. PathInfo.
  148. -spec qs(req()) -> binary().
  149. qs(#{qs := Qs}) ->
  150. Qs.
  151. %% @todo Might be useful to limit the number of keys.
  152. -spec parse_qs(req()) -> [{binary(), binary() | true}].
  153. parse_qs(#{qs := Qs}) ->
  154. cow_qs:parse_qs(Qs).
  155. -spec match_qs(cowboy:fields(), req()) -> map().
  156. match_qs(Fields, Req) ->
  157. filter(Fields, kvlist_to_map(Fields, parse_qs(Req))).
  158. -spec uri(req()) -> iodata().
  159. uri(Req) ->
  160. uri(Req, #{}).
  161. -spec uri(req(), map()) -> iodata().
  162. uri(#{scheme := Scheme0, host := Host0, port := Port0,
  163. path := Path0, qs := Qs0}, Opts) ->
  164. Scheme = case maps:get(scheme, Opts, Scheme0) of
  165. S = undefined -> S;
  166. S -> iolist_to_binary(S)
  167. end,
  168. Host = maps:get(host, Opts, Host0),
  169. Port = maps:get(port, Opts, Port0),
  170. Path = maps:get(path, Opts, Path0),
  171. Qs = maps:get(qs, Opts, Qs0),
  172. Fragment = maps:get(fragment, Opts, undefined),
  173. [uri_host(Scheme, Scheme0, Port, Host), uri_path(Path), uri_qs(Qs), uri_fragment(Fragment)].
  174. uri_host(_, _, _, undefined) -> <<>>;
  175. uri_host(Scheme, Scheme0, Port, Host) ->
  176. case iolist_size(Host) of
  177. 0 -> <<>>;
  178. _ -> [uri_scheme(Scheme), <<"//">>, Host, uri_port(Scheme, Scheme0, Port)]
  179. end.
  180. uri_scheme(undefined) -> <<>>;
  181. uri_scheme(Scheme) ->
  182. case iolist_size(Scheme) of
  183. 0 -> Scheme;
  184. _ -> [Scheme, $:]
  185. end.
  186. uri_port(_, _, undefined) -> <<>>;
  187. uri_port(undefined, <<"http">>, 80) -> <<>>;
  188. uri_port(undefined, <<"https">>, 443) -> <<>>;
  189. uri_port(<<"http">>, _, 80) -> <<>>;
  190. uri_port(<<"https">>, _, 443) -> <<>>;
  191. uri_port(_, _, Port) ->
  192. [$:, integer_to_binary(Port)].
  193. uri_path(undefined) -> <<>>;
  194. uri_path(Path) -> Path.
  195. uri_qs(undefined) -> <<>>;
  196. uri_qs(Qs) ->
  197. case iolist_size(Qs) of
  198. 0 -> Qs;
  199. _ -> [$?, Qs]
  200. end.
  201. uri_fragment(undefined) -> <<>>;
  202. uri_fragment(Fragment) ->
  203. case iolist_size(Fragment) of
  204. 0 -> Fragment;
  205. _ -> [$#, Fragment]
  206. end.
  207. -ifdef(TEST).
  208. uri1_test() ->
  209. <<"http://localhost/path">> = iolist_to_binary(uri(#{
  210. scheme => <<"http">>, host => <<"localhost">>, port => 80,
  211. path => <<"/path">>, qs => <<>>})),
  212. <<"http://localhost:443/path">> = iolist_to_binary(uri(#{
  213. scheme => <<"http">>, host => <<"localhost">>, port => 443,
  214. path => <<"/path">>, qs => <<>>})),
  215. <<"http://localhost:8080/path">> = iolist_to_binary(uri(#{
  216. scheme => <<"http">>, host => <<"localhost">>, port => 8080,
  217. path => <<"/path">>, qs => <<>>})),
  218. <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(#{
  219. scheme => <<"http">>, host => <<"localhost">>, port => 8080,
  220. path => <<"/path">>, qs => <<"dummy=2785">>})),
  221. <<"https://localhost/path">> = iolist_to_binary(uri(#{
  222. scheme => <<"https">>, host => <<"localhost">>, port => 443,
  223. path => <<"/path">>, qs => <<>>})),
  224. <<"https://localhost:8443/path">> = iolist_to_binary(uri(#{
  225. scheme => <<"https">>, host => <<"localhost">>, port => 8443,
  226. path => <<"/path">>, qs => <<>>})),
  227. <<"https://localhost:8443/path?dummy=2785">> = iolist_to_binary(uri(#{
  228. scheme => <<"https">>, host => <<"localhost">>, port => 8443,
  229. path => <<"/path">>, qs => <<"dummy=2785">>})),
  230. ok.
  231. uri2_test() ->
  232. Req = #{
  233. scheme => <<"http">>, host => <<"localhost">>, port => 8080,
  234. path => <<"/path">>, qs => <<"dummy=2785">>
  235. },
  236. <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{})),
  237. %% Disable individual components.
  238. <<"//localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => undefined})),
  239. <<"/path?dummy=2785">> = iolist_to_binary(uri(Req, #{host => undefined})),
  240. <<"http://localhost/path?dummy=2785">> = iolist_to_binary(uri(Req, #{port => undefined})),
  241. <<"http://localhost:8080?dummy=2785">> = iolist_to_binary(uri(Req, #{path => undefined})),
  242. <<"http://localhost:8080/path">> = iolist_to_binary(uri(Req, #{qs => undefined})),
  243. <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{fragment => undefined})),
  244. <<"http://localhost:8080">> = iolist_to_binary(uri(Req, #{path => undefined, qs => undefined})),
  245. <<>> = iolist_to_binary(uri(Req, #{host => undefined, path => undefined, qs => undefined})),
  246. %% Empty values.
  247. <<"//localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => <<>>})),
  248. <<"//localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => ""})),
  249. <<"//localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => [<<>>]})),
  250. <<"/path?dummy=2785">> = iolist_to_binary(uri(Req, #{host => <<>>})),
  251. <<"/path?dummy=2785">> = iolist_to_binary(uri(Req, #{host => ""})),
  252. <<"/path?dummy=2785">> = iolist_to_binary(uri(Req, #{host => [<<>>]})),
  253. <<"http://localhost:8080?dummy=2785">> = iolist_to_binary(uri(Req, #{path => <<>>})),
  254. <<"http://localhost:8080?dummy=2785">> = iolist_to_binary(uri(Req, #{path => ""})),
  255. <<"http://localhost:8080?dummy=2785">> = iolist_to_binary(uri(Req, #{path => [<<>>]})),
  256. <<"http://localhost:8080/path">> = iolist_to_binary(uri(Req, #{qs => <<>>})),
  257. <<"http://localhost:8080/path">> = iolist_to_binary(uri(Req, #{qs => ""})),
  258. <<"http://localhost:8080/path">> = iolist_to_binary(uri(Req, #{qs => [<<>>]})),
  259. <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{fragment => <<>>})),
  260. <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{fragment => ""})),
  261. <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{fragment => [<<>>]})),
  262. %% Port is integer() | undefined.
  263. {'EXIT', _} = (catch iolist_to_binary(uri(Req, #{port => <<>>}))),
  264. {'EXIT', _} = (catch iolist_to_binary(uri(Req, #{port => ""}))),
  265. {'EXIT', _} = (catch iolist_to_binary(uri(Req, #{port => [<<>>]}))),
  266. %% Update components.
  267. <<"https://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => "https"})),
  268. <<"http://example.org:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{host => "example.org"})),
  269. <<"http://localhost:123/path?dummy=2785">> = iolist_to_binary(uri(Req, #{port => 123})),
  270. <<"http://localhost:8080/custom?dummy=2785">> = iolist_to_binary(uri(Req, #{path => "/custom"})),
  271. <<"http://localhost:8080/path?smart=42">> = iolist_to_binary(uri(Req, #{qs => "smart=42"})),
  272. <<"http://localhost:8080/path?dummy=2785#intro">> = iolist_to_binary(uri(Req, #{fragment => "intro"})),
  273. %% Interesting combinations.
  274. <<"http://localhost/path?dummy=2785">> = iolist_to_binary(uri(Req, #{port => 80})),
  275. <<"https://localhost/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => "https", port => 443})),
  276. ok.
  277. -endif.
  278. -spec binding(atom(), req()) -> any() | undefined.
  279. binding(Name, Req) ->
  280. binding(Name, Req, undefined).
  281. -spec binding(atom(), req(), Default) -> any() | Default when Default::any().
  282. binding(Name, #{bindings := Bindings}, Default) when is_atom(Name) ->
  283. case lists:keyfind(Name, 1, Bindings) of
  284. {_, Value} -> Value;
  285. false -> Default
  286. end;
  287. binding(Name, _, Default) when is_atom(Name) ->
  288. Default.
  289. -spec bindings(req()) -> [{atom(), any()}].
  290. bindings(#{bindings := Bindings}) ->
  291. Bindings;
  292. bindings(_) ->
  293. [].
  294. -spec header(binary(), req()) -> binary() | undefined.
  295. header(Name, Req) ->
  296. header(Name, Req, undefined).
  297. -spec header(binary(), req(), Default) -> binary() | Default when Default::any().
  298. header(Name, #{headers := Headers}, Default) ->
  299. maps:get(Name, Headers, Default).
  300. -spec headers(req()) -> cowboy:http_headers().
  301. headers(#{headers := Headers}) ->
  302. Headers.
  303. -spec parse_header(binary(), Req) -> any() when Req::req().
  304. parse_header(Name = <<"content-length">>, Req) ->
  305. parse_header(Name, Req, 0, fun cow_http_hd:parse_content_length/1);
  306. parse_header(Name = <<"cookie">>, Req) ->
  307. parse_header(Name, Req, [], fun cow_cookie:parse_cookie/1);
  308. %% @todo That header is abstracted out and should never reach cowboy_req.
  309. parse_header(Name = <<"transfer-encoding">>, Req) ->
  310. parse_header(Name, Req, [<<"identity">>], fun cow_http_hd:parse_transfer_encoding/1);
  311. parse_header(Name, Req) ->
  312. parse_header(Name, Req, undefined).
  313. -spec parse_header(binary(), Req, any()) -> any() when Req::req().
  314. parse_header(Name, Req, Default) ->
  315. parse_header(Name, Req, Default, parse_header_fun(Name)).
  316. parse_header_fun(<<"accept">>) -> fun cow_http_hd:parse_accept/1;
  317. parse_header_fun(<<"accept-charset">>) -> fun cow_http_hd:parse_accept_charset/1;
  318. parse_header_fun(<<"accept-encoding">>) -> fun cow_http_hd:parse_accept_encoding/1;
  319. parse_header_fun(<<"accept-language">>) -> fun cow_http_hd:parse_accept_language/1;
  320. parse_header_fun(<<"authorization">>) -> fun cow_http_hd:parse_authorization/1;
  321. parse_header_fun(<<"connection">>) -> fun cow_http_hd:parse_connection/1;
  322. parse_header_fun(<<"content-length">>) -> fun cow_http_hd:parse_content_length/1;
  323. parse_header_fun(<<"content-type">>) -> fun cow_http_hd:parse_content_type/1;
  324. parse_header_fun(<<"cookie">>) -> fun cow_cookie:parse_cookie/1;
  325. parse_header_fun(<<"expect">>) -> fun cow_http_hd:parse_expect/1;
  326. parse_header_fun(<<"if-match">>) -> fun cow_http_hd:parse_if_match/1;
  327. parse_header_fun(<<"if-modified-since">>) -> fun cow_http_hd:parse_if_modified_since/1;
  328. parse_header_fun(<<"if-none-match">>) -> fun cow_http_hd:parse_if_none_match/1;
  329. parse_header_fun(<<"if-unmodified-since">>) -> fun cow_http_hd:parse_if_unmodified_since/1;
  330. parse_header_fun(<<"range">>) -> fun cow_http_hd:parse_range/1;
  331. parse_header_fun(<<"sec-websocket-extensions">>) -> fun cow_http_hd:parse_sec_websocket_extensions/1;
  332. parse_header_fun(<<"sec-websocket-protocol">>) -> fun cow_http_hd:parse_sec_websocket_protocol_req/1;
  333. parse_header_fun(<<"transfer-encoding">>) -> fun cow_http_hd:parse_transfer_encoding/1;
  334. parse_header_fun(<<"upgrade">>) -> fun cow_http_hd:parse_upgrade/1;
  335. parse_header_fun(<<"x-forwarded-for">>) -> fun cow_http_hd:parse_x_forwarded_for/1.
  336. parse_header(Name, Req, Default, ParseFun) ->
  337. case header(Name, Req) of
  338. undefined -> Default;
  339. Value -> ParseFun(Value)
  340. end.
  341. -spec parse_cookies(req()) -> [{binary(), binary()}].
  342. parse_cookies(Req) ->
  343. parse_header(<<"cookie">>, Req).
  344. -spec match_cookies(cowboy:fields(), req()) -> map().
  345. match_cookies(Fields, Req) ->
  346. filter(Fields, kvlist_to_map(Fields, parse_cookies(Req))).
  347. %% Request body.
  348. -spec has_body(req()) -> boolean().
  349. has_body(#{has_body := HasBody}) ->
  350. HasBody.
  351. %% The length may not be known if HTTP/1.1 with a transfer-encoding;
  352. %% or HTTP/2 with no content-length header. The length is always
  353. %% known once the body has been completely read.
  354. -spec body_length(req()) -> undefined | non_neg_integer().
  355. body_length(#{body_length := Length}) ->
  356. Length.
  357. -spec read_body(Req) -> {ok, binary(), Req} | {more, binary(), Req} when Req::req().
  358. read_body(Req) ->
  359. read_body(Req, #{}).
  360. -spec read_body(Req, body_opts()) -> {ok, binary(), Req} | {more, binary(), Req} when Req::req().
  361. read_body(Req=#{has_body := false}, _) ->
  362. {ok, <<>>, Req};
  363. read_body(Req=#{has_read_body := true}, _) ->
  364. {ok, <<>>, Req};
  365. read_body(Req=#{pid := Pid, streamid := StreamID}, Opts) ->
  366. Length = maps:get(length, Opts, 8000000),
  367. Period = maps:get(period, Opts, 15000),
  368. Timeout = maps:get(timeout, Opts, Period + 1000),
  369. Ref = make_ref(),
  370. Pid ! {{Pid, StreamID}, {read_body, Ref, Length, Period}},
  371. receive
  372. {request_body, Ref, nofin, Body} ->
  373. {more, Body, Req};
  374. {request_body, Ref, {fin, BodyLength}, Body} ->
  375. {ok, Body, set_body_length(Req, BodyLength)}
  376. after Timeout ->
  377. exit(timeout)
  378. end.
  379. set_body_length(Req=#{headers := Headers}, BodyLength) ->
  380. Req#{
  381. headers => Headers#{<<"content-length">> => integer_to_binary(BodyLength)},
  382. body_length => BodyLength,
  383. has_read_body => true
  384. }.
  385. -spec read_urlencoded_body(Req) -> {ok, [{binary(), binary() | true}], Req} when Req::req().
  386. read_urlencoded_body(Req) ->
  387. read_urlencoded_body(Req, #{length => 64000, period => 5000}).
  388. -spec read_urlencoded_body(Req, body_opts()) -> {ok, [{binary(), binary() | true}], Req} when Req::req().
  389. read_urlencoded_body(Req0, Opts) ->
  390. {ok, Body, Req} = read_body(Req0, Opts),
  391. {ok, cow_qs:parse_qs(Body), Req}.
  392. %% Multipart.
  393. -spec read_part(Req)
  394. -> {ok, cow_multipart:headers(), Req} | {done, Req}
  395. when Req::req().
  396. read_part(Req) ->
  397. read_part(Req, #{length => 64000, period => 5000}).
  398. -spec read_part(Req, body_opts())
  399. -> {ok, cow_multipart:headers(), Req} | {done, Req}
  400. when Req::req().
  401. read_part(Req, Opts) ->
  402. case maps:is_key(multipart, Req) of
  403. true ->
  404. {Data, Req2} = stream_multipart(Req, Opts),
  405. read_part(Data, Opts, Req2);
  406. false ->
  407. read_part(init_multipart(Req), Opts)
  408. end.
  409. read_part(Buffer, Opts, Req=#{multipart := {Boundary, _}}) ->
  410. case cow_multipart:parse_headers(Buffer, Boundary) of
  411. more ->
  412. {Data, Req2} = stream_multipart(Req, Opts),
  413. read_part(<< Buffer/binary, Data/binary >>, Opts, Req2);
  414. {more, Buffer2} ->
  415. {Data, Req2} = stream_multipart(Req, Opts),
  416. read_part(<< Buffer2/binary, Data/binary >>, Opts, Req2);
  417. {ok, Headers, Rest} ->
  418. %% @todo We may want headers as a map. Need to check the
  419. %% rules for multipart header parsing before taking a decision.
  420. {ok, Headers, Req#{multipart => {Boundary, Rest}}};
  421. %% Ignore epilogue.
  422. {done, _} ->
  423. {done, Req#{multipart => done}}
  424. end.
  425. -spec read_part_body(Req)
  426. -> {ok, binary(), Req} | {more, binary(), Req}
  427. when Req::req().
  428. read_part_body(Req) ->
  429. read_part_body(Req, #{}).
  430. -spec read_part_body(Req, body_opts())
  431. -> {ok, binary(), Req} | {more, binary(), Req}
  432. when Req::req().
  433. read_part_body(Req, Opts) ->
  434. case maps:is_key(multipart, Req) of
  435. true ->
  436. read_part_body(<<>>, Opts, Req, <<>>);
  437. false ->
  438. read_part_body(init_multipart(Req), Opts)
  439. end.
  440. read_part_body(Buffer, Opts, Req=#{multipart := {Boundary, _}}, Acc) ->
  441. Length = maps:get(length, Opts, 8000000),
  442. case byte_size(Acc) > Length of
  443. true ->
  444. {more, Acc, Req#{multipart => {Boundary, Buffer}}};
  445. false ->
  446. {Data, Req2} = stream_multipart(Req, Opts),
  447. case cow_multipart:parse_body(<< Buffer/binary, Data/binary >>, Boundary) of
  448. {ok, Body} ->
  449. read_part_body(<<>>, Opts, Req2, << Acc/binary, Body/binary >>);
  450. {ok, Body, Rest} ->
  451. read_part_body(Rest, Opts, Req2, << Acc/binary, Body/binary >>);
  452. done ->
  453. {ok, Acc, Req2};
  454. {done, Body} ->
  455. {ok, << Acc/binary, Body/binary >>, Req2};
  456. {done, Body, Rest} ->
  457. {ok, << Acc/binary, Body/binary >>,
  458. Req2#{multipart => {Boundary, Rest}}}
  459. end
  460. end.
  461. init_multipart(Req) ->
  462. {<<"multipart">>, _, Params} = parse_header(<<"content-type">>, Req),
  463. {_, Boundary} = lists:keyfind(<<"boundary">>, 1, Params),
  464. Req#{multipart => {Boundary, <<>>}}.
  465. stream_multipart(Req=#{multipart := done}, _) ->
  466. {<<>>, Req};
  467. stream_multipart(Req=#{multipart := {_, <<>>}}, Opts) ->
  468. {_, Data, Req2} = read_body(Req, Opts),
  469. {Data, Req2};
  470. stream_multipart(Req=#{multipart := {Boundary, Buffer}}, _) ->
  471. {Buffer, Req#{multipart => {Boundary, <<>>}}}.
  472. %% Response.
  473. -spec set_resp_cookie(iodata(), iodata(), Req)
  474. -> Req when Req::req().
  475. set_resp_cookie(Name, Value, Req) ->
  476. set_resp_cookie(Name, Value, #{}, Req).
  477. %% The cookie name cannot contain any of the following characters:
  478. %% =,;\s\t\r\n\013\014
  479. %%
  480. %% The cookie value cannot contain any of the following characters:
  481. %% ,; \t\r\n\013\014
  482. %% @todo Fix the cookie_opts() type.
  483. -spec set_resp_cookie(iodata(), iodata(), cookie_opts(), Req)
  484. -> Req when Req::req().
  485. set_resp_cookie(Name, Value, Opts, Req) ->
  486. Cookie = cow_cookie:setcookie(Name, Value, maps:to_list(Opts)),
  487. RespCookies = maps:get(resp_cookies, Req, #{}),
  488. Req#{resp_cookies => RespCookies#{Name => Cookie}}.
  489. %% @todo We could add has_resp_cookie and delete_resp_cookie now.
  490. -spec set_resp_header(binary(), iodata(), Req)
  491. -> Req when Req::req().
  492. set_resp_header(Name, Value, Req=#{resp_headers := RespHeaders}) ->
  493. Req#{resp_headers => RespHeaders#{Name => Value}};
  494. set_resp_header(Name,Value, Req) ->
  495. Req#{resp_headers => #{Name => Value}}.
  496. -spec set_resp_body(resp_body(), Req) -> Req when Req::req().
  497. set_resp_body(Body, Req) ->
  498. Req#{resp_body => Body}.
  499. -spec has_resp_header(binary(), req()) -> boolean().
  500. has_resp_header(Name, #{resp_headers := RespHeaders}) ->
  501. maps:is_key(Name, RespHeaders);
  502. has_resp_header(_, _) ->
  503. false.
  504. -spec has_resp_body(req()) -> boolean().
  505. has_resp_body(#{resp_body := {sendfile, _, _, _}}) ->
  506. true;
  507. has_resp_body(#{resp_body := RespBody}) ->
  508. iolist_size(RespBody) > 0;
  509. has_resp_body(_) ->
  510. false.
  511. -spec delete_resp_header(binary(), Req)
  512. -> Req when Req::req().
  513. delete_resp_header(Name, Req=#{resp_headers := RespHeaders}) ->
  514. Req#{resp_headers => maps:remove(Name, RespHeaders)}.
  515. -spec reply(cowboy:http_status(), Req) -> Req when Req::req().
  516. reply(Status, Req) ->
  517. reply(Status, #{}, Req).
  518. -spec reply(cowboy:http_status(), cowboy:http_headers(), Req)
  519. -> Req when Req::req().
  520. reply(Status, Headers, Req=#{resp_body := Body}) ->
  521. reply(Status, Headers, Body, Req);
  522. reply(Status, Headers, Req) ->
  523. reply(Status, Headers, <<>>, Req).
  524. -spec reply(cowboy:http_status(), cowboy:http_headers(), resp_body(), Req)
  525. -> Req when Req::req().
  526. reply(_, _, _, #{has_sent_resp := _}) ->
  527. error(function_clause);
  528. reply(Status, Headers, SendFile = {sendfile, _, Len, _}, Req)
  529. when is_integer(Status); is_binary(Status) ->
  530. do_reply(Status, Headers#{
  531. <<"content-length">> => integer_to_binary(Len)
  532. }, SendFile, Req);
  533. reply(Status, Headers, Body, Req)
  534. when is_integer(Status); is_binary(Status) ->
  535. do_reply(Status, Headers#{
  536. <<"content-length">> => integer_to_binary(iolist_size(Body))
  537. }, Body, Req).
  538. %% Don't send any body for HEAD responses. While the protocol code is
  539. %% supposed to enforce this rule, we prefer to avoid copying too much
  540. %% data around if we can avoid it.
  541. do_reply(Status, Headers, _, Req=#{pid := Pid, streamid := StreamID, method := <<"HEAD">>}) ->
  542. Pid ! {{Pid, StreamID}, {response, Status, response_headers(Headers, Req), <<>>}},
  543. done_replying(Req, true);
  544. do_reply(Status, Headers, Body, Req=#{pid := Pid, streamid := StreamID}) ->
  545. Pid ! {{Pid, StreamID}, {response, Status, response_headers(Headers, Req), Body}},
  546. done_replying(Req, true).
  547. done_replying(Req, HasSentResp) ->
  548. maps:without([resp_cookies, resp_headers, resp_body], Req#{has_sent_resp => HasSentResp}).
  549. -spec stream_reply(cowboy:http_status(), Req) -> Req when Req::req().
  550. stream_reply(Status, Req) ->
  551. stream_reply(Status, #{}, Req).
  552. -spec stream_reply(cowboy:http_status(), cowboy:http_headers(), Req)
  553. -> Req when Req::req().
  554. stream_reply(_, _, #{has_sent_resp := _}) ->
  555. error(function_clause);
  556. stream_reply(Status, Headers=#{}, Req=#{pid := Pid, streamid := StreamID})
  557. when is_integer(Status); is_binary(Status) ->
  558. Pid ! {{Pid, StreamID}, {headers, Status, response_headers(Headers, Req)}},
  559. done_replying(Req, headers).
  560. -spec stream_body(iodata(), fin | nofin, req()) -> ok.
  561. %% Error out if headers were not sent.
  562. %% Don't send any body for HEAD responses.
  563. stream_body(_, _, #{method := <<"HEAD">>, has_sent_resp := headers}) ->
  564. ok;
  565. %% Don't send a message if the data is empty, except for the
  566. %% very last message with IsFin=fin.
  567. stream_body(Data, IsFin=nofin, #{pid := Pid, streamid := StreamID, has_sent_resp := headers}) ->
  568. case iolist_size(Data) of
  569. 0 -> ok;
  570. _ ->
  571. Pid ! {{Pid, StreamID}, {data, IsFin, Data}},
  572. ok
  573. end;
  574. stream_body(Data, IsFin, #{pid := Pid, streamid := StreamID, has_sent_resp := headers}) ->
  575. Pid ! {{Pid, StreamID}, {data, IsFin, Data}},
  576. ok.
  577. -spec push(binary(), cowboy:http_headers(), req()) -> ok.
  578. push(Path, Headers, Req) ->
  579. push(Path, Headers, Req, #{}).
  580. %% @todo Optimization: don't send anything at all for HTTP/1.0 and HTTP/1.1.
  581. %% @todo Path, Headers, Opts, everything should be in proper binary,
  582. %% or normalized when creating the Req object.
  583. -spec push(binary(), cowboy:http_headers(), req(), push_opts()) -> ok.
  584. push(Path, Headers, #{pid := Pid, streamid := StreamID,
  585. scheme := Scheme0, host := Host0, port := Port0}, Opts) ->
  586. Method = maps:get(method, Opts, <<"GET">>),
  587. Scheme = maps:get(scheme, Opts, Scheme0),
  588. Host = maps:get(host, Opts, Host0),
  589. Port = maps:get(port, Opts, Port0),
  590. Qs = maps:get(qs, Opts, <<>>),
  591. Pid ! {{Pid, StreamID}, {push, Method, Scheme, Host, Port, Path, Qs, Headers}},
  592. ok.
  593. %% Internal.
  594. %% @todo What about set-cookie headers set through set_resp_header or reply?
  595. -spec response_headers(Headers, req()) -> Headers when Headers::cowboy:http_headers().
  596. response_headers(Headers0, Req) ->
  597. RespHeaders = maps:get(resp_headers, Req, #{}),
  598. Headers = maps:merge(#{
  599. <<"date">> => cowboy_clock:rfc1123(),
  600. <<"server">> => <<"Cowboy">>
  601. }, maps:merge(RespHeaders, Headers0)),
  602. %% The set-cookie header is special; we can only send one cookie per header.
  603. %% We send the list of values for many cookies in one key of the map,
  604. %% and let the protocols deal with it directly.
  605. case maps:get(resp_cookies, Req, undefined) of
  606. undefined -> Headers;
  607. RespCookies -> Headers#{<<"set-cookie">> => maps:values(RespCookies)}
  608. end.
  609. %% Create map, convert keys to atoms and group duplicate keys into lists.
  610. %% Keys that are not found in the user provided list are entirely skipped.
  611. %% @todo Can probably be done directly while parsing.
  612. kvlist_to_map(Fields, KvList) ->
  613. Keys = [case K of
  614. {Key, _} -> Key;
  615. {Key, _, _} -> Key;
  616. Key -> Key
  617. end || K <- Fields],
  618. kvlist_to_map(Keys, KvList, #{}).
  619. kvlist_to_map(_, [], Map) ->
  620. Map;
  621. kvlist_to_map(Keys, [{Key, Value}|Tail], Map) ->
  622. try binary_to_existing_atom(Key, utf8) of
  623. Atom ->
  624. case lists:member(Atom, Keys) of
  625. true ->
  626. case maps:find(Atom, Map) of
  627. {ok, MapValue} when is_list(MapValue) ->
  628. kvlist_to_map(Keys, Tail,
  629. Map#{Atom => [Value|MapValue]});
  630. {ok, MapValue} ->
  631. kvlist_to_map(Keys, Tail,
  632. Map#{Atom => [Value, MapValue]});
  633. error ->
  634. kvlist_to_map(Keys, Tail,
  635. Map#{Atom => Value})
  636. end;
  637. false ->
  638. kvlist_to_map(Keys, Tail, Map)
  639. end
  640. catch error:badarg ->
  641. kvlist_to_map(Keys, Tail, Map)
  642. end.
  643. %% Loop through fields, if value is missing and no default, crash;
  644. %% else if value is missing and has a default, set default;
  645. %% otherwise apply constraints. If constraint fails, crash.
  646. filter([], Map) ->
  647. Map;
  648. filter([{Key, Constraints}|Tail], Map) ->
  649. filter_constraints(Tail, Map, Key, maps:get(Key, Map), Constraints);
  650. filter([{Key, Constraints, Default}|Tail], Map) ->
  651. case maps:find(Key, Map) of
  652. {ok, Value} ->
  653. filter_constraints(Tail, Map, Key, Value, Constraints);
  654. error ->
  655. filter(Tail, Map#{Key => Default})
  656. end;
  657. filter([Key|Tail], Map) ->
  658. true = maps:is_key(Key, Map),
  659. filter(Tail, Map).
  660. filter_constraints(Tail, Map, Key, Value, Constraints) ->
  661. case cowboy_constraints:validate(Value, Constraints) of
  662. true ->
  663. filter(Tail, Map);
  664. {true, Value2} ->
  665. filter(Tail, Map#{Key => Value2})
  666. end.