aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cowboy_acceptor.erl2
-rw-r--r--src/cowboy_acceptors_sup.erl8
-rw-r--r--src/cowboy_clock.erl15
-rw-r--r--src/cowboy_dispatcher.erl2
-rw-r--r--src/cowboy_http.erl124
-rw-r--r--src/cowboy_http_protocol.erl2
-rw-r--r--src/cowboy_http_req.erl54
-rw-r--r--src/cowboy_http_rest.erl44
-rw-r--r--src/cowboy_http_static.erl34
-rw-r--r--src/cowboy_http_websocket.erl8
-rw-r--r--src/cowboy_listener.erl15
-rw-r--r--src/cowboy_multipart.erl6
-rw-r--r--src/cowboy_requests_sup.erl8
-rw-r--r--src/cowboy_ssl_transport.erl22
-rw-r--r--src/cowboy_sup.erl8
-rw-r--r--src/cowboy_tcp_transport.erl4
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).