Просмотр исходного кода

Add Websocket option validate_utf8

This allows disabling the UTF-8 validation check
for text and close frames.
Loïc Hoguin 5 лет назад
Родитель
Сommit
3e23aff1d1

+ 18 - 4
doc/src/manual/cowboy_websocket.asciidoc

@@ -151,11 +151,12 @@ Cowboy does it automatically for you.
 [source,erlang]
 ----
 opts() :: #{
-    compress => boolean(),
-    deflate_opts => cow_ws:deflate_opts()
-    idle_timeout => timeout(),
+    compress       => boolean(),
+    deflate_opts   => cow_ws:deflate_opts()
+    idle_timeout   => timeout(),
     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
 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
 
+* *2.7*: The option `validate_utf8` has been added.
 * *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 callback `websocket_terminate/3` was removed in favor of `terminate/3`.

+ 8 - 3
src/cowboy_websocket.erl

@@ -73,7 +73,8 @@
 	deflate_opts => cow_ws:deflate_opts(),
 	idle_timeout => timeout(),
 	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]).
 
@@ -91,7 +92,7 @@
 	hibernate = false :: boolean(),
 	frag_state = undefined :: cow_ws:frag_state(),
 	frag_buffer = <<>> :: binary(),
-	utf8_state = 0 :: cow_ws:utf8_state(),
+	utf8_state :: cow_ws:utf8_state(),
 	deflate = true :: boolean(),
 	extensions = #{} :: 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);
 		FilterFun -> FilterFun(Req0)
 	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
 		{ok, State, Req} ->
 			websocket_handshake(State, Req, HandlerState, Env);

+ 23 - 0
test/handlers/ws_dont_validate_utf8_h.erl

@@ -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}.

+ 12 - 1
test/ws_SUITE.erl

@@ -67,7 +67,8 @@ init_dispatch() ->
 			{"/ws_timeout_hibernate", ws_timeout_hibernate, []},
 			{"/ws_timeout_cancel", ws_timeout_cancel, []},
 			{"/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),
 	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) ->
 	doc("Client sends the first frame immediately with the handshake. "
 		"This is invalid according to the protocol but we still want "