123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276 |
- %% Copyright (c) 2011, Loïc Hoguin <essen@dev-extend.eu>
- %%
- %% Permission to use, copy, modify, and/or distribute this software for any
- %% purpose with or without fee is hereby granted, provided that the above
- %% copyright notice and this permission notice appear in all copies.
- %%
- %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- -module(cowboy_http_req).
- -export([
- method/1, version/1, peer/1,
- host/1, raw_host/1,
- path/1, raw_path/1,
- qs_val/2, qs_val/3, qs_vals/1, raw_qs/1,
- binding/2, binding/3, bindings/1,
- header/2, header/3, headers/1
- %% cookie/2, cookie/3, cookies/1 @todo
- ]). %% Request API.
- -export([
- body/1, body/2, body_qs/1
- ]). %% Request Body API.
- -export([
- reply/4
- ]). %% Response API.
- -include("include/types.hrl").
- -include("include/http.hrl").
- -include_lib("eunit/include/eunit.hrl").
- %% Request API.
- -spec method(Req::#http_req{}) -> {Method::http_method(), Req::#http_req{}}.
- method(Req) ->
- {Req#http_req.method, Req}.
- -spec version(Req::#http_req{}) -> {Version::http_version(), Req::#http_req{}}.
- version(Req) ->
- {Req#http_req.version, Req}.
- -spec peer(Req::#http_req{})
- -> {{Address::ip_address(), Port::port_number()}, Req::#http_req{}}.
- peer(Req=#http_req{socket=Socket, transport=Transport, peer=undefined}) ->
- {ok, Peer} = Transport:peername(Socket),
- {Peer, Req#http_req{peer=Peer}};
- peer(Req) ->
- {Req#http_req.peer, Req}.
- -spec host(Req::#http_req{}) -> {Host::path_tokens(), Req::#http_req{}}.
- host(Req) ->
- {Req#http_req.host, Req}.
- -spec raw_host(Req::#http_req{}) -> {RawHost::string(), Req::#http_req{}}.
- raw_host(Req) ->
- {Req#http_req.raw_host, Req}.
- -spec path(Req::#http_req{}) -> {Path::path_tokens(), Req::#http_req{}}.
- path(Req) ->
- {Req#http_req.path, Req}.
- -spec raw_path(Req::#http_req{}) -> {RawPath::string(), Req::#http_req{}}.
- raw_path(Req) ->
- {Req#http_req.raw_path, Req}.
- -spec qs_val(Name::atom(), Req::#http_req{})
- -> {Value::string(), Req::#http_req{}}.
- qs_val(Name, Req=#http_req{raw_qs=RawQs, qs_vals=undefined}) ->
- QsVals = parse_qs(RawQs),
- qs_val(Name, Req#http_req{qs_vals=QsVals});
- qs_val(Name, Req) ->
- {Name, Value} = lists:keyfind(Name, 1, Req#http_req.qs_vals),
- {Value, Req}.
- -spec qs_val(Name::atom(), Default::term(), Req::#http_req{})
- -> {Value::string() | term(), Req::#http_req{}}.
- qs_val(Name, Default, Req=#http_req{raw_qs=RawQs, qs_vals=undefined}) ->
- QsVals = parse_qs(RawQs),
- qs_val(Name, Default, Req#http_req{qs_vals=QsVals});
- qs_val(Name, Default, Req) ->
- Value = proplists:get_value(Name, Req#http_req.qs_vals, Default),
- {Value, Req}.
- -spec qs_vals(Req::#http_req{})
- -> {list({Name::atom(), Value::string()}), Req::#http_req{}}.
- qs_vals(Req=#http_req{raw_qs=RawQs, qs_vals=undefined}) ->
- QsVals = parse_qs(RawQs),
- qs_vals(Req#http_req{qs_vals=QsVals});
- qs_vals(Req=#http_req{qs_vals=QsVals}) ->
- {QsVals, Req}.
- -spec raw_qs(Req::#http_req{}) -> {RawQs::string(), Req::#http_req{}}.
- raw_qs(Req) ->
- {Req#http_req.raw_qs, Req}.
- -spec binding(Name::atom(), Req::#http_req{})
- -> {Value::string(), Req::#http_req{}}.
- binding(Name, Req) ->
- {Name, Value} = lists:keyfind(Name, 1, Req#http_req.bindings),
- {Value, Req}.
- -spec binding(Name::atom(), Default::term(), Req::#http_req{})
- -> {Value::string() | term(), Req::#http_req{}}.
- binding(Name, Default, Req) ->
- Value = proplists:get_value(Name, Req#http_req.bindings, Default),
- {Value, Req}.
- -spec bindings(Req::#http_req{})
- -> {list({Name::atom(), Value::string()}), Req::#http_req{}}.
- bindings(Req) ->
- {Req#http_req.bindings, Req}.
- -spec header(Name::atom() | string(), Req::#http_req{})
- -> {Value::string(), Req::#http_req{}}.
- header(Name, Req) ->
- case lists:keyfind(Name, 1, Req#http_req.headers) of
- {Name, Value} -> {Value, Req};
- false -> {"", Req}
- end.
- -spec header(Name::atom() | string(), Default::term(), Req::#http_req{})
- -> {Value::string() | term(), Req::#http_req{}}.
- header(Name, Default, Req) ->
- Value = proplists:get_value(Name, Req#http_req.headers, Default),
- {Value, Req}.
- -spec headers(Req::#http_req{})
- -> {list({Name::atom() | string(), Value::string()}), Req::#http_req{}}.
- headers(Req) ->
- {Req#http_req.headers, Req}.
- %% Request Body API.
- %% @todo We probably want to allow a max length.
- -spec body(Req::#http_req{})
- -> {ok, Body::binary(), Req::#http_req{}} | {error, Reason::posix()}.
- body(Req) ->
- {Length, Req2} = cowboy_http_req:header('Content-Length', Req),
- case Length of
- "" -> {error, badarg};
- _Any ->
- Length2 = list_to_integer(Length),
- body(Length2, Req2)
- end.
- %% @todo We probably want to configure the timeout.
- -spec body(Length::non_neg_integer(), Req::#http_req{})
- -> {ok, Body::binary(), Req::#http_req{}} | {error, Reason::posix()}.
- body(Length, Req=#http_req{socket=Socket, transport=Transport,
- body_state=waiting}) ->
- Transport:setopts(Socket, [{packet, raw}]),
- case Transport:recv(Socket, Length, 5000) of
- {ok, Body} -> {ok, Body, Req#http_req{body_state=done}};
- {error, Reason} -> {error, Reason}
- end.
- -spec body_qs(Req::#http_req{})
- -> {list({Name::string(), Value::string()}), Req::#http_req{}}.
- body_qs(Req) ->
- {ok, Body, Req2} = body(Req),
- {parse_qs(binary_to_list(Body)), Req2}.
- %% Response API.
- -spec reply(Code::http_status(), Headers::http_headers(),
- Body::iolist(), Req::#http_req{}) -> {ok, Req::#http_req{}}.
- %% @todo Don't be naive about the headers!
- reply(Code, Headers, Body, Req=#http_req{socket=Socket,
- transport=Transport, connection=Connection,
- resp_state=waiting}) ->
- StatusLine = ["HTTP/1.1 ", status(Code), "\r\n"],
- BaseHeaders = ["Connection: ", atom_to_connection(Connection),
- "\r\nContent-Length: ", integer_to_list(iolist_size(Body)), "\r\n"],
- Transport:send(Socket,
- [StatusLine, BaseHeaders, Headers, "\r\n", Body]),
- {ok, Req#http_req{resp_state=done}}.
- %% Internal.
- -spec parse_qs(Qs::string()) -> list({Name::string(), Value::string() | true}).
- parse_qs(Qs) ->
- Tokens = string:tokens(Qs, "&"),
- [case string:chr(Token, $=) of
- 0 ->
- {Token, true};
- N ->
- {Name, [$=|Value]} = lists:split(N - 1, Token),
- {Name, Value}
- end || Token <- Tokens].
- -spec atom_to_connection(Atom::keepalive | close) -> string().
- atom_to_connection(keepalive) ->
- "keep-alive";
- atom_to_connection(close) ->
- "close".
- -spec status(Code::http_status()) -> string().
- status(100) -> "100 Continue";
- status(101) -> "101 Switching Protocols";
- status(102) -> "102 Processing";
- status(200) -> "200 OK";
- status(201) -> "201 Created";
- status(202) -> "202 Accepted";
- status(203) -> "203 Non-Authoritative Information";
- status(204) -> "204 No Content";
- status(205) -> "205 Reset Content";
- status(206) -> "206 Partial Content";
- status(207) -> "207 Multi-Status";
- status(226) -> "226 IM Used";
- status(300) -> "300 Multiple Choices";
- status(301) -> "301 Moved Permanently";
- status(302) -> "302 Found";
- status(303) -> "303 See Other";
- status(304) -> "304 Not Modified";
- status(305) -> "305 Use Proxy";
- status(306) -> "306 Switch Proxy";
- status(307) -> "307 Temporary Redirect";
- status(400) -> "400 Bad Request";
- status(401) -> "401 Unauthorized";
- status(402) -> "402 Payment Required";
- status(403) -> "403 Forbidden";
- status(404) -> "404 Not Found";
- status(405) -> "405 Method Not Allowed";
- status(406) -> "406 Not Acceptable";
- status(407) -> "407 Proxy Authentication Required";
- status(408) -> "408 Request Timeout";
- status(409) -> "409 Conflict";
- status(410) -> "410 Gone";
- status(411) -> "411 Length Required";
- status(412) -> "412 Precondition Failed";
- status(413) -> "413 Request Entity Too Large";
- status(414) -> "414 Request-URI Too Long";
- status(415) -> "415 Unsupported Media Type";
- status(416) -> "416 Requested Range Not Satisfiable";
- status(417) -> "417 Expectation Failed";
- status(418) -> "418 I'm a teapot";
- status(422) -> "422 Unprocessable Entity";
- status(423) -> "423 Locked";
- status(424) -> "424 Failed Dependency";
- status(425) -> "425 Unordered Collection";
- status(426) -> "426 Upgrade Required";
- status(500) -> "500 Internal Server Error";
- status(501) -> "501 Not Implemented";
- status(502) -> "502 Bad Gateway";
- status(503) -> "503 Service Unavailable";
- status(504) -> "504 Gateway Timeout";
- status(505) -> "505 HTTP Version Not Supported";
- status(506) -> "506 Variant Also Negotiates";
- status(507) -> "507 Insufficient Storage";
- status(510) -> "510 Not Extended";
- status(L) when is_list(L) -> L.
- %% Tests.
- -ifdef(TEST).
- parse_qs_test_() ->
- %% {Qs, Result}
- Tests = [
- {"", []},
- {"a=b", [{"a", "b"}]},
- {"aaa=bbb", [{"aaa", "bbb"}]},
- {"a&b", [{"a", true}, {"b", true}]},
- {"a=b&c&d=e", [{"a", "b"}, {"c", true}, {"d", "e"}]},
- {"a=b=c=d=e&f=g", [{"a", "b=c=d=e"}, {"f", "g"}]}
- ],
- [{Qs, fun() -> R = parse_qs(Qs) end} || {Qs, R} <- Tests].
- -endif.
|