diff options
-rw-r--r-- | ebin/.gitignore | 0 | ||||
-rw-r--r-- | examples/README.md | 6 | ||||
-rw-r--r-- | src/cowboy_acceptor.erl | 2 | ||||
-rw-r--r-- | src/cowboy_http.erl | 106 | ||||
-rw-r--r-- | src/cowboy_http_req.erl | 47 | ||||
-rw-r--r-- | src/cowboy_http_rest.erl | 2 | ||||
-rw-r--r-- | src/cowboy_listener.erl | 15 | ||||
-rw-r--r-- | src/cowboy_multipart.erl | 6 | ||||
-rw-r--r-- | src/cowboy_ssl_transport.erl | 17 |
9 files changed, 126 insertions, 75 deletions
diff --git a/ebin/.gitignore b/ebin/.gitignore new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/ebin/.gitignore diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..dc88057 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,6 @@ +Cowboy examples +=============== + +The Cowboy examples can be found in a separate repository: + +* https://github.com/extend/cowboy_examples diff --git a/src/cowboy_acceptor.erl b/src/cowboy_acceptor.erl index 29f7c09..b2a1ef0 100644 --- a/src/cowboy_acceptor.erl +++ b/src/cowboy_acceptor.erl @@ -41,7 +41,7 @@ acceptor(LSocket, Transport, Protocol, Opts, OptsVsn, ListenerPid, ReqsSup) -> cowboy_listener:add_connection(ListenerPid, default, Pid, OptsVsn); {error, timeout} -> - ok; + cowboy_listener:check_upgrades(ListenerPid, OptsVsn); {error, _Reason} -> %% @todo Probably do something here. If the socket was closed, %% we may want to try and listen again on the port? diff --git a/src/cowboy_http.erl b/src/cowboy_http.erl index 32b0ca9..93b83e5 100644 --- a/src/cowboy_http.erl +++ b/src/cowboy_http.erl @@ -17,14 +17,14 @@ -module(cowboy_http). %% Parsing. --export([list/2, nonempty_list/2, content_type/1, content_type_params/3, - media_range/2, conneg/2, language_range/2, entity_tag_match/1, +-export([list/2, nonempty_list/2, content_type/1, media_range/2, conneg/2, + language_range/2, entity_tag_match/1, expectation/2, params/2, http_date/1, rfc1123_date/1, rfc850_date/1, asctime_date/1, whitespace/2, digits/1, token/2, token_ci/2, quoted_string/2]). %% Interpretation. -export([connection_to_atom/1, urldecode/1, urldecode/2, urlencode/1, - urlencode/2]). + urlencode/2, x_www_form_urlencoded/2]). -type method() :: 'OPTIONS' | 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'TRACE' | binary(). @@ -98,33 +98,9 @@ list(Data, Fun, Acc) -> content_type(Data) -> media_type(Data, fun (Rest, Type, SubType) -> - content_type_params(Rest, - fun (Params) -> {Type, SubType, Params} end, []) - end). - --spec content_type_params(binary(), fun(), list({binary(), binary()})) - -> any(). -content_type_params(Data, Fun, Acc) -> - whitespace(Data, - fun (<< $;, Rest/binary >>) -> content_type_param(Rest, Fun, Acc); - (<<>>) -> Fun(lists:reverse(Acc)); - (_Rest) -> {error, badarg} - end). - --spec content_type_param(binary(), fun(), list({binary(), binary()})) - -> any(). -content_type_param(Data, Fun, Acc) -> - whitespace(Data, - fun (Rest) -> - token_ci(Rest, - fun (_Rest2, <<>>) -> {error, badarg}; - (<< $=, Rest2/binary >>, Attr) -> - word(Rest2, - fun (Rest3, Value) -> - content_type_params(Rest3, Fun, - [{Attr, Value}|Acc]) - end); - (_Rest2, _Attr) -> {error, badarg} + params(Rest, + fun (<<>>, Params) -> {Type, SubType, Params}; + (_Rest2, _) -> {error, badarg} end) end). @@ -319,6 +295,50 @@ opaque_tag(Data, Fun, Strength) -> (Rest, OpaqueTag) -> Fun(Rest, {Strength, OpaqueTag}) end). +%% @doc Parse an expectation. +-spec expectation(binary(), fun()) -> any(). +expectation(Data, Fun) -> + token_ci(Data, + fun (_Rest, <<>>) -> {error, badarg}; + (<< $=, Rest/binary >>, Expectation) -> + word(Rest, + fun (Rest2, ExtValue) -> + params(Rest2, fun (Rest3, ExtParams) -> + Fun(Rest3, {Expectation, ExtValue, ExtParams}) + end) + end); + (Rest, Expectation) -> + Fun(Rest, Expectation) + end). + +%% @doc Parse a list of parameters (a=b;c=d). +-spec params(binary(), fun()) -> any(). +params(Data, Fun) -> + params(Data, Fun, []). + +-spec params(binary(), fun(), [{binary(), binary()}]) -> any(). +params(Data, Fun, Acc) -> + whitespace(Data, + fun (<< $;, Rest/binary >>) -> param(Rest, Fun, Acc); + (Rest) -> Fun(Rest, lists:reverse(Acc)) + end). + +-spec param(binary(), fun(), [{binary(), binary()}]) -> any(). +param(Data, Fun, Acc) -> + whitespace(Data, + fun (Rest) -> + token_ci(Rest, + fun (_Rest2, <<>>) -> {error, badarg}; + (<< $=, Rest2/binary >>, Attr) -> + word(Rest2, + fun (Rest3, Value) -> + params(Rest3, Fun, + [{Attr, Value}|Acc]) + end); + (_Rest2, _Attr) -> {error, badarg} + end) + end). + %% @doc Parse an HTTP date (RFC1123, RFC850 or asctime date). %% @end %% @@ -780,6 +800,16 @@ tohexu(C) when C < 17 -> $A + C - 10. tohexl(C) when C < 10 -> $0 + C; tohexl(C) when C < 17 -> $a + C - 10. +-spec x_www_form_urlencoded(binary(), fun((binary()) -> binary())) -> + list({binary(), binary() | true}). +x_www_form_urlencoded(<<>>, _URLDecode) -> + []; +x_www_form_urlencoded(Qs, URLDecode) -> + Tokens = binary:split(Qs, <<"&">>, [global, trim]), + [case binary:split(Token, <<"=">>) of + [Token] -> {URLDecode(Token), true}; + [Name, Value] -> {URLDecode(Name), URLDecode(Value)} + end || Token <- Tokens]. %% Tests. @@ -947,6 +977,22 @@ digits_test_() -> ], [{V, fun() -> R = digits(V) end} || {V, R} <- Tests]. +x_www_form_urlencoded_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">>}]}, + {<<"a+b=c+d">>, [{<<"a b">>, <<"c d">>}]} + ], + URLDecode = fun urldecode/1, + [{Qs, fun() -> R = x_www_form_urlencoded( + Qs, URLDecode) end} || {Qs, R} <- Tests]. + urldecode_test_() -> U = fun urldecode/2, [?_assertEqual(<<" ">>, U(<<"%20">>, crash)), diff --git a/src/cowboy_http_req.erl b/src/cowboy_http_req.erl index 92d96ad..dd8e5ca 100644 --- a/src/cowboy_http_req.erl +++ b/src/cowboy_http_req.erl @@ -51,7 +51,6 @@ ]). %% Misc API. -include("include/http.hrl"). --include_lib("eunit/include/eunit.hrl"). %% Request API. @@ -151,7 +150,8 @@ qs_val(Name, Req) when is_binary(Name) -> -> {binary() | true | Default, #http_req{}} when Default::any(). qs_val(Name, Req=#http_req{raw_qs=RawQs, qs_vals=undefined, urldecode={URLDecFun, URLDecArg}}, Default) when is_binary(Name) -> - QsVals = parse_qs(RawQs, fun(Bin) -> URLDecFun(Bin, URLDecArg) end), + QsVals = cowboy_http:x_www_form_urlencoded( + RawQs, fun(Bin) -> URLDecFun(Bin, URLDecArg) end), qs_val(Name, Req#http_req{qs_vals=QsVals}, Default); qs_val(Name, Req, Default) -> case lists:keyfind(Name, 1, Req#http_req.qs_vals) of @@ -163,7 +163,8 @@ qs_val(Name, Req, Default) -> -spec qs_vals(#http_req{}) -> {list({binary(), binary() | true}), #http_req{}}. qs_vals(Req=#http_req{raw_qs=RawQs, qs_vals=undefined, urldecode={URLDecFun, URLDecArg}}) -> - QsVals = parse_qs(RawQs, fun(Bin) -> URLDecFun(Bin, URLDecArg) end), + QsVals = cowboy_http:x_www_form_urlencoded( + RawQs, fun(Bin) -> URLDecFun(Bin, URLDecArg) end), qs_vals(Req#http_req{qs_vals=QsVals}); qs_vals(Req=#http_req{qs_vals=QsVals}) -> {QsVals, Req}. @@ -271,6 +272,11 @@ parse_header(Name, Req, Default) when Name =:= 'Content-Type' -> fun (Value) -> cowboy_http:content_type(Value) end); +parse_header(Name, Req, Default) when Name =:= 'Expect' -> + parse_header(Name, Req, Default, + fun (Value) -> + cowboy_http:nonempty_list(Value, fun cowboy_http:expectation/2) + end); parse_header(Name, Req, Default) when Name =:= 'If-Match'; Name =:= 'If-None-Match' -> parse_header(Name, Req, Default, @@ -400,7 +406,8 @@ body(Length, Req=#http_req{socket=Socket, transport=Transport, -spec body_qs(#http_req{}) -> {list({binary(), binary() | true}), #http_req{}}. body_qs(Req=#http_req{urldecode={URLDecFun, URLDecArg}}) -> {ok, Body, Req2} = body(Req), - {parse_qs(Body, fun(Bin) -> URLDecFun(Bin, URLDecArg) end), Req2}. + {cowboy_http:x_www_form_urlencoded( + Body, fun(Bin) -> URLDecFun(Bin, URLDecArg) end), Req2}. %% Multipart Request API. @@ -636,17 +643,6 @@ transport(#http_req{transport=Transport, socket=Socket}) -> %% Internal. --spec parse_qs(binary(), fun((binary()) -> binary())) -> - list({binary(), binary() | true}). -parse_qs(<<>>, _URLDecode) -> - []; -parse_qs(Qs, URLDecode) -> - Tokens = binary:split(Qs, <<"&">>, [global, trim]), - [case binary:split(Token, <<"=">>) of - [Token] -> {URLDecode(Token), true}; - [Name, Value] -> {URLDecode(Name), URLDecode(Value)} - end || Token <- Tokens]. - -spec response_connection(cowboy_http:headers(), keepalive | close) -> keepalive | close. response_connection([], Connection) -> @@ -808,24 +804,3 @@ header_to_binary('Cookie') -> <<"Cookie">>; header_to_binary('Keep-Alive') -> <<"Keep-Alive">>; header_to_binary('Proxy-Connection') -> <<"Proxy-Connection">>; header_to_binary(B) when is_binary(B) -> B. - -%% 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">>}]}, - {<<"a+b=c+d">>, [{<<"a b">>, <<"c d">>}]} - ], - URLDecode = fun cowboy_http:urldecode/1, - [{Qs, fun() -> R = parse_qs(Qs, URLDecode) end} || {Qs, R} <- Tests]. - --endif. diff --git a/src/cowboy_http_rest.erl b/src/cowboy_http_rest.erl index 392b172..8f14388 100644 --- a/src/cowboy_http_rest.erl +++ b/src/cowboy_http_rest.erl @@ -699,7 +699,7 @@ process_post(Req, State) -> terminate(Req2, State#state{handler_state=HandlerState}); {true, Req2, HandlerState} -> State2 = State#state{handler_state=HandlerState}, - next(Req2, State2, 201); + next(Req2, State2, fun is_new_resource/2); {false, Req2, HandlerState} -> State2 = State#state{handler_state=HandlerState}, respond(Req2, State2, 500) diff --git a/src/cowboy_listener.erl b/src/cowboy_listener.erl index ad54941..4b2c2fb 100644 --- a/src/cowboy_listener.erl +++ b/src/cowboy_listener.erl @@ -17,7 +17,7 @@ -behaviour(gen_server). -export([start_link/2, stop/1, - add_connection/4, move_connection/3, remove_connection/2, + add_connection/4, move_connection/3, remove_connection/2, check_upgrades/2, get_protocol_options/1, set_protocol_options/2]). %% API. -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% gen_server. @@ -84,6 +84,12 @@ move_connection(ServerPid, DestPool, ConnPid) -> remove_connection(ServerPid, ConnPid) -> gen_server:cast(ServerPid, {remove_connection, ConnPid}). +%% @doc Return whether a protocol upgrade is required. +-spec check_upgrades(pid(), non_neg_integer()) + -> ok | {upgrade, any(), non_neg_integer()}. +check_upgrades(ServerPid, OptsVsn) -> + gen_server:call(ServerPid, {check_upgrades, OptsVsn}). + %% @doc Return the current protocol options. -spec get_protocol_options(pid()) -> {ok, any()}. get_protocol_options(ServerPid) -> @@ -121,6 +127,13 @@ handle_call({add_connection, Pool, ConnPid, AccOptsVsn}, From, State=#state{ true -> {reply, ok, State2} end; +handle_call({check_upgrades, AccOptsVsn}, _From, State=#state{ + proto_opts=ProtoOpts, proto_opts_vsn=LisOptsVsn}) -> + if AccOptsVsn =/= LisOptsVsn -> + {reply, {upgrade, ProtoOpts, LisOptsVsn}, State}; + true -> + {reply, ok, State} + end; handle_call(get_protocol_options, _From, State=#state{proto_opts=ProtoOpts}) -> {reply, {ok, ProtoOpts}, State}; handle_call({set_protocol_options, ProtoOpts}, _From, diff --git a/src/cowboy_multipart.erl b/src/cowboy_multipart.erl index b7aeb54..2428b52 100644 --- a/src/cowboy_multipart.erl +++ b/src/cowboy_multipart.erl @@ -45,8 +45,10 @@ content_disposition(Data) -> cowboy_http:token_ci(Data, fun (_Rest, <<>>) -> {error, badarg}; (Rest, Disposition) -> - cowboy_http:content_type_params(Rest, - fun (Params) -> {Disposition, Params} end, []) + cowboy_http:params(Rest, + fun (<<>>, Params) -> {Disposition, Params}; + (_Rest2, _) -> {error, badarg} + end) end). %% Internal. diff --git a/src/cowboy_ssl_transport.erl b/src/cowboy_ssl_transport.erl index bf8b1fb..8eaf320 100644 --- a/src/cowboy_ssl_transport.erl +++ b/src/cowboy_ssl_transport.erl @@ -68,21 +68,30 @@ listen(Opts) -> {port, Port} = lists:keyfind(port, 1, Opts), Backlog = proplists:get_value(backlog, Opts, 1024), {certfile, CertFile} = lists:keyfind(certfile, 1, Opts), - {keyfile, KeyFile} = lists:keyfind(keyfile, 1, Opts), - {password, Password} = lists:keyfind(password, 1, Opts), + KeyFileOpts = + case lists:keyfind(keyfile, 1, Opts) of + false -> []; + KeyFile -> [KeyFile] + end, + PasswordOpts = + case lists:keyfind(password, 1, Opts) of + false -> []; + Password -> [Password] + end, ListenOpts0 = [binary, {active, false}, {backlog, Backlog}, {packet, raw}, {reuseaddr, true}, - {certfile, CertFile}, {keyfile, KeyFile}, {password, Password}], + {certfile, CertFile}], ListenOpts1 = case lists:keyfind(ip, 1, Opts) of false -> ListenOpts0; Ip -> [Ip|ListenOpts0] end, - ListenOpts = + ListenOpts2 = case lists:keyfind(cacertfile, 1, Opts) of false -> ListenOpts1; CACertFile -> [CACertFile|ListenOpts1] end, + ListenOpts = ListenOpts2 ++ KeyFileOpts ++ PasswordOpts, ssl:listen(Port, ListenOpts). %% @doc Accept an incoming connection on a listen socket. |