cowboy_http_req.erl 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. %% Copyright (c) 2011, Loïc Hoguin <essen@dev-extend.eu>
  2. %% Copyright (c) 2011, Anthony Ramine <nox@dev-extend.eu>
  3. %%
  4. %% Permission to use, copy, modify, and/or distribute this software for any
  5. %% purpose with or without fee is hereby granted, provided that the above
  6. %% copyright notice and this permission notice appear in all copies.
  7. %%
  8. %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  9. %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  10. %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  11. %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  12. %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  13. %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  14. %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. -module(cowboy_http_req).
  16. -export([
  17. method/1, version/1, peer/1,
  18. host/1, host_info/1, raw_host/1, port/1,
  19. path/1, path_info/1, raw_path/1,
  20. qs_val/2, qs_val/3, qs_vals/1, raw_qs/1,
  21. binding/2, binding/3, bindings/1,
  22. header/2, header/3, headers/1
  23. %% cookie/2, cookie/3, cookies/1 @todo
  24. ]). %% Request API.
  25. -export([
  26. body/1, body/2, body_qs/1
  27. ]). %% Request Body API.
  28. -export([
  29. reply/4, chunked_reply/3, chunk/2
  30. ]). %% Response API.
  31. -include("include/http.hrl").
  32. -include_lib("eunit/include/eunit.hrl").
  33. %% Request API.
  34. -spec method(Req::#http_req{}) -> {Method::http_method(), Req::#http_req{}}.
  35. method(Req) ->
  36. {Req#http_req.method, Req}.
  37. -spec version(Req::#http_req{}) -> {Version::http_version(), Req::#http_req{}}.
  38. version(Req) ->
  39. {Req#http_req.version, Req}.
  40. -spec peer(Req::#http_req{})
  41. -> {{Address::ip_address(), Port::ip_port()}, Req::#http_req{}}.
  42. peer(Req=#http_req{socket=Socket, transport=Transport, peer=undefined}) ->
  43. {ok, Peer} = Transport:peername(Socket),
  44. {Peer, Req#http_req{peer=Peer}};
  45. peer(Req) ->
  46. {Req#http_req.peer, Req}.
  47. -spec host(Req::#http_req{})
  48. -> {Host::cowboy_dispatcher:path_tokens(), Req::#http_req{}}.
  49. host(Req) ->
  50. {Req#http_req.host, Req}.
  51. -spec host_info(Req::#http_req{})
  52. -> {HostInfo::cowboy_dispatcher:path_tokens() | undefined,
  53. Req::#http_req{}}.
  54. host_info(Req) ->
  55. {Req#http_req.host_info, Req}.
  56. -spec raw_host(Req::#http_req{}) -> {RawHost::binary(), Req::#http_req{}}.
  57. raw_host(Req) ->
  58. {Req#http_req.raw_host, Req}.
  59. -spec port(Req::#http_req{}) -> {Port::ip_port(), Req::#http_req{}}.
  60. port(Req) ->
  61. {Req#http_req.port, Req}.
  62. -spec path(Req::#http_req{})
  63. -> {Path::cowboy_dispatcher:path_tokens(), Req::#http_req{}}.
  64. path(Req) ->
  65. {Req#http_req.path, Req}.
  66. -spec path_info(Req::#http_req{})
  67. -> {PathInfo::cowboy_dispatcher:path_tokens() | undefined,
  68. Req::#http_req{}}.
  69. path_info(Req) ->
  70. {Req#http_req.path_info, Req}.
  71. -spec raw_path(Req::#http_req{}) -> {RawPath::binary(), Req::#http_req{}}.
  72. raw_path(Req) ->
  73. {Req#http_req.raw_path, Req}.
  74. -spec qs_val(Name::binary(), Req::#http_req{})
  75. -> {Value::binary() | true | undefined, Req::#http_req{}}.
  76. %% @equiv qs_val(Name, Req) -> qs_val(Name, Req, undefined)
  77. qs_val(Name, Req) ->
  78. qs_val(Name, Req, undefined).
  79. -spec qs_val(Name::binary(), Req::#http_req{}, Default)
  80. -> {Value::binary() | true | Default, Req::#http_req{}}
  81. when Default::term().
  82. qs_val(Name, Req=#http_req{raw_qs=RawQs, qs_vals=undefined}, Default) ->
  83. QsVals = parse_qs(RawQs),
  84. qs_val(Name, Req#http_req{qs_vals=QsVals}, Default);
  85. qs_val(Name, Req, Default) ->
  86. case lists:keyfind(Name, 1, Req#http_req.qs_vals) of
  87. {Name, Value} -> {Value, Req};
  88. false -> {Default, Req}
  89. end.
  90. -spec qs_vals(Req::#http_req{})
  91. -> {list({Name::binary(), Value::binary() | true}), Req::#http_req{}}.
  92. qs_vals(Req=#http_req{raw_qs=RawQs, qs_vals=undefined}) ->
  93. QsVals = parse_qs(RawQs),
  94. qs_vals(Req#http_req{qs_vals=QsVals});
  95. qs_vals(Req=#http_req{qs_vals=QsVals}) ->
  96. {QsVals, Req}.
  97. -spec raw_qs(Req::#http_req{}) -> {RawQs::binary(), Req::#http_req{}}.
  98. raw_qs(Req) ->
  99. {Req#http_req.raw_qs, Req}.
  100. -spec binding(Name::atom(), Req::#http_req{})
  101. -> {Value::binary() | undefined, Req::#http_req{}}.
  102. %% @equiv binding(Name, Req) -> binding(Name, Req, undefined)
  103. binding(Name, Req) ->
  104. binding(Name, Req, undefined).
  105. -spec binding(Name::atom(), Req::#http_req{}, Default)
  106. -> {Value::binary() | Default, Req::#http_req{}} when Default::term().
  107. binding(Name, Req, Default) ->
  108. case lists:keyfind(Name, 1, Req#http_req.bindings) of
  109. {Name, Value} -> {Value, Req};
  110. false -> {Default, Req}
  111. end.
  112. -spec bindings(Req::#http_req{})
  113. -> {list({Name::atom(), Value::binary()}), Req::#http_req{}}.
  114. bindings(Req) ->
  115. {Req#http_req.bindings, Req}.
  116. -spec header(Name::atom() | binary(), Req::#http_req{})
  117. -> {Value::binary() | undefined, Req::#http_req{}}.
  118. %% @equiv header(Name, Req) -> header(Name, Req, undefined)
  119. header(Name, Req) ->
  120. header(Name, Req, undefined).
  121. -spec header(Name::atom() | binary(), Req::#http_req{}, Default)
  122. -> {Value::binary() | Default, Req::#http_req{}} when Default::term().
  123. header(Name, Req, Default) ->
  124. case lists:keyfind(Name, 1, Req#http_req.headers) of
  125. {Name, Value} -> {Value, Req};
  126. false -> {Default, Req}
  127. end.
  128. -spec headers(Req::#http_req{})
  129. -> {Headers::http_headers(), Req::#http_req{}}.
  130. headers(Req) ->
  131. {Req#http_req.headers, Req}.
  132. %% Request Body API.
  133. %% @todo We probably want to allow a max length.
  134. -spec body(Req::#http_req{})
  135. -> {ok, Body::binary(), Req::#http_req{}} | {error, Reason::atom()}.
  136. body(Req) ->
  137. {Length, Req2} = cowboy_http_req:header('Content-Length', Req),
  138. case Length of
  139. undefined -> {error, badarg};
  140. _Any ->
  141. Length2 = list_to_integer(binary_to_list(Length)),
  142. body(Length2, Req2)
  143. end.
  144. %% @todo We probably want to configure the timeout.
  145. -spec body(Length::non_neg_integer(), Req::#http_req{})
  146. -> {ok, Body::binary(), Req::#http_req{}} | {error, Reason::atom()}.
  147. body(Length, Req=#http_req{body_state=waiting, buffer=Buffer})
  148. when Length =:= byte_size(Buffer) ->
  149. {ok, Buffer, Req#http_req{body_state=done, buffer= <<>>}};
  150. body(Length, Req=#http_req{socket=Socket, transport=Transport,
  151. body_state=waiting, buffer=Buffer}) when Length > byte_size(Buffer) ->
  152. case Transport:recv(Socket, Length - byte_size(Buffer), 5000) of
  153. {ok, Body} -> {ok, << Buffer/binary, Body/binary >>, Req#http_req{body_state=done, buffer= <<>>}};
  154. {error, Reason} -> {error, Reason}
  155. end.
  156. -spec body_qs(Req::#http_req{})
  157. -> {list({Name::binary(), Value::binary() | true}), Req::#http_req{}}.
  158. body_qs(Req) ->
  159. {ok, Body, Req2} = body(Req),
  160. {parse_qs(Body), Req2}.
  161. %% Response API.
  162. -spec reply(Code::http_status(), Headers::http_headers(),
  163. Body::iodata(), Req::#http_req{}) -> {ok, Req::#http_req{}}.
  164. reply(Code, Headers, Body, Req=#http_req{socket=Socket,
  165. transport=Transport, connection=Connection,
  166. resp_state=waiting}) ->
  167. Head = response_head(Code, Headers, [
  168. {<<"Connection">>, atom_to_connection(Connection)},
  169. {<<"Content-Length">>,
  170. list_to_binary(integer_to_list(iolist_size(Body)))},
  171. {<<"Date">>, cowboy_clock:rfc1123()},
  172. {<<"Server">>, <<"Cowboy">>}
  173. ]),
  174. Transport:send(Socket, [Head, Body]),
  175. {ok, Req#http_req{resp_state=done}}.
  176. -spec chunked_reply(Code::http_status(), Headers::http_headers(),
  177. Req::#http_req{}) -> {ok, Req::#http_req{}}.
  178. chunked_reply(Code, Headers, Req=#http_req{socket=Socket, transport=Transport,
  179. resp_state=waiting}) ->
  180. Head = response_head(Code, Headers, [
  181. {<<"Connection">>, <<"close">>},
  182. {<<"Transfer-Encoding">>, <<"chunked">>},
  183. {<<"Date">>, cowboy_clock:rfc1123()},
  184. {<<"Server">>, <<"Cowboy">>}
  185. ]),
  186. Transport:send(Socket, Head),
  187. {ok, Req#http_req{resp_state=chunks}}.
  188. -spec chunk(Data::iodata(), Req::#http_req{}) -> ok.
  189. chunk(Data, #http_req{socket=Socket, transport=Transport, resp_state=chunks}) ->
  190. Transport:send(Socket, [integer_to_list(iolist_size(Data), 16),
  191. <<"\r\n">>, Data, <<"\r\n">>]).
  192. %% Internal.
  193. -spec parse_qs(Qs::binary()) -> list({Name::binary(), Value::binary() | true}).
  194. parse_qs(<<>>) ->
  195. [];
  196. parse_qs(Qs) ->
  197. Tokens = binary:split(Qs, <<"&">>, [global, trim]),
  198. [case binary:split(Token, <<"=">>) of
  199. [Token] -> {Token, true};
  200. [Name, Value] -> {Name, Value}
  201. end || Token <- Tokens].
  202. -spec response_head(Code::http_status(), Headers::http_headers(),
  203. DefaultHeaders::http_headers()) -> iolist().
  204. response_head(Code, Headers, DefaultHeaders) ->
  205. StatusLine = <<"HTTP/1.1 ", (status(Code))/binary, "\r\n">>,
  206. Headers2 = [{header_to_binary(Key), Value} || {Key, Value} <- Headers],
  207. Headers3 = lists:keysort(1, Headers2),
  208. Headers4 = lists:ukeymerge(1, Headers3, DefaultHeaders),
  209. Headers5 = [<< Key/binary, ": ", Value/binary, "\r\n" >>
  210. || {Key, Value} <- Headers4],
  211. [StatusLine, Headers5, <<"\r\n">>].
  212. -spec atom_to_connection(Atom::keepalive | close) -> binary().
  213. atom_to_connection(keepalive) ->
  214. <<"keep-alive">>;
  215. atom_to_connection(close) ->
  216. <<"close">>.
  217. -spec status(Code::http_status()) -> binary().
  218. status(100) -> <<"100 Continue">>;
  219. status(101) -> <<"101 Switching Protocols">>;
  220. status(102) -> <<"102 Processing">>;
  221. status(200) -> <<"200 OK">>;
  222. status(201) -> <<"201 Created">>;
  223. status(202) -> <<"202 Accepted">>;
  224. status(203) -> <<"203 Non-Authoritative Information">>;
  225. status(204) -> <<"204 No Content">>;
  226. status(205) -> <<"205 Reset Content">>;
  227. status(206) -> <<"206 Partial Content">>;
  228. status(207) -> <<"207 Multi-Status">>;
  229. status(226) -> <<"226 IM Used">>;
  230. status(300) -> <<"300 Multiple Choices">>;
  231. status(301) -> <<"301 Moved Permanently">>;
  232. status(302) -> <<"302 Found">>;
  233. status(303) -> <<"303 See Other">>;
  234. status(304) -> <<"304 Not Modified">>;
  235. status(305) -> <<"305 Use Proxy">>;
  236. status(306) -> <<"306 Switch Proxy">>;
  237. status(307) -> <<"307 Temporary Redirect">>;
  238. status(400) -> <<"400 Bad Request">>;
  239. status(401) -> <<"401 Unauthorized">>;
  240. status(402) -> <<"402 Payment Required">>;
  241. status(403) -> <<"403 Forbidden">>;
  242. status(404) -> <<"404 Not Found">>;
  243. status(405) -> <<"405 Method Not Allowed">>;
  244. status(406) -> <<"406 Not Acceptable">>;
  245. status(407) -> <<"407 Proxy Authentication Required">>;
  246. status(408) -> <<"408 Request Timeout">>;
  247. status(409) -> <<"409 Conflict">>;
  248. status(410) -> <<"410 Gone">>;
  249. status(411) -> <<"411 Length Required">>;
  250. status(412) -> <<"412 Precondition Failed">>;
  251. status(413) -> <<"413 Request Entity Too Large">>;
  252. status(414) -> <<"414 Request-URI Too Long">>;
  253. status(415) -> <<"415 Unsupported Media Type">>;
  254. status(416) -> <<"416 Requested Range Not Satisfiable">>;
  255. status(417) -> <<"417 Expectation Failed">>;
  256. status(418) -> <<"418 I'm a teapot">>;
  257. status(422) -> <<"422 Unprocessable Entity">>;
  258. status(423) -> <<"423 Locked">>;
  259. status(424) -> <<"424 Failed Dependency">>;
  260. status(425) -> <<"425 Unordered Collection">>;
  261. status(426) -> <<"426 Upgrade Required">>;
  262. status(500) -> <<"500 Internal Server Error">>;
  263. status(501) -> <<"501 Not Implemented">>;
  264. status(502) -> <<"502 Bad Gateway">>;
  265. status(503) -> <<"503 Service Unavailable">>;
  266. status(504) -> <<"504 Gateway Timeout">>;
  267. status(505) -> <<"505 HTTP Version Not Supported">>;
  268. status(506) -> <<"506 Variant Also Negotiates">>;
  269. status(507) -> <<"507 Insufficient Storage">>;
  270. status(510) -> <<"510 Not Extended">>;
  271. status(B) when is_binary(B) -> B.
  272. -spec header_to_binary(http_header()) -> binary().
  273. header_to_binary('Cache-Control') -> <<"Cache-Control">>;
  274. header_to_binary('Connection') -> <<"Connection">>;
  275. header_to_binary('Date') -> <<"Date">>;
  276. header_to_binary('Pragma') -> <<"Pragma">>;
  277. header_to_binary('Transfer-Encoding') -> <<"Transfer-Encoding">>;
  278. header_to_binary('Upgrade') -> <<"Upgrade">>;
  279. header_to_binary('Via') -> <<"Via">>;
  280. header_to_binary('Accept') -> <<"Accept">>;
  281. header_to_binary('Accept-Charset') -> <<"Accept-Charset">>;
  282. header_to_binary('Accept-Encoding') -> <<"Accept-Encoding">>;
  283. header_to_binary('Accept-Language') -> <<"Accept-Language">>;
  284. header_to_binary('Authorization') -> <<"Authorization">>;
  285. header_to_binary('From') -> <<"From">>;
  286. header_to_binary('Host') -> <<"Host">>;
  287. header_to_binary('If-Modified-Since') -> <<"If-Modified-Since">>;
  288. header_to_binary('If-Match') -> <<"If-Match">>;
  289. header_to_binary('If-None-Match') -> <<"If-None-Match">>;
  290. header_to_binary('If-Range') -> <<"If-Range">>;
  291. header_to_binary('If-Unmodified-Since') -> <<"If-Unmodified-Since">>;
  292. header_to_binary('Max-Forwards') -> <<"Max-Forwards">>;
  293. header_to_binary('Proxy-Authorization') -> <<"Proxy-Authorization">>;
  294. header_to_binary('Range') -> <<"Range">>;
  295. header_to_binary('Referer') -> <<"Referer">>;
  296. header_to_binary('User-Agent') -> <<"User-Agent">>;
  297. header_to_binary('Age') -> <<"Age">>;
  298. header_to_binary('Location') -> <<"Location">>;
  299. header_to_binary('Proxy-Authenticate') -> <<"Proxy-Authenticate">>;
  300. header_to_binary('Public') -> <<"Public">>;
  301. header_to_binary('Retry-After') -> <<"Retry-After">>;
  302. header_to_binary('Server') -> <<"Server">>;
  303. header_to_binary('Vary') -> <<"Vary">>;
  304. header_to_binary('Warning') -> <<"Warning">>;
  305. header_to_binary('Www-Authenticate') -> <<"Www-Authenticate">>;
  306. header_to_binary('Allow') -> <<"Allow">>;
  307. header_to_binary('Content-Base') -> <<"Content-Base">>;
  308. header_to_binary('Content-Encoding') -> <<"Content-Encoding">>;
  309. header_to_binary('Content-Language') -> <<"Content-Language">>;
  310. header_to_binary('Content-Length') -> <<"Content-Length">>;
  311. header_to_binary('Content-Location') -> <<"Content-Location">>;
  312. header_to_binary('Content-Md5') -> <<"Content-Md5">>;
  313. header_to_binary('Content-Range') -> <<"Content-Range">>;
  314. header_to_binary('Content-Type') -> <<"Content-Type">>;
  315. header_to_binary('Etag') -> <<"Etag">>;
  316. header_to_binary('Expires') -> <<"Expires">>;
  317. header_to_binary('Last-Modified') -> <<"Last-Modified">>;
  318. header_to_binary('Accept-Ranges') -> <<"Accept-Ranges">>;
  319. header_to_binary('Set-Cookie') -> <<"Set-Cookie">>;
  320. header_to_binary('Set-Cookie2') -> <<"Set-Cookie2">>;
  321. header_to_binary('X-Forwarded-For') -> <<"X-Forwarded-For">>;
  322. header_to_binary('Cookie') -> <<"Cookie">>;
  323. header_to_binary('Keep-Alive') -> <<"Keep-Alive">>;
  324. header_to_binary('Proxy-Connection') -> <<"Proxy-Connection">>;
  325. header_to_binary(B) when is_binary(B) -> B.
  326. %% Tests.
  327. -ifdef(TEST).
  328. parse_qs_test_() ->
  329. %% {Qs, Result}
  330. Tests = [
  331. {<<"">>, []},
  332. {<<"a=b">>, [{<<"a">>, <<"b">>}]},
  333. {<<"aaa=bbb">>, [{<<"aaa">>, <<"bbb">>}]},
  334. {<<"a&b">>, [{<<"a">>, true}, {<<"b">>, true}]},
  335. {<<"a=b&c&d=e">>, [{<<"a">>, <<"b">>}, {<<"c">>, true}, {<<"d">>, <<"e">>}]},
  336. {<<"a=b=c=d=e&f=g">>, [{<<"a">>, <<"b=c=d=e">>}, {<<"f">>, <<"g">>}]}
  337. ],
  338. [{Qs, fun() -> R = parse_qs(Qs) end} || {Qs, R} <- Tests].
  339. -endif.