0
Fork 0
mirror of https://github.com/ninenines/cowboy.git synced 2025-07-14 12:20:24 +00:00

Add Websocket option validate_utf8

This allows disabling the UTF-8 validation check
for text and close frames.
This commit is contained in:
Loïc Hoguin 2019-10-05 17:32:50 +02:00
parent c50d6aa09c
commit 3e23aff1d1
No known key found for this signature in database
GPG key ID: 8A9DF795F6FED764
4 changed files with 61 additions and 8 deletions

View file

@ -151,11 +151,12 @@ Cowboy does it automatically for you.
[source,erlang] [source,erlang]
---- ----
opts() :: #{ opts() :: #{
compress => boolean(), compress => boolean(),
deflate_opts => cow_ws:deflate_opts() deflate_opts => cow_ws:deflate_opts()
idle_timeout => timeout(), idle_timeout => timeout(),
max_frame_size => non_neg_integer() | infinity, max_frame_size => non_neg_integer() | infinity,
req_filter => fun((cowboy_req:req()) -> map()) req_filter => fun((cowboy_req:req()) -> map()),
validate_utf8 => boolean()
} }
---- ----
@ -209,8 +210,21 @@ given back in the `terminate/3` callback. By default
it keeps the method, version, URI components and peer it keeps the method, version, URI components and peer
information. information.
validate_utf8 (true)::
Whether Cowboy should verify that the payload of
`text` and `close` frames is valid UTF-8. This is
required by the protocol specification but in some
cases it may be more interesting to disable it in
order to save resources.
+
Note that `binary` frames do not have this UTF-8
requirement and are what should be used under
normal circumstances if necessary.
== Changelog == Changelog
* *2.7*: The option `validate_utf8` has been added.
* *2.6*: Deflate options can now be configured via `deflate_opts`. * *2.6*: Deflate options can now be configured via `deflate_opts`.
* *2.0*: The Req object is no longer passed to Websocket callbacks. * *2.0*: The Req object is no longer passed to Websocket callbacks.
* *2.0*: The callback `websocket_terminate/3` was removed in favor of `terminate/3`. * *2.0*: The callback `websocket_terminate/3` was removed in favor of `terminate/3`.

View file

@ -73,7 +73,8 @@
deflate_opts => cow_ws:deflate_opts(), deflate_opts => cow_ws:deflate_opts(),
idle_timeout => timeout(), idle_timeout => timeout(),
max_frame_size => non_neg_integer() | infinity, max_frame_size => non_neg_integer() | infinity,
req_filter => fun((cowboy_req:req()) -> map()) req_filter => fun((cowboy_req:req()) -> map()),
validate_utf8 => boolean()
}. }.
-export_type([opts/0]). -export_type([opts/0]).
@ -91,7 +92,7 @@
hibernate = false :: boolean(), hibernate = false :: boolean(),
frag_state = undefined :: cow_ws:frag_state(), frag_state = undefined :: cow_ws:frag_state(),
frag_buffer = <<>> :: binary(), frag_buffer = <<>> :: binary(),
utf8_state = 0 :: cow_ws:utf8_state(), utf8_state :: cow_ws:utf8_state(),
deflate = true :: boolean(), deflate = true :: boolean(),
extensions = #{} :: map(), extensions = #{} :: map(),
req = #{} :: map() req = #{} :: map()
@ -133,7 +134,11 @@ upgrade(Req0=#{version := Version}, Env, Handler, HandlerState, Opts) ->
undefined -> maps:with([method, version, scheme, host, port, path, qs, peer], Req0); undefined -> maps:with([method, version, scheme, host, port, path, qs, peer], Req0);
FilterFun -> FilterFun(Req0) FilterFun -> FilterFun(Req0)
end, end,
State0 = #state{opts=Opts, handler=Handler, req=FilteredReq}, Utf8State = case maps:get(validate_utf8, Opts, true) of
true -> 0;
false -> undefined
end,
State0 = #state{opts=Opts, handler=Handler, utf8_state=Utf8State, req=FilteredReq},
try websocket_upgrade(State0, Req0) of try websocket_upgrade(State0, Req0) of
{ok, State, Req} -> {ok, State, Req} ->
websocket_handshake(State, Req, HandlerState, Env); websocket_handshake(State, Req, HandlerState, Env);

View file

@ -0,0 +1,23 @@
%% This module disables UTF-8 validation.
-module(ws_dont_validate_utf8_h).
-behavior(cowboy_websocket).
-export([init/2]).
-export([websocket_handle/2]).
-export([websocket_info/2]).
init(Req, State) ->
{cowboy_websocket, Req, State, #{
validate_utf8 => false
}}.
websocket_handle({text, Data}, State) ->
{reply, {text, Data}, State};
websocket_handle({binary, Data}, State) ->
{reply, {binary, Data}, State};
websocket_handle(_, State) ->
{ok, State}.
websocket_info(_, State) ->
{ok, State}.

View file

@ -67,7 +67,8 @@ init_dispatch() ->
{"/ws_timeout_hibernate", ws_timeout_hibernate, []}, {"/ws_timeout_hibernate", ws_timeout_hibernate, []},
{"/ws_timeout_cancel", ws_timeout_cancel, []}, {"/ws_timeout_cancel", ws_timeout_cancel, []},
{"/ws_max_frame_size", ws_max_frame_size, []}, {"/ws_max_frame_size", ws_max_frame_size, []},
{"/ws_deflate_opts", ws_deflate_opts_h, []} {"/ws_deflate_opts", ws_deflate_opts_h, []},
{"/ws_dont_validate_utf8", ws_dont_validate_utf8_h, []}
]} ]}
]). ]).
@ -304,6 +305,16 @@ do_ws_deflate_opts_z(Path, Config) ->
{error, closed} = gen_tcp:recv(Socket, 0, 6000), {error, closed} = gen_tcp:recv(Socket, 0, 6000),
ok. ok.
ws_dont_validate_utf8(Config) ->
doc("Handler is configured with UTF-8 validation disabled."),
{ok, Socket, _} = do_handshake("/ws_dont_validate_utf8", Config),
%% Send an invalid UTF-8 text frame and receive it back.
Mask = 16#37fa213d,
MaskedInvalid = do_mask(<<255, 255, 255, 255>>, Mask, <<>>),
ok = gen_tcp:send(Socket, <<1:1, 0:3, 1:4, 1:1, 4:7, Mask:32, MaskedInvalid/binary>>),
{ok, <<1:1, 0:3, 1:4, 0:1, 4:7, 255, 255, 255, 255>>} = gen_tcp:recv(Socket, 0, 6000),
ok.
ws_first_frame_with_handshake(Config) -> ws_first_frame_with_handshake(Config) ->
doc("Client sends the first frame immediately with the handshake. " doc("Client sends the first frame immediately with the handshake. "
"This is invalid according to the protocol but we still want " "This is invalid according to the protocol but we still want "