aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2017-10-25 20:17:21 +0100
committerLoïc Hoguin <[email protected]>2017-10-25 21:03:26 +0100
commitef58e15547ee171a716eaa768374e2e7e2f7d397 (patch)
tree0871f7f142014592f581f7c1a9705eb95d65dc2a /src
parent4090adaecc6ba30eeeabf50aee57c22945fa27eb (diff)
downloadcowboy-ef58e15547ee171a716eaa768374e2e7e2f7d397.tar.gz
cowboy-ef58e15547ee171a716eaa768374e2e7e2f7d397.tar.bz2
cowboy-ef58e15547ee171a716eaa768374e2e7e2f7d397.zip
Introduce cowboy_req:sock/1 and cowboy_req:cert/1
To obtain the local socket ip/port and the client TLS certificate, respectively.
Diffstat (limited to 'src')
-rw-r--r--src/cowboy_http.erl55
-rw-r--r--src/cowboy_http2.erl62
-rw-r--r--src/cowboy_req.erl10
3 files changed, 99 insertions, 28 deletions
diff --git a/src/cowboy_http.erl b/src/cowboy_http.erl
index 2ed0840..f6d064e 100644
--- a/src/cowboy_http.erl
+++ b/src/cowboy_http.erl
@@ -85,6 +85,12 @@
%% Remote address and port for the connection.
peer = undefined :: {inet:ip_address(), inet:port_number()},
+ %% Local address and port for the connection.
+ sock = undefined :: {inet:ip_address(), inet:port_number()},
+
+ %% Client certificate (TLS only).
+ cert :: undefined | binary(),
+
timer = undefined :: undefined | reference(),
%% Identifier for the stream currently being read (or waiting to be received).
@@ -115,16 +121,36 @@
-spec init(pid(), ranch:ref(), inet:socket(), module(), cowboy:opts()) -> ok.
init(Parent, Ref, Socket, Transport, Opts) ->
- case Transport:peername(Socket) of
- {ok, Peer} ->
+ Peer0 = Transport:peername(Socket),
+ Sock0 = Transport:sockname(Socket),
+ Cert1 = case Transport:name() of
+ ssl ->
+ case ssl:peercert(Socket) of
+ {error, no_peercert} ->
+ {ok, undefined};
+ Cert0 ->
+ Cert0
+ end;
+ _ ->
+ {ok, undefined}
+ end,
+ case {Peer0, Sock0, Cert1} of
+ {{ok, Peer}, {ok, Sock}, {ok, Cert}} ->
LastStreamID = maps:get(max_keepalive, Opts, 100),
before_loop(set_timeout(#state{
parent=Parent, ref=Ref, socket=Socket,
transport=Transport, opts=Opts,
- peer=Peer, last_streamid=LastStreamID}), <<>>);
- {error, Reason} ->
- %% Couldn't read the peer address; connection is gone.
- terminate(undefined, {socket_error, Reason, 'An error has occurred on the socket.'})
+ peer=Peer, sock=Sock, cert=Cert,
+ last_streamid=LastStreamID}), <<>>);
+ {{error, Reason}, _, _} ->
+ terminate(undefined, {socket_error, Reason,
+ 'A socket error occurred when retrieving the peer name.'});
+ {_, {error, Reason}, _} ->
+ terminate(undefined, {socket_error, Reason,
+ 'A socket error occurred when retrieving the sock name.'});
+ {_, _, {error, Reason}} ->
+ terminate(undefined, {socket_error, Reason,
+ 'A socket error occurred when retrieving the client TLS certificate.'})
end.
before_loop(State=#state{socket=Socket, transport=Transport}, Buffer) ->
@@ -559,8 +585,9 @@ default_port(_) -> 80.
%% End of request parsing.
-request(Buffer, State0=#state{ref=Ref, transport=Transport, peer=Peer, in_streamid=StreamID,
- in_state=PS=#ps_header{method=Method, path=Path, qs=Qs, version=Version}},
+request(Buffer, State0=#state{ref=Ref, transport=Transport, peer=Peer, sock=Sock, cert=Cert,
+ in_streamid=StreamID, in_state=
+ PS=#ps_header{method=Method, path=Path, qs=Qs, version=Version}},
Headers, Host, Port) ->
Scheme = case Transport:secure() of
true -> <<"https">>;
@@ -589,6 +616,8 @@ request(Buffer, State0=#state{ref=Ref, transport=Transport, peer=Peer, in_stream
pid => self(),
streamid => StreamID,
peer => Peer,
+ sock => Sock,
+ cert => Cert,
method => Method,
scheme => Scheme,
host => Host,
@@ -644,11 +673,12 @@ is_http2_upgrade(_, _) ->
%% Prior knowledge upgrade, without an HTTP/1.1 request.
http2_upgrade(State=#state{parent=Parent, ref=Ref, socket=Socket, transport=Transport,
- opts=Opts, peer=Peer}, Buffer) ->
+ opts=Opts, peer=Peer, sock=Sock, cert=Cert}, Buffer) ->
case Transport:secure() of
false ->
_ = cancel_timeout(State),
- cowboy_http2:init(Parent, Ref, Socket, Transport, Opts, Peer, Buffer);
+ cowboy_http2:init(Parent, Ref, Socket, Transport, Opts,
+ Peer, Sock, Cert, Buffer);
true ->
error_terminate(400, State, {connection_error, protocol_error,
'Clients that support HTTP/2 over TLS MUST use ALPN. (RFC7540 3.4)'})
@@ -656,7 +686,7 @@ http2_upgrade(State=#state{parent=Parent, ref=Ref, socket=Socket, transport=Tran
%% Upgrade via an HTTP/1.1 request.
http2_upgrade(State=#state{parent=Parent, ref=Ref, socket=Socket, transport=Transport,
- opts=Opts, peer=Peer}, Buffer, HTTP2Settings, Req) ->
+ opts=Opts, peer=Peer, sock=Sock, cert=Cert}, Buffer, HTTP2Settings, Req) ->
%% @todo
%% However if the client sent a body, we need to read the body in full
%% and if we can't do that, return a 413 response. Some options are in order.
@@ -664,7 +694,8 @@ http2_upgrade(State=#state{parent=Parent, ref=Ref, socket=Socket, transport=Tran
try cow_http_hd:parse_http2_settings(HTTP2Settings) of
Settings ->
_ = cancel_timeout(State),
- cowboy_http2:init(Parent, Ref, Socket, Transport, Opts, Peer, Buffer, Settings, Req)
+ cowboy_http2:init(Parent, Ref, Socket, Transport, Opts,
+ Peer, Sock, Cert, Buffer, Settings, Req)
catch _:_ ->
error_terminate(400, State, {connection_error, protocol_error,
'The HTTP2-Settings header must contain a base64 SETTINGS payload. (RFC7540 3.2, RFC7540 3.2.1)'})
diff --git a/src/cowboy_http2.erl b/src/cowboy_http2.erl
index e57f02c..d863d1a 100644
--- a/src/cowboy_http2.erl
+++ b/src/cowboy_http2.erl
@@ -15,8 +15,8 @@
-module(cowboy_http2).
-export([init/5]).
--export([init/7]).
-export([init/9]).
+-export([init/11]).
-export([system_continue/3]).
-export([system_terminate/4]).
@@ -64,6 +64,12 @@
%% Remote address and port for the connection.
peer = undefined :: {inet:ip_address(), inet:port_number()},
+ %% Local address and port for the connection.
+ sock = undefined :: {inet:ip_address(), inet:port_number()},
+
+ %% Client certificate (TLS only).
+ cert :: undefined | binary(),
+
%% Settings are separate for each endpoint. In addition, settings
%% must be acknowledged before they can be expected to be applied.
%%
@@ -123,19 +129,39 @@
-spec init(pid(), ranch:ref(), inet:socket(), module(), cowboy:opts()) -> ok.
init(Parent, Ref, Socket, Transport, Opts) ->
- case Transport:peername(Socket) of
- {ok, Peer} ->
- init(Parent, Ref, Socket, Transport, Opts, Peer, <<>>);
- {error, Reason} ->
- %% Couldn't read the peer address; connection is gone.
- terminate(undefined, {socket_error, Reason, 'An error has occurred on the socket.'})
+ Peer0 = Transport:peername(Socket),
+ Sock0 = Transport:sockname(Socket),
+ Cert1 = case Transport:name() of
+ ssl ->
+ case ssl:peercert(Socket) of
+ {error, no_peercert} ->
+ {ok, undefined};
+ Cert0 ->
+ Cert0
+ end;
+ _ ->
+ {ok, undefined}
+ end,
+ case {Peer0, Sock0, Cert1} of
+ {{ok, Peer}, {ok, Sock}, {ok, Cert}} ->
+ init(Parent, Ref, Socket, Transport, Opts, Peer, Sock, Cert, <<>>);
+ {{error, Reason}, _, _} ->
+ terminate(undefined, {socket_error, Reason,
+ 'A socket error occurred when retrieving the peer name.'});
+ {_, {error, Reason}, _} ->
+ terminate(undefined, {socket_error, Reason,
+ 'A socket error occurred when retrieving the sock name.'});
+ {_, _, {error, Reason}} ->
+ terminate(undefined, {socket_error, Reason,
+ 'A socket error occurred when retrieving the client TLS certificate.'})
end.
-spec init(pid(), ranch:ref(), inet:socket(), module(), cowboy:opts(),
- {inet:ip_address(), inet:port_number()}, binary()) -> ok.
-init(Parent, Ref, Socket, Transport, Opts, Peer, Buffer) ->
+ {inet:ip_address(), inet:port_number()}, {inet:ip_address(), inet:port_number()},
+ binary() | undefined, binary()) -> ok.
+init(Parent, Ref, Socket, Transport, Opts, Peer, Sock, Cert, Buffer) ->
State = #state{parent=Parent, ref=Ref, socket=Socket,
- transport=Transport, opts=Opts, peer=Peer,
+ transport=Transport, opts=Opts, peer=Peer, sock=Sock, cert=Cert,
parse_state={preface, sequence, preface_timeout(Opts)}},
preface(State),
case Buffer of
@@ -145,10 +171,11 @@ init(Parent, Ref, Socket, Transport, Opts, Peer, Buffer) ->
%% @todo Add an argument for the request body.
-spec init(pid(), ranch:ref(), inet:socket(), module(), cowboy:opts(),
- {inet:ip_address(), inet:port_number()}, binary(), map() | undefined, cowboy_req:req()) -> ok.
-init(Parent, Ref, Socket, Transport, Opts, Peer, Buffer, _Settings, Req) ->
+ {inet:ip_address(), inet:port_number()}, {inet:ip_address(), inet:port_number()},
+ binary() | undefined, binary(), map() | undefined, cowboy_req:req()) -> ok.
+init(Parent, Ref, Socket, Transport, Opts, Peer, Sock, Cert, Buffer, _Settings, Req) ->
State0 = #state{parent=Parent, ref=Ref, socket=Socket,
- transport=Transport, opts=Opts, peer=Peer,
+ transport=Transport, opts=Opts, peer=Peer, sock=Sock, cert=Cert,
parse_state={preface, sequence, preface_timeout(Opts)}},
%% @todo Apply settings.
%% StreamID from HTTP/1.1 Upgrade requests is always 1.
@@ -720,9 +747,10 @@ stream_decode_init(State=#state{socket=Socket, transport=Transport,
'Error while trying to decode HPACK-encoded header block. (RFC7540 4.3)'})
end.
-stream_req_init(State=#state{ref=Ref, peer=Peer}, StreamID, IsFin, Headers0=#{
- <<":method">> := Method, <<":scheme">> := Scheme,
- <<":authority">> := Authority, <<":path">> := PathWithQs}) ->
+stream_req_init(State=#state{ref=Ref, peer=Peer, sock=Sock, cert=Cert},
+ StreamID, IsFin, Headers0=#{
+ <<":method">> := Method, <<":scheme">> := Scheme,
+ <<":authority">> := Authority, <<":path">> := PathWithQs}) ->
Headers = maps:without([<<":method">>, <<":scheme">>, <<":authority">>, <<":path">>], Headers0),
BodyLength = case Headers of
_ when IsFin =:= fin ->
@@ -746,6 +774,8 @@ stream_req_init(State=#state{ref=Ref, peer=Peer}, StreamID, IsFin, Headers0=#{
pid => self(),
streamid => StreamID,
peer => Peer,
+ sock => Sock,
+ cert => Cert,
method => Method,
scheme => Scheme,
host => Host,
diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl
index 43f7a44..1615c07 100644
--- a/src/cowboy_req.erl
+++ b/src/cowboy_req.erl
@@ -19,6 +19,8 @@
-export([method/1]).
-export([version/1]).
-export([peer/1]).
+-export([sock/1]).
+-export([cert/1]).
-export([scheme/1]).
-export([host/1]).
-export([host_info/1]).
@@ -151,6 +153,14 @@ version(#{version := Version}) ->
peer(#{peer := Peer}) ->
Peer.
+-spec sock(req()) -> {inet:ip_address(), inet:port_number()}.
+sock(#{sock := Sock}) ->
+ Sock.
+
+-spec cert(req()) -> binary() | undefined.
+cert(#{cert := Cert}) ->
+ Cert.
+
-spec scheme(req()) -> binary().
scheme(#{scheme := Scheme}) ->
Scheme.