diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/cowboy_acceptor.erl | 2 | ||||
-rw-r--r-- | src/cowboy_acceptors_sup.erl | 8 | ||||
-rw-r--r-- | src/cowboy_clock.erl | 15 | ||||
-rw-r--r-- | src/cowboy_dispatcher.erl | 2 | ||||
-rw-r--r-- | src/cowboy_http.erl | 124 | ||||
-rw-r--r-- | src/cowboy_http_protocol.erl | 2 | ||||
-rw-r--r-- | src/cowboy_http_req.erl | 54 | ||||
-rw-r--r-- | src/cowboy_http_rest.erl | 44 | ||||
-rw-r--r-- | src/cowboy_http_static.erl | 34 | ||||
-rw-r--r-- | src/cowboy_http_websocket.erl | 8 | ||||
-rw-r--r-- | src/cowboy_listener.erl | 15 | ||||
-rw-r--r-- | src/cowboy_multipart.erl | 6 | ||||
-rw-r--r-- | src/cowboy_requests_sup.erl | 8 | ||||
-rw-r--r-- | src/cowboy_ssl_transport.erl | 22 | ||||
-rw-r--r-- | src/cowboy_sup.erl | 8 | ||||
-rw-r--r-- | src/cowboy_tcp_transport.erl | 4 |
16 files changed, 230 insertions, 126 deletions
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_acceptors_sup.erl b/src/cowboy_acceptors_sup.erl index 625028c..7c962d2 100644 --- a/src/cowboy_acceptors_sup.erl +++ b/src/cowboy_acceptors_sup.erl @@ -30,7 +30,13 @@ start_link(NbAcceptors, Transport, TransOpts, %% supervisor. --spec init(list()) -> {ok, {{one_for_one, 10, 10}, list()}}. +-spec init([any()]) -> {'ok', {{'one_for_one', 10, 10}, [{ + any(), {atom() | tuple(), atom(), 'undefined' | [any()]}, + 'permanent' | 'temporary' | 'transient', + 'brutal_kill' | 'infinity' | non_neg_integer(), + 'supervisor' | 'worker', + 'dynamic' | [atom() | tuple()]}] +}}. init([NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts, ListenerPid, ReqsPid]) -> {ok, LSocket} = Transport:listen(TransOpts), diff --git a/src/cowboy_clock.erl b/src/cowboy_clock.erl index c699f4f..e22b718 100644 --- a/src/cowboy_clock.erl +++ b/src/cowboy_clock.erl @@ -64,7 +64,20 @@ rfc2109(LocalTime) -> {{YYYY,MM,DD},{Hour,Min,Sec}} = case calendar:local_time_to_universal_time_dst(LocalTime) of [Gmt] -> Gmt; - [_,Gmt] -> Gmt + [_,Gmt] -> Gmt; + [] -> + %% The localtime generated by cowboy_cookies may fall within + %% the hour that is skipped by daylight savings time. If this + %% is such a localtime, increment the localtime with one hour + %% and try again, if this succeeds, subtracting the max_age + %% from the resulting universaltime and converting to a local + %% time will yield the original localtime. + {Date, {Hour1, Min1, Sec1}} = LocalTime, + LocalTime2 = {Date, {Hour1 + 1, Min1, Sec1}}, + case calendar:local_time_to_universal_time_dst(LocalTime2) of + [Gmt] -> Gmt; + [_,Gmt] -> Gmt + end end, Wday = calendar:day_of_the_week({YYYY,MM,DD}), DayBin = pad_int(DD), diff --git a/src/cowboy_dispatcher.erl b/src/cowboy_dispatcher.erl index 22f6e1e..db40e63 100644 --- a/src/cowboy_dispatcher.erl +++ b/src/cowboy_dispatcher.erl @@ -33,7 +33,7 @@ %% @doc Split a hostname into a list of tokens. -spec split_host(binary()) - -> {tokens(), binary(), undefined | inet:ip_port()}. + -> {tokens(), binary(), undefined | inet:port_number()}. split_host(<<>>) -> {[], <<>>, undefined}; split_host(Host) -> diff --git a/src/cowboy_http.erl b/src/cowboy_http.erl index 32b0ca9..9d727f3 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(). @@ -51,7 +51,6 @@ -export_type([method/0, uri/0, version/0, header/0, headers/0, status/0]). --include("include/http.hrl"). -include_lib("eunit/include/eunit.hrl"). %% Parsing. @@ -98,33 +97,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). @@ -181,6 +156,13 @@ media_type(Data, Fun) -> fun (_Rest2, <<>>) -> {error, badarg}; (Rest2, SubType) -> Fun(Rest2, Type, SubType) end); + %% This is a non-strict parsing clause required by some user agents + %% that use * instead of */* in the list of media types. + (Rest, <<"*">> = Type) -> + token_ci(<<"*", Rest/binary>>, + fun (_Rest2, <<>>) -> {error, badarg}; + (Rest2, SubType) -> Fun(Rest2, Type, SubType) + end); (_Rest, _Type) -> {error, badarg} end). @@ -319,6 +301,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 %% @@ -657,6 +683,9 @@ quoted_string(<< C, Rest/binary >>, Fun, Acc) -> -spec qvalue(binary(), fun()) -> any(). qvalue(<< $0, $., Rest/binary >>, Fun) -> qvalue(Rest, Fun, 0, 100); +%% Some user agents use q=.x instead of q=0.x +qvalue(<< $., Rest/binary >>, Fun) -> + qvalue(Rest, Fun, 0, 100); qvalue(<< $0, Rest/binary >>, Fun) -> Fun(Rest, 0); qvalue(<< $1, $., $0, $0, $0, Rest/binary >>, Fun) -> @@ -780,6 +809,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. @@ -865,6 +904,13 @@ media_range_list_test_() -> [{<<"level">>, <<"1">>}, {<<"quoted">>, <<"hi hi hi">>}]}, 123, [<<"standalone">>, {<<"complex">>, <<"gits">>}]}, {{<<"text">>, <<"plain">>, []}, 1000, []} + ]}, + {<<"text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2">>, [ + {{<<"text">>, <<"html">>, []}, 1000, []}, + {{<<"image">>, <<"gif">>, []}, 1000, []}, + {{<<"image">>, <<"jpeg">>, []}, 1000, []}, + {{<<"*">>, <<"*">>, []}, 200, []}, + {{<<"*">>, <<"*">>, []}, 200, []} ]} ], [{V, fun() -> R = list(V, fun media_range/2) end} || {V, R} <- Tests]. @@ -947,6 +993,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_protocol.erl b/src/cowboy_http_protocol.erl index d7ba508..71518fa 100644 --- a/src/cowboy_http_protocol.erl +++ b/src/cowboy_http_protocol.erl @@ -38,7 +38,7 @@ -export([start_link/4]). %% API. -export([init/4, parse_request/1, handler_loop/3]). %% FSM. --include("include/http.hrl"). +-include("http.hrl"). -include_lib("eunit/include/eunit.hrl"). -record(state, { diff --git a/src/cowboy_http_req.erl b/src/cowboy_http_req.erl index 92d96ad..6b947d9 100644 --- a/src/cowboy_http_req.erl +++ b/src/cowboy_http_req.erl @@ -50,8 +50,7 @@ compact/1, transport/1 ]). %% Misc API. --include("include/http.hrl"). --include_lib("eunit/include/eunit.hrl"). +-include("http.hrl"). %% Request API. @@ -66,7 +65,8 @@ version(Req) -> {Req#http_req.version, Req}. %% @doc Return the peer address and port number of the remote host. --spec peer(#http_req{}) -> {{inet:ip_address(), inet:ip_port()}, #http_req{}}. +-spec peer(#http_req{}) + -> {{inet:ip_address(), inet:port_number()}, #http_req{}}. peer(Req=#http_req{socket=Socket, transport=Transport, peer=undefined}) -> {ok, Peer} = Transport:peername(Socket), {Peer, Req#http_req{peer=Peer}}; @@ -114,7 +114,7 @@ raw_host(Req) -> {Req#http_req.raw_host, Req}. %% @doc Return the port used for this request. --spec port(#http_req{}) -> {inet:ip_port(), #http_req{}}. +-spec port(#http_req{}) -> {inet:port_number(), #http_req{}}. port(Req) -> {Req#http_req.port, Req}. @@ -151,7 +151,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 +164,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 +273,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 +407,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 +644,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 +805,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 589183d..c19d838 100644 --- a/src/cowboy_http_rest.erl +++ b/src/cowboy_http_rest.erl @@ -41,12 +41,12 @@ charset_a :: undefined | binary(), %% Cached resource calls. - etag :: undefined | no_call | binary(), + etag :: undefined | no_call | {strong | weak, binary()}, last_modified :: undefined | no_call | calendar:datetime(), expires :: undefined | no_call | calendar:datetime() }). --include("include/http.hrl"). +-include("http.hrl"). %% @doc Upgrade a HTTP request to the REST protocol. %% @@ -487,14 +487,10 @@ if_match_exists(Req, State) -> if_match(Req, State, EtagsList) -> {Etag, Req2, State2} = generate_etag(Req, State), - case Etag of - no_call -> - precondition_failed(Req2, State2); - Etag -> - case lists:member(Etag, EtagsList) of - true -> if_unmodified_since_exists(Req2, State2); - false -> precondition_failed(Req2, State2) - end + case lists:member(Etag, EtagsList) of + true -> if_unmodified_since_exists(Req2, State2); + %% Etag may be `undefined' which cannot be a member. + false -> precondition_failed(Req2, State2) end. if_match_musnt_exist(Req, State) -> @@ -534,7 +530,7 @@ if_none_match_exists(Req, State) -> if_none_match(Req, State, EtagsList) -> {Etag, Req2, State2} = generate_etag(Req, State), case Etag of - no_call -> + undefined -> precondition_failed(Req2, State2); Etag -> case lists:member(Etag, EtagsList) of @@ -699,7 +695,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) @@ -733,11 +729,14 @@ put_resource(Req, State, OnTrue) -> choose_content_type(Req3, State2, OnTrue, ContentType, CTA) end. +%% The special content type '*' will always match. It can be used as a +%% catch-all content type for accepting any kind of request content. +%% Note that because it will always match, it should be the last of the +%% list of content types, otherwise it'll shadow the ones following. choose_content_type(Req, State, _OnTrue, _ContentType, []) -> respond(Req, State, 415); -choose_content_type(Req, State, OnTrue, ContentType, - [{Accepted, Fun}|_Tail]) - when Accepted =:= '*' orelse ContentType =:= Accepted -> +choose_content_type(Req, State, OnTrue, ContentType, [{Accepted, Fun}|_Tail]) + when Accepted =:= '*' orelse Accepted =:= ContentType -> case call(Req, State, Fun) of {halt, Req2, HandlerState} -> terminate(Req2, State#state{handler_state=HandlerState}); @@ -811,10 +810,14 @@ set_resp_etag(Req, State) -> {Req2, State2}; Etag -> {ok, Req3} = cowboy_http_req:set_resp_header( - <<"Etag">>, Etag, Req2), + <<"ETag">>, encode_etag(Etag), Req2), {Req3, State2} end. +-spec encode_etag({strong | weak, binary()}) -> iolist(). +encode_etag({strong, Etag}) -> [$",Etag,$"]; +encode_etag({weak, Etag}) -> ["W/\"",Etag,$"]. + set_resp_expires(Req, State) -> {Expires, Req2, State2} = expires(Req, State), case Expires of @@ -835,6 +838,15 @@ generate_etag(Req, State=#state{etag=undefined}) -> case call(Req, State, generate_etag) of no_call -> {undefined, Req, State#state{etag=no_call}}; + %% Previously the return value from the generate_etag/2 callback was set + %% as the value of the ETag header in the response. Therefore the only + %% valid return type was `binary()'. If a handler returns a `binary()' + %% it must be mapped to the expected type or it'll always fail to + %% compare equal to any entity tags present in the request headers. + %% @todo Remove support for binary return values after 0.6. + {Etag, Req2, HandlerState} when is_binary(Etag) -> + [Etag2] = cowboy_http:entity_tag_match(Etag), + {Etag2, Req2, State#state{handler_state=HandlerState, etag=Etag2}}; {Etag, Req2, HandlerState} -> {Etag, Req2, State#state{handler_state=HandlerState, etag=Etag}} end; diff --git a/src/cowboy_http_static.erl b/src/cowboy_http_static.erl index 0ee996a..007cd16 100644 --- a/src/cowboy_http_static.erl +++ b/src/cowboy_http_static.erl @@ -96,15 +96,16 @@ %% %% The default behaviour can be overridden to generate an ETag header based on %% a combination of the file path, file size, inode and mtime values. If the -%% option value is a list of attribute names tagged with `attributes' a hex -%% encoded CRC32 checksum of the attribute values are used as the ETag header -%% value. +%% option value is a non-empty list of attribute names tagged with `attributes' +%% a hex encoded checksum of each attribute specified is included in the value +%% of the the ETag header. If the list of attribute names is empty no ETag +%% header is generated. %% %% If a strong ETag is required a user defined function for generating the %% header value can be supplied. The function must accept a proplist of the %% file attributes as the first argument and a second argument containing any -%% additional data that the function requires. The function must return a -%% `binary()' or `undefined'. +%% additional data that the function requires. The function must return a term +%% of the type `{weak | strong, binary()}' or `undefined'. %% %% ==== Examples ==== %% ``` @@ -130,7 +131,7 @@ %% {_, _Modified} = lists:keyfind(mtime, 1, Arguments), %% ChecksumCommand = lists:flatten(io_lib:format("sha1sum ~s", [Filepath])), %% [Checksum|_] = string:tokens(os:cmd(ChecksumCommand), " "), -%% iolist_to_binary(Checksum). +%% {strong, iolist_to_binary(Checksum)}. %% ''' -module(cowboy_http_static). @@ -161,7 +162,8 @@ filepath :: binary() | error, fileinfo :: {ok, #file_info{}} | {error, _} | error, mimetypes :: {fun((binary(), T) -> [mimedef()]), T} | undefined, - etag_fun :: {fun(([etagarg()], T) -> undefined | binary()), T}}). + etag_fun :: {fun(([etagarg()], T) -> + undefined | {strong | weak, binary()}), T}}). %% @private Upgrade from HTTP handler to REST handler. @@ -183,8 +185,9 @@ rest_init(Req, Opts) -> ETagFunction = case proplists:get_value(etag, Opts) of default -> {fun no_etag_function/2, undefined}; undefined -> {fun no_etag_function/2, undefined}; + {attributes, []} -> {fun no_etag_function/2, undefined}; {attributes, Attrs} -> {fun attr_etag_function/2, Attrs}; - {_, _}=EtagFunction1 -> EtagFunction1 + {_, _}=ETagFunction1 -> ETagFunction1 end, {Filepath, Req1} = cowboy_http_req:path_info(Req), State = case check_path(Filepath) of @@ -411,16 +414,13 @@ no_etag_function(_Args, undefined) -> %% @private A simple alternative is to send an ETag based on file attributes. -type fileattr() :: filepath | filesize | mtime | inode. --spec attr_etag_function([etagarg()], [fileattr()]) -> binary(). +-spec attr_etag_function([etagarg()], [fileattr()]) -> {strong, binary()}. attr_etag_function(Args, Attrs) -> - attr_etag_function(Args, Attrs, []). - --spec attr_etag_function([etagarg()], [fileattr()], [binary()]) -> binary(). -attr_etag_function(_Args, [], Acc) -> - list_to_binary(integer_to_list(erlang:crc32(Acc), 16)); -attr_etag_function(Args, [H|T], Acc) -> - {_, Value} = lists:keyfind(H, 1, Args), - attr_etag_function(Args, T, [term_to_binary(Value)|Acc]). + [[_|H]|T] = [begin + {_,Pair} = {_,{_,_}} = {Attr,lists:keyfind(Attr, 1, Args)}, + [$-|integer_to_list(erlang:phash2(Pair, 1 bsl 32), 16)] + end || Attr <- Attrs], + {strong, list_to_binary([H|T])}. -ifdef(TEST). diff --git a/src/cowboy_http_websocket.erl b/src/cowboy_http_websocket.erl index ec75571..40fef23 100644 --- a/src/cowboy_http_websocket.erl +++ b/src/cowboy_http_websocket.erl @@ -40,7 +40,7 @@ -export([upgrade/4]). %% API. -export([handler_loop/4]). %% Internal. --include("include/http.hrl"). +-include("http.hrl"). -include_lib("eunit/include/eunit.hrl"). -type opcode() :: 0 | 1 | 2 | 8 | 9 | 10. @@ -467,8 +467,8 @@ hixie76_key_to_integer(Key) -> Spaces = length([C || << C >> <= Key, C =:= 32]), Number div Spaces. --spec hixie76_location(atom(), binary(), inet:ip_port(), binary(), binary()) - -> binary(). +-spec hixie76_location(atom(), binary(), inet:port_number(), + binary(), binary()) -> binary(). hixie76_location(Protocol, Host, Port, Path, <<>>) -> << (hixie76_location_protocol(Protocol))/binary, "://", Host/binary, (hixie76_location_port(Protocol, Port))/binary, Path/binary>>; @@ -482,7 +482,7 @@ hixie76_location_protocol(_) -> <<"ws">>. %% @todo We should add a secure/0 function to transports %% instead of relying on their name. --spec hixie76_location_port(atom(), inet:ip_port()) -> binary(). +-spec hixie76_location_port(atom(), inet:port_number()) -> binary(). hixie76_location_port(ssl, 443) -> <<>>; hixie76_location_port(tcp, 80) -> 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_requests_sup.erl b/src/cowboy_requests_sup.erl index 87d5352..ddd8d3b 100644 --- a/src/cowboy_requests_sup.erl +++ b/src/cowboy_requests_sup.erl @@ -32,7 +32,13 @@ start_request(ListenerPid, Socket, Transport, Protocol, Opts) -> %% supervisor. --spec init([]) -> {ok, {{simple_one_for_one, 0, 1}, [{_, _, _, _, _, _}, ...]}}. +-spec init([]) -> {'ok', {{'simple_one_for_one', 0, 1}, [{ + any(), {atom() | tuple(), atom(), 'undefined' | [any()]}, + 'permanent' | 'temporary' | 'transient', + 'brutal_kill' | 'infinity' | non_neg_integer(), + 'supervisor' | 'worker', + 'dynamic' | [atom() | tuple()]}] +}}. init([]) -> {ok, {{simple_one_for_one, 0, 1}, [{?MODULE, {?MODULE, start_request, []}, temporary, brutal_kill, worker, [?MODULE]}]}}. diff --git a/src/cowboy_ssl_transport.erl b/src/cowboy_ssl_transport.erl index bf8b1fb..ccd8e5a 100644 --- a/src/cowboy_ssl_transport.erl +++ b/src/cowboy_ssl_transport.erl @@ -58,8 +58,7 @@ messages() -> {ssl, ssl_closed, ssl_error}. %% </dl> %% %% @see ssl:listen/2 -%% @todo The password option shouldn't be mandatory. --spec listen([{port, inet:ip_port()} | {certfile, string()} +-spec listen([{port, inet:port_number()} | {certfile, string()} | {keyfile, string()} | {password, string()} | {cacertfile, string()} | {ip, inet:ip_address()}]) -> {ok, ssl:sslsocket()} | {error, atom()}. @@ -68,21 +67,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. @@ -131,7 +139,7 @@ controlling_process(Socket, Pid) -> %% @doc Return the address and port for the other end of a connection. %% @see ssl:peername/1 -spec peername(ssl:sslsocket()) - -> {ok, {inet:ip_address(), inet:ip_port()}} | {error, atom()}. + -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}. peername(Socket) -> ssl:peername(Socket). diff --git a/src/cowboy_sup.erl b/src/cowboy_sup.erl index 34591bc..502c592 100644 --- a/src/cowboy_sup.erl +++ b/src/cowboy_sup.erl @@ -29,7 +29,13 @@ start_link() -> %% supervisor. --spec init([]) -> {ok, {{one_for_one, 10, 10}, [{_, _, _, _, _, _}, ...]}}. +-spec init([]) -> {'ok', {{'one_for_one', 10, 10}, [{ + any(), {atom() | tuple(), atom(), 'undefined' | [any()]}, + 'permanent' | 'temporary' | 'transient', + 'brutal_kill' | 'infinity' | non_neg_integer(), + 'supervisor' | 'worker', + 'dynamic' | [atom() | tuple()]}] +}}. init([]) -> Procs = [{cowboy_clock, {cowboy_clock, start_link, []}, permanent, 5000, worker, [cowboy_clock]}], diff --git a/src/cowboy_tcp_transport.erl b/src/cowboy_tcp_transport.erl index c1dad62..82d193b 100644 --- a/src/cowboy_tcp_transport.erl +++ b/src/cowboy_tcp_transport.erl @@ -45,7 +45,7 @@ messages() -> {tcp, tcp_closed, tcp_error}. %% </dl> %% %% @see gen_tcp:listen/2 --spec listen([{port, inet:ip_port()} | {ip, inet:ip_address()}]) +-spec listen([{port, inet:port_number()} | {ip, inet:ip_address()}]) -> {ok, inet:socket()} | {error, atom()}. listen(Opts) -> {port, Port} = lists:keyfind(port, 1, Opts), @@ -95,7 +95,7 @@ controlling_process(Socket, Pid) -> %% @doc Return the address and port for the other end of a connection. %% @see inet:peername/1 -spec peername(inet:socket()) - -> {ok, {inet:ip_address(), inet:ip_port()}} | {error, atom()}. + -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}. peername(Socket) -> inet:peername(Socket). |