aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cow_cookie.erl2
-rw-r--r--src/cow_date.erl6
-rw-r--r--src/cow_http.erl6
-rw-r--r--src/cow_http_hd.erl66
-rw-r--r--src/cow_http_te.erl7
-rw-r--r--src/cow_mimetypes.erl2
-rw-r--r--src/cow_multipart.erl19
-rw-r--r--src/cow_qs.erl10
-rw-r--r--src/cow_spdy.erl2
-rw-r--r--src/cow_ws.erl473
-rw-r--r--src/cowlib.app.src4
11 files changed, 396 insertions, 201 deletions
diff --git a/src/cow_cookie.erl b/src/cow_cookie.erl
index 02df65e..150efeb 100644
--- a/src/cow_cookie.erl
+++ b/src/cow_cookie.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2013-2014, Loïc Hoguin <[email protected]>
+%% Copyright (c) 2013-2015, Loïc Hoguin <[email protected]>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
diff --git a/src/cow_date.erl b/src/cow_date.erl
index 1e54090..b805aec 100644
--- a/src/cow_date.erl
+++ b/src/cow_date.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2013-2014, Loïc Hoguin <[email protected]>
+%% Copyright (c) 2013-2015, Loïc Hoguin <[email protected]>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
@@ -182,9 +182,7 @@ http_date_test_() ->
{<<"Sun Nov 6 08:49:37 1994">>, {{1994, 11, 6}, {8, 49, 37}}}
],
[{V, fun() -> R = http_date(V) end} || {V, R} <- Tests].
--endif.
--ifdef(PERF).
horse_http_date_fixdate() ->
horse:repeat(200000,
http_date(<<"Sun, 06 Nov 1994 08:49:37 GMT">>)
@@ -221,9 +219,7 @@ rfc2109_test_() ->
{<<"Sun, 01-Jan-2012 00:00:00 GMT">>, {{2012, 1, 1}, { 0, 0, 0}}}
],
[{R, fun() -> R = rfc2109(D) end} || {R, D} <- Tests].
--endif.
--ifdef(PERF).
horse_rfc2019_20130101_000000() ->
horse:repeat(100000,
rfc2109({{2013, 1, 1}, {0, 0, 0}})
diff --git a/src/cow_http.erl b/src/cow_http.erl
index 60f3faa..8f2ae92 100644
--- a/src/cow_http.erl
+++ b/src/cow_http.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2013-2014, Loïc Hoguin <[email protected]>
+%% Copyright (c) 2013-2015, Loïc Hoguin <[email protected]>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
@@ -82,9 +82,7 @@ parse_status_line_error_test_() ->
],
[{V, fun() -> {'EXIT', _} = (catch parse_status_line(V)) end}
|| V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_status_line_200() ->
horse:repeat(200000,
parse_status_line(<<"HTTP/1.1 200 OK\r\n">>)
@@ -184,9 +182,7 @@ parse_headers_error_test_() ->
],
[{V, fun() -> {'EXIT', _} = (catch parse_headers(V)) end}
|| V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_headers() ->
horse:repeat(50000,
parse_headers(<<"Server: Erlang/R17\r\n"
diff --git a/src/cow_http_hd.erl b/src/cow_http_hd.erl
index b54ade6..e47d80d 100644
--- a/src/cow_http_hd.erl
+++ b/src/cow_http_hd.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2014, Loïc Hoguin <[email protected]>
+%% Copyright (c) 2014-2015, Loïc Hoguin <[email protected]>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
@@ -386,9 +386,7 @@ parse_accept_error_test_() ->
<<"audio/basic;t=\"zero \\", 0, " woo\"">>
],
[{V, fun() -> {'EXIT', _} = (catch parse_accept(V)) end} || V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_accept() ->
horse:repeat(20000,
parse_accept(<<"text/*;q=0.3, text/html;q=0.7, text/html;level=1, "
@@ -480,9 +478,7 @@ parse_accept_charset_error_test_() ->
<<>>
],
[{V, fun() -> {'EXIT', _} = (catch parse_accept_charset(V)) end} || V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_accept_charset() ->
horse:repeat(20000,
parse_accept_charset(<<"iso-8859-5, unicode-1-1;q=0.8">>)
@@ -538,9 +534,7 @@ parse_accept_encoding_test_() ->
]}
],
[{V, fun() -> R = parse_accept_encoding(V) end} || {V, R} <- Tests].
--endif.
--ifdef(PERF).
horse_parse_accept_encoding() ->
horse:repeat(20000,
parse_accept_encoding(<<"gzip;q=1.0, identity; q=0.5, *;q=0">>)
@@ -657,9 +651,7 @@ parse_accept_language_error_test_() ->
<<"419-en-us">>
],
[{V, fun() -> {'EXIT', _} = (catch parse_accept_language(V)) end} || V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_accept_language() ->
horse:repeat(20000,
parse_accept_language(<<"da, en-gb;q=0.8, en;q=0.7">>)
@@ -688,9 +680,7 @@ parse_accept_ranges_error_test_() ->
<<>>
],
[{V, fun() -> {'EXIT', _} = (catch parse_accept_ranges(V)) end} || V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_accept_ranges_none() ->
horse:repeat(200000,
parse_accept_ranges(<<"none">>)
@@ -764,9 +754,7 @@ parse_allow_test_() ->
{<<"GET, HEAD, PUT">>, [<<"GET">>, <<"HEAD">>, <<"PUT">>]}
],
[{V, fun() -> R = parse_allow(V) end} || {V, R} <- Tests].
--endif.
--ifdef(PERF).
horse_parse_allow() ->
horse:repeat(200000,
parse_allow(<<"GET, HEAD, PUT">>)
@@ -863,9 +851,7 @@ parse_authorization_test_() ->
{<<"opaque">>, <<"5ccc069c403ebaf9f0171e9517f40e41">>}]}}
],
[{V, fun() -> R = parse_authorization(V) end} || {V, R} <- Tests].
--endif.
--ifdef(PERF).
horse_parse_authorization_basic() ->
horse:repeat(20000,
parse_authorization(<<"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==">>)
@@ -1014,9 +1000,7 @@ parse_cache_control_error_test_() ->
<<>>
],
[{V, fun() -> {'EXIT', _} = (catch parse_cache_control(V)) end} || V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_cache_control_no_cache() ->
horse:repeat(200000,
parse_cache_control(<<"no-cache">>)
@@ -1078,9 +1062,7 @@ parse_connection_error_test_() ->
<<>>
],
[{V, fun() -> {'EXIT', _} = (catch parse_connection(V)) end} || V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_connection_close() ->
horse:repeat(200000,
parse_connection(<<"close">>)
@@ -1115,9 +1097,7 @@ parse_content_encoding_error_test_() ->
<<>>
],
[{V, fun() -> {'EXIT', _} = (catch parse_content_encoding(V)) end} || V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_content_encoding() ->
horse:repeat(200000,
parse_content_encoding(<<"gzip">>)
@@ -1403,9 +1383,7 @@ parse_content_language_error_test_() ->
<<>>
],
[{V, fun() -> {'EXIT', _} = (catch parse_content_language(V)) end} || V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_content_language() ->
horse:repeat(100000,
parse_content_language(<<"fr, en-US, es-419, az-Arab, x-pig-latin, man-Nkoo-GN">>)
@@ -1449,9 +1427,7 @@ parse_content_length_error_test_() ->
<<"4.17">>
],
[{V, fun() -> {'EXIT', _} = (catch parse_content_length(V)) end} || V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_content_length_zero() ->
horse:repeat(100000,
parse_content_length(<<"0">>)
@@ -1551,9 +1527,7 @@ parse_content_range_error_test_() ->
<<>>
],
[{V, fun() -> {'EXIT', _} = (catch parse_content_range(V)) end} || V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_content_range_bytes() ->
horse:repeat(200000,
parse_content_range(<<"bytes 21010-47021/47022">>)
@@ -1666,9 +1640,7 @@ parse_content_type_test_() ->
]}}
],
[{V, fun() -> R = parse_content_type(V) end} || {V, R} <- Tests].
--endif.
--ifdef(PERF).
horse_parse_content_type() ->
horse:repeat(200000,
parse_content_type(<<"text/html;charset=utf-8">>)
@@ -1739,9 +1711,7 @@ parse_etag_error_test_() ->
<<"W/">>
],
[{V, fun() -> {'EXIT', _} = (catch parse_etag(V)) end} || V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_etag() ->
horse:repeat(200000,
parse_etag(<<"W/\"xyzzy\"">>)
@@ -1789,9 +1759,7 @@ parse_expect_error_test_() ->
<<"Cookies">>
],
[{V, fun() -> {'EXIT', _} = (catch parse_expect(V)) end} || V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_expect() ->
horse:repeat(200000,
parse_expect(<<"100-continue">>)
@@ -1821,9 +1789,7 @@ parse_expires_test_() ->
{<<"Thu, 01 Dec 1994 16:00:00 GMT">>, {{1994, 12, 1}, {16, 0, 0}}}
],
[{V, fun() -> R = parse_expires(V) end} || {V, R} <- Tests].
--endif.
--ifdef(PERF).
horse_parse_expires_0() ->
horse:repeat(200000,
parse_expires(<<"0">>)
@@ -1897,9 +1863,7 @@ parse_host_test_() ->
{<<"[::ffff:192.0.2.1]">>, {<<"[::ffff:192.0.2.1]">>, undefined}}
],
[{V, fun() -> R = parse_host(V) end} || {V, R} <- Tests].
--endif.
--ifdef(PERF).
horse_parse_host_blue_example_org() ->
horse:repeat(200000,
parse_host(<<"blue.example.org:8080">>)
@@ -1966,9 +1930,7 @@ parse_if_match_error_test_() ->
<<>>
],
[{V, fun() -> {'EXIT', _} = (catch parse_if_match(V)) end} || V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_if_match() ->
horse:repeat(200000,
parse_if_match(<<"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\"">>)
@@ -2015,9 +1977,7 @@ parse_if_none_match_error_test_() ->
<<>>
],
[{V, fun() -> {'EXIT', _} = (catch parse_if_none_match(V)) end} || V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_if_none_match() ->
horse:repeat(200000,
parse_if_none_match(<<"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\"">>)
@@ -2048,9 +2008,7 @@ parse_if_range_error_test_() ->
<<>>
],
[{V, fun() -> {'EXIT', _} = (catch parse_if_range(V)) end} || V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_if_range_etag() ->
horse:repeat(200000,
parse_if_range(<<"\"xyzzy\"">>)
@@ -2255,9 +2213,7 @@ parse_range_error_test_() ->
<<>>
],
[{V, fun() -> {'EXIT', _} = (catch parse_range(V)) end} || V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_range_first_last() ->
horse:repeat(200000,
parse_range(<<"bytes=500-999">>)
@@ -2307,9 +2263,7 @@ parse_retry_after_error_test_() ->
<<>>
],
[{V, fun() -> {'EXIT', _} = (catch parse_retry_after(V)) end} || V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_retry_after_date() ->
horse:repeat(200000,
parse_retry_after(<<"Fri, 31 Dec 1999 23:59:59 GMT">>)
@@ -2417,9 +2371,7 @@ parse_sec_websocket_extensions_error_test_() ->
],
[{V, fun() -> {'EXIT', _} = (catch parse_sec_websocket_extensions(V)) end}
|| V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_sec_websocket_extensions() ->
horse:repeat(200000,
parse_sec_websocket_extensions(<<"mux; max-channels=4; flow-control, deflate-stream">>)
@@ -2456,9 +2408,7 @@ parse_sec_websocket_protocol_req_error_test_() ->
],
[{V, fun() -> {'EXIT', _} = (catch parse_sec_websocket_protocol_req(V)) end}
|| V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_sec_websocket_protocol_req() ->
horse:repeat(200000,
parse_sec_websocket_protocol_req(<<"chat, superchat">>)
@@ -2494,9 +2444,7 @@ parse_sec_websocket_protocol_resp_error_test_() ->
],
[{V, fun() -> {'EXIT', _} = (catch parse_sec_websocket_protocol_resp(V)) end}
|| V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_sec_websocket_protocol_resp() ->
horse:repeat(200000,
parse_sec_websocket_protocol_resp(<<"chat">>)
@@ -2533,9 +2481,7 @@ parse_sec_websocket_version_req_error_test_() ->
],
[{V, fun() -> {'EXIT', _} = (catch parse_sec_websocket_version_req(V)) end}
|| V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_sec_websocket_version_req_13() ->
horse:repeat(200000,
parse_sec_websocket_version_req(<<"13">>)
@@ -2591,9 +2537,7 @@ parse_sec_websocket_version_resp_error_test_() ->
],
[{V, fun() -> {'EXIT', _} = (catch parse_sec_websocket_version_resp(V)) end}
|| V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_sec_websocket_version_resp() ->
horse:repeat(200000,
parse_sec_websocket_version_resp(<<"13, 8, 7">>)
@@ -2691,9 +2635,7 @@ parse_te_test_() ->
{<<"trailers, deflate;q=0.5">>, {trailers, [{<<"deflate">>, 500}]}}
],
[{V, fun() -> R = parse_te(V) end} || {V, R} <- Tests].
--endif.
--ifdef(PERF).
horse_parse_te() ->
horse:repeat(200000,
parse_te(<<"trailers, deflate;q=0.5">>)
@@ -2718,9 +2660,7 @@ parse_trailer_error_test_() ->
<<>>
],
[{V, fun() -> {'EXIT', _} = (catch parse_trailer(V)) end} || V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_trailer() ->
horse:repeat(200000,
parse_trailer(<<"Date, Content-MD5">>)
@@ -2769,9 +2709,7 @@ parse_transfer_encoding_error_test_() ->
],
[{V, fun() -> {'EXIT', _} = (catch parse_transfer_encoding(V)) end}
|| V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_transfer_encoding_chunked() ->
horse:repeat(200000,
parse_transfer_encoding(<<"chunked">>)
@@ -3018,9 +2956,7 @@ parse_www_authenticate_error_test_() ->
<<>>
],
[{V, fun() -> {'EXIT', _} = (catch parse_www_authenticate(V)) end} || V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_www_authenticate() ->
horse:repeat(200000,
parse_www_authenticate(<<"Newauth realm=\"apps\", type=1, title=\"Login to \\\"apps\\\"\", Basic realm=\"simple\"">>)
diff --git a/src/cow_http_te.erl b/src/cow_http_te.erl
index 5ab71f4..1e7b43f 100644
--- a/src/cow_http_te.erl
+++ b/src/cow_http_te.erl
@@ -34,7 +34,7 @@
| {done, Data::binary(), TotalLen::non_neg_integer(), Rest::binary()}.
-export_type([decode_ret/0]).
--ifdef(EXTRA).
+-ifdef(TEST).
dripfeed(<< C, Rest/bits >>, Acc, State, F) ->
case F(<< Acc/binary, C >>, State) of
more ->
@@ -92,11 +92,8 @@ stream_identity_parts_test() ->
{done, << 0:7992 >>, 2999, <<>>}
= stream_identity(<< 0:7992 >>, S2),
ok.
--endif.
--ifdef(PERF).
%% Using the same data as the chunked one for comparison.
-
horse_stream_identity() ->
horse:repeat(10000,
stream_identity(<<
@@ -296,9 +293,7 @@ stream_chunked_error_test_() ->
[{lists:flatten(io_lib:format("value ~p state ~p", [V, S])),
fun() -> {'EXIT', _} = (catch stream_chunked(V, S)) end}
|| {V, S} <- Tests].
--endif.
--ifdef(PERF).
horse_stream_chunked() ->
horse:repeat(10000,
stream_chunked(<<
diff --git a/src/cow_mimetypes.erl b/src/cow_mimetypes.erl
index 69284e1..58585b9 100644
--- a/src/cow_mimetypes.erl
+++ b/src/cow_mimetypes.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2013-2014, Loïc Hoguin <[email protected]>
+%% Copyright (c) 2013-2015, Loïc Hoguin <[email protected]>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
diff --git a/src/cow_multipart.erl b/src/cow_multipart.erl
index 284c597..276a689 100644
--- a/src/cow_multipart.erl
+++ b/src/cow_multipart.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2014, Loïc Hoguin <[email protected]>
+%% Copyright (c) 2014-2015, Loïc Hoguin <[email protected]>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
@@ -98,7 +98,7 @@
>>).
-define(TEST4_BOUNDARY, <<"boundary">>).
-%% RFC 2046, Section 5.1.1:
+%% RFC 2046, Section 5.1.1
-define(TEST5_MIME, <<
"This is the preamble. It is to be ignored, though it\r\n"
"is a handy place for composition agents to include an\r\n"
@@ -203,8 +203,7 @@ skip_preamble(Stream, Boundary) ->
end.
before_parse_headers(<< "\r\n\r\n", Stream/bits >>) ->
- %% This indicates that there are no headers, so we can abort
- %% immediately.
+ %% This indicates that there are no headers, so we can abort immediately.
{ok, [], Stream};
before_parse_headers(<< "\r\n", Stream/bits >>) ->
%% There is a line break right after the boundary, skip it.
@@ -364,7 +363,7 @@ parse_epilogue_crlf_test() ->
ok.
parse_rfc2046_test() ->
- %% The following is an included in RFC 2046, Section 5.1.1.
+ %% The following is an example included in RFC 2046, Section 5.1.1.
Body1 = <<"This is implicitly typed plain US-ASCII text.\r\n"
"It does NOT end with a linebreak.">>,
Body2 = <<"This is explicitly typed plain US-ASCII text.\r\n"
@@ -400,9 +399,7 @@ parse_partial_test() ->
{ok, <<"boundary">>, <<"\r\n--">>}
= parse_body(<<"boundary\r\n--">>, <<"boundary">>),
ok.
--endif.
--ifdef(PERF).
perf_parse_multipart(Stream, Boundary) ->
case parse_headers(Stream, Boundary) of
{ok, _, Rest} ->
@@ -499,9 +496,7 @@ identity_test() ->
{done, Body2, M6} = parse_body(M5, B),
{done, Epilogue} = parse_headers(M6, B),
ok.
--endif.
--ifdef(PERF).
perf_build_multipart() ->
B = boundary(),
[
@@ -613,9 +608,7 @@ parse_content_disposition_test_() ->
{<<"file">>, [{<<"filename">>, <<"file2.gif">>}]}}
],
[{V, fun() -> R = parse_content_disposition(V) end} || {V, R} <- Tests].
--endif.
--ifdef(PERF).
horse_parse_content_disposition_attachment() ->
horse:repeat(100000,
parse_content_disposition(<<"attachment; filename=genome.jpeg;"
@@ -655,9 +648,7 @@ parse_content_transfer_encoding_test_() ->
],
[{V, fun() -> R = parse_content_transfer_encoding(V) end}
|| {V, R} <- Tests].
--endif.
--ifdef(PERF).
horse_parse_content_transfer_encoding() ->
horse:repeat(100000,
parse_content_transfer_encoding(<<"QUOTED-PRINTABLE">>)
@@ -718,9 +709,7 @@ parse_content_type_test_() ->
],
[{V, fun() -> R = parse_content_type(V) end}
|| {V, R} <- Tests].
--endif.
--ifdef(PERF).
horse_parse_content_type_zero() ->
horse:repeat(100000,
parse_content_type(<<"text/plain">>)
diff --git a/src/cow_qs.erl b/src/cow_qs.erl
index 413562b..33d385b 100644
--- a/src/cow_qs.erl
+++ b/src/cow_qs.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2013-2014, Loïc Hoguin <[email protected]>
+%% Copyright (c) 2013-2015, Loïc Hoguin <[email protected]>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
@@ -126,9 +126,7 @@ parse_qs_identity_test_() ->
"b-sid=521732&ortb-xt=IAB3&ortb-ugc=">>
],
[{V, fun() -> V = qs(parse_qs(V)) end} || V <- Tests].
--endif.
--ifdef(PERF).
horse_parse_qs_shorter() ->
horse:repeat(20000,
parse_qs(<<"hl=en&q=erlang%20cowboy">>)
@@ -310,9 +308,7 @@ qs_identity_test_() ->
[{lists:flatten(io_lib:format("~p", [V])), fun() ->
V = parse_qs(qs(V))
end} || V <- Tests].
--endif.
--ifdef(PERF).
horse_qs_shorter() ->
horse:repeat(20000, qs(?QS_SHORTER)).
@@ -395,9 +391,7 @@ urldecode_identity_test_() ->
"%BE%8B%E3%80%9C">>
],
[{V, fun() -> V = urlencode(urldecode(V)) end} || V <- Tests].
--endif.
--ifdef(PERF).
horse_urldecode() ->
horse:repeat(100000,
urldecode(<<"nothingnothingnothingnothing">>)
@@ -544,9 +538,7 @@ urlencode_identity_test_() ->
129,153,227,130,139,230,151,139,229,190,139,227,128,156>>
],
[{V, fun() -> V = urldecode(urlencode(V)) end} || V <- Tests].
--endif.
--ifdef(PERF).
horse_urlencode() ->
horse:repeat(100000,
urlencode(<<"nothingnothingnothingnothing">>)
diff --git a/src/cow_spdy.erl b/src/cow_spdy.erl
index 59c1ba4..ac7f0fc 100644
--- a/src/cow_spdy.erl
+++ b/src/cow_spdy.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2013-2014, Loïc Hoguin <[email protected]>
+%% Copyright (c) 2013-2015, Loïc Hoguin <[email protected]>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
diff --git a/src/cow_ws.erl b/src/cow_ws.erl
index de7d0b5..c89c17a 100644
--- a/src/cow_ws.erl
+++ b/src/cow_ws.erl
@@ -14,27 +14,206 @@
-module(cow_ws).
+-export([key/0]).
+-export([encode_key/1]).
+
+-export([negotiate_permessage_deflate/3]).
+-export([negotiate_x_webkit_deflate_frame/3]).
+
+-export([validate_permessage_deflate/3]).
+
-export([parse_header/3]).
--export([parse_close_code/2]).
-export([parse_payload/9]).
+-export([make_frame/4]).
+
-export([frame/2]).
+-export([masked_frame/2]).
-type close_code() :: 1000..1003 | 1006..1011 | 3000..4999.
-export_type([close_code/0]).
--type frag_state() :: undefined | {fin | nofin, text | binary}.
+-type extensions() :: map().
+-export_type([extensions/0]).
+
+-type frag_state() :: undefined | {fin | nofin, text | binary, rsv()}.
-export_type([frag_state/0]).
-type frame() :: close | ping | pong
| {text | binary | close | ping | pong, iodata()}
- | {close, close_code(), iodata()}.
+ | {close, close_code(), iodata()}
+ | {fragment, fin | nofin, text | binary | continuation, iodata()}.
-export_type([frame/0]).
--type extensions() :: map().
-type frame_type() :: fragment | text | binary | close | ping | pong.
+-export_type([frame_type/0]).
+
-type mask_key() :: undefined | 0..16#ffffffff.
+-export_type([mask_key/0]).
+
-type rsv() :: <<_:3>>.
--type utf8_state() :: <<>> | <<_:8>> | <<_:16>> | <<_:24>>.
+-export_type([rsv/0]).
+
+-type utf8_state() :: 0..8.
+-export_type([utf8_state/0]).
+
+%% @doc Generate a key for the Websocket handshake request.
+
+-spec key() -> binary().
+key() ->
+ base64:encode(crypto:rand_bytes(16)).
+
+%% @doc Encode the key into the accept value for the Websocket handshake response.
+
+-spec encode_key(binary()) -> binary().
+encode_key(Key) ->
+ base64:encode(crypto:hash(sha, [Key, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"])).
+
+%% @doc Negotiate the permessage-deflate extension.
+
+%% Ignore if deflate already negotiated.
+negotiate_permessage_deflate(_, #{deflate := _}, _) ->
+ ignore;
+negotiate_permessage_deflate(Params, Extensions, Opts) ->
+ case lists:usort(Params) of
+ %% Ignore if multiple parameters with the same name.
+ Params2 when length(Params) =/= length(Params2) ->
+ ignore;
+ Params2 ->
+ %% @todo Might want to make these configurable defaults.
+ case parse_request_permessage_deflate_params(Params2, 15, takeover, 15, takeover, []) of
+ ignore ->
+ ignore;
+ {ClientWindowBits, ClientTakeOver, ServerWindowBits, ServerTakeOver, RespParams} ->
+ {Inflate, Deflate} = init_permessage_deflate(ClientWindowBits, ServerWindowBits, Opts),
+ {ok, [<<"permessage-deflate">>, RespParams],
+ Extensions#{
+ deflate => Deflate,
+ deflate_takeover => ServerTakeOver,
+ inflate => Inflate,
+ inflate_takeover => ClientTakeOver}}
+ end
+ end.
+
+parse_request_permessage_deflate_params([], CB, CTO, SB, STO, RespParams) ->
+ {CB, CTO, SB, STO, RespParams};
+parse_request_permessage_deflate_params([<<"client_max_window_bits">>|Tail], CB, CTO, SB, STO, RespParams) ->
+ parse_request_permessage_deflate_params(Tail, CB, CTO, SB, STO,
+ [<<"; ">>, <<"client_max_window_bits=">>, integer_to_binary(CB)|RespParams]);
+parse_request_permessage_deflate_params([{<<"client_max_window_bits">>, Max}|Tail], _, CTO, SB, STO, RespParams) ->
+ case parse_max_window_bits(Max) of
+ error ->
+ ignore;
+ CB ->
+ parse_request_permessage_deflate_params(Tail, CB, CTO, SB, STO,
+ [<<"; ">>, <<"client_max_window_bits=">>, Max|RespParams])
+ end;
+parse_request_permessage_deflate_params([<<"client_no_context_takeover">>|Tail], CB, _, SB, STO, RespParams) ->
+ parse_request_permessage_deflate_params(Tail, CB, no_takeover, SB, STO, [<<"; ">>, <<"client_no_context_takeover">>|RespParams]);
+parse_request_permessage_deflate_params([{<<"server_max_window_bits">>, Max}|Tail], CB, CTO, _, STO, RespParams) ->
+ case parse_max_window_bits(Max) of
+ error ->
+ ignore;
+ SB ->
+ parse_request_permessage_deflate_params(Tail, CB, CTO, SB, STO,
+ [<<"; ">>, <<"server_max_window_bits=">>, Max|RespParams])
+ end;
+parse_request_permessage_deflate_params([<<"server_no_context_takeover">>|Tail], CB, CTO, SB, _, RespParams) ->
+ parse_request_permessage_deflate_params(Tail, CB, CTO, SB, no_takeover, [<<"; ">>, <<"server_no_context_takeover">>|RespParams]);
+%% Ignore if unknown parameter; ignore if parameter with invalid or missing value.
+parse_request_permessage_deflate_params(_, _, _, _, _, _) ->
+ ignore.
+
+parse_max_window_bits(<<"8">>) -> 8;
+parse_max_window_bits(<<"9">>) -> 9;
+parse_max_window_bits(<<"10">>) -> 10;
+parse_max_window_bits(<<"11">>) -> 11;
+parse_max_window_bits(<<"12">>) -> 12;
+parse_max_window_bits(<<"13">>) -> 13;
+parse_max_window_bits(<<"14">>) -> 14;
+parse_max_window_bits(<<"15">>) -> 15;
+parse_max_window_bits(_) -> error.
+
+% A negative WindowBits value indicates that zlib headers are not used.
+init_permessage_deflate(InflateWindowBits, DeflateWindowBits, Opts) ->
+ Inflate = zlib:open(),
+ ok = zlib:inflateInit(Inflate, -InflateWindowBits),
+ Deflate = zlib:open(),
+ %% @todo Remove this case .. of for OTP 18+ if PR https://github.com/erlang/otp/pull/633 gets merged.
+ DeflateWindowBits2 = case DeflateWindowBits of
+ 8 -> 9;
+ _ -> DeflateWindowBits
+ end,
+ ok = zlib:deflateInit(Deflate,
+ maps:get(level, Opts, best_compression),
+ deflated,
+ -DeflateWindowBits2,
+ maps:get(mem_level, Opts, 8),
+ maps:get(strategy, Opts, default)),
+ {Inflate, Deflate}.
+
+%% @doc Negotiate the x-webkit-deflate-frame extension.
+%%
+%% The implementation is very basic and none of the parameters
+%% are currently supported.
+
+negotiate_x_webkit_deflate_frame(_, #{deflate := _}, _) ->
+ ignore;
+negotiate_x_webkit_deflate_frame(_Params, Extensions, Opts) ->
+ % Since we are negotiating an unconstrained deflate-frame
+ % then we must be willing to accept frames using the
+ % maximum window size which is 2^15.
+ {Inflate, Deflate} = init_permessage_deflate(15, 15, Opts),
+ {ok, <<"x-webkit-deflate-frame">>,
+ Extensions#{
+ deflate => Deflate,
+ deflate_takeover => takeover,
+ inflate => Inflate,
+ inflate_takeover => takeover}}.
+
+%% @doc Validate the negotiated permessage-deflate extension.
+
+%% Error when more than one deflate extension was negotiated.
+validate_permessage_deflate(_, #{deflate := _}, _) ->
+ error;
+validate_permessage_deflate(Params, Extensions, Opts) ->
+ case lists:usort(Params) of
+ %% Error if multiple parameters with the same name.
+ Params2 when length(Params) =/= length(Params2) ->
+ error;
+ Params2 ->
+ %% @todo Might want to make some of these configurable defaults if at all possible.
+ case parse_response_permessage_deflate_params(Params2, 15, takeover, 15, takeover) of
+ error ->
+ error;
+ {ClientWindowBits, ClientTakeOver, ServerWindowBits, ServerTakeOver} ->
+ {Inflate, Deflate} = init_permessage_deflate(ServerWindowBits, ClientWindowBits, Opts),
+ {ok, Extensions#{
+ deflate => Deflate,
+ deflate_takeover => ClientTakeOver,
+ inflate => Inflate,
+ inflate_takeover => ServerTakeOver}}
+ end
+ end.
+
+parse_response_permessage_deflate_params([], CB, CTO, SB, STO) ->
+ {CB, CTO, SB, STO};
+parse_response_permessage_deflate_params([{<<"client_max_window_bits">>, Max}|Tail], _, CTO, SB, STO) ->
+ case parse_max_window_bits(Max) of
+ error -> error;
+ CB -> parse_response_permessage_deflate_params(Tail, CB, CTO, SB, STO)
+ end;
+parse_response_permessage_deflate_params([<<"client_no_context_takeover">>|Tail], CB, _, SB, STO) ->
+ parse_response_permessage_deflate_params(Tail, CB, no_takeover, SB, STO);
+parse_response_permessage_deflate_params([{<<"server_max_window_bits">>, Max}|Tail], CB, CTO, _, STO) ->
+ case parse_max_window_bits(Max) of
+ error -> error;
+ SB -> parse_response_permessage_deflate_params(Tail, CB, CTO, SB, STO)
+ end;
+parse_response_permessage_deflate_params([<<"server_no_context_takeover">>|Tail], CB, CTO, SB, _) ->
+ parse_response_permessage_deflate_params(Tail, CB, CTO, SB, no_takeover);
+%% Error if unknown parameter; error if parameter with invalid or missing value.
+parse_response_permessage_deflate_params(_, _, _, _, _) ->
+ error.
%% @doc Parse and validate the Websocket frame header.
%%
@@ -47,8 +226,8 @@
%% that defines meanings for non-zero values.
parse_header(<< _:1, Rsv:3, _/bits >>, Extensions, _) when Extensions =:= #{}, Rsv =/= 0 -> error;
%% Last 2 RSV bits MUST be 0 if deflate-frame extension is used.
-parse_header(<< _:2, 1:1, _/bits >>, #{deflate_frame := _}, _) -> error;
-parse_header(<< _:3, 1:1, _/bits >>, #{deflate_frame := _}, _) -> error;
+parse_header(<< _:2, 1:1, _/bits >>, #{deflate := _}, _) -> error;
+parse_header(<< _:3, 1:1, _/bits >>, #{deflate := _}, _) -> error;
%% Invalid opcode. Note that these opcodes may be used by extensions.
parse_header(<< _:4, 3:4, _/bits >>, _, _) -> error;
parse_header(<< _:4, 4:4, _/bits >>, _, _) -> error;
@@ -65,13 +244,13 @@ parse_header(<< 0:1, _:3, Opcode:4, _/bits >>, _, _) when Opcode >= 8 -> error;
%% A frame MUST NOT use the zero opcode unless fragmentation was initiated.
parse_header(<< _:4, 0:4, _/bits >>, _, undefined) -> error;
%% Non-control opcode when expecting control message or next fragment.
-parse_header(<< _:4, 1:4, _/bits >>, _, {_, _}) -> error;
-parse_header(<< _:4, 2:4, _/bits >>, _, {_, _}) -> error;
-parse_header(<< _:4, 3:4, _/bits >>, _, {_, _}) -> error;
-parse_header(<< _:4, 4:4, _/bits >>, _, {_, _}) -> error;
-parse_header(<< _:4, 5:4, _/bits >>, _, {_, _}) -> error;
-parse_header(<< _:4, 6:4, _/bits >>, _, {_, _}) -> error;
-parse_header(<< _:4, 7:4, _/bits >>, _, {_, _}) -> error;
+parse_header(<< _:4, 1:4, _/bits >>, _, {_, _, _}) -> error;
+parse_header(<< _:4, 2:4, _/bits >>, _, {_, _, _}) -> error;
+parse_header(<< _:4, 3:4, _/bits >>, _, {_, _, _}) -> error;
+parse_header(<< _:4, 4:4, _/bits >>, _, {_, _, _}) -> error;
+parse_header(<< _:4, 5:4, _/bits >>, _, {_, _, _}) -> error;
+parse_header(<< _:4, 6:4, _/bits >>, _, {_, _, _}) -> error;
+parse_header(<< _:4, 7:4, _/bits >>, _, {_, _, _}) -> error;
%% Close control frame length MUST be 0 or >= 2.
parse_header(<< _:4, 8:4, _:1, 1:7, _/bits >>, _, _) -> error;
%% Close control frame with incomplete close code. Need more data.
@@ -111,7 +290,7 @@ parse_header(Opcode, Fin, FragState, Rsv, Len, MaskKey, Rest) ->
0 -> fragment;
1 -> Type
end,
- {Type2, frag_state(Type, Fin, FragState), Rsv, Len, MaskKey, Rest}.
+ {Type2, frag_state(Type, Fin, Rsv, FragState), Rsv, Len, MaskKey, Rest}.
opcode_to_frame_type(0) -> fragment;
opcode_to_frame_type(1) -> text;
@@ -120,25 +299,10 @@ opcode_to_frame_type(8) -> close;
opcode_to_frame_type(9) -> ping;
opcode_to_frame_type(10) -> pong.
-frag_state(Type, 0, undefined) -> {nofin, Type};
-frag_state(fragment, 0, FragState = {nofin, _}) -> FragState;
-frag_state(fragment, 1, {nofin, Type}) -> {fin, Type};
-frag_state(_, 1, FragState) -> FragState.
-
-%% @doc Parse and validate the close frame's close code.
-%%
-%% The close code is part of the payload and must therefore be unmasked.
-
--spec parse_close_code(binary(), mask_key()) -> {ok, close_code(), binary()} | error.
-parse_close_code(<< MaskedCode:2/binary, Rest/bits >>, MaskKey) ->
- << Code:16 >> = unmask(MaskedCode, MaskKey, 0),
- if
- Code < 1000; Code =:= 1004; Code =:= 1005; Code =:= 1006;
- (Code > 1011) and (Code < 3000); Code > 4999 ->
- error;
- true ->
- {ok, Code, Rest}
- end.
+frag_state(Type, 0, Rsv, undefined) -> {nofin, Type, Rsv};
+frag_state(fragment, 0, _, FragState = {nofin, _, _}) -> FragState;
+frag_state(fragment, 1, _, {nofin, Type, Rsv}) -> {fin, Type, Rsv};
+frag_state(_, 1, _, FragState) -> FragState.
%% @doc Parse and validate the frame's payload.
%%
@@ -148,10 +312,46 @@ parse_close_code(<< MaskedCode:2/binary, Rest/bits >>, MaskKey) ->
-spec parse_payload(binary(), mask_key(), utf8_state(), non_neg_integer(),
frame_type(), non_neg_integer(), frag_state(), extensions(), rsv())
-> {ok, binary(), utf8_state(), binary()} | {more, binary(), utf8_state()} | error.
-parse_payload(Data, MaskKey, Utf8State, ParsedLen, Type, Len, FragState, #{deflate_frame := Inflate}, << 1:1, 0:2 >>) ->
+%% Empty last frame of compressed message.
+parse_payload(Data, _, Utf8State, _, _, 0, {fin, _, << 1:1, 0:2 >>},
+ #{inflate := Inflate, inflate_takeover := TakeOver}, _) ->
+ zlib:inflate(Inflate, << 0, 0, 255, 255 >>),
+ case TakeOver of
+ no_takeover -> zlib:inflateReset(Inflate);
+ takeover -> ok
+ end,
+ {ok, <<>>, Utf8State, Data};
+%% Compressed fragmented frame.
+parse_payload(Data, MaskKey, Utf8State, ParsedLen, Type, Len, FragState = {_, _, << 1:1, 0:2 >>},
+ #{inflate := Inflate, inflate_takeover := TakeOver}, _) ->
{Data2, Rest, Eof} = split_payload(Data, Len),
- Payload = inflate_frame(unmask(Data2, MaskKey, ParsedLen), Inflate, FragState, Eof),
+ Payload = inflate_frame(unmask(Data2, MaskKey, ParsedLen), Inflate, TakeOver, FragState, Eof),
validate_payload(Payload, Rest, Utf8State, ParsedLen, Type, FragState, Eof);
+%% Compressed frame.
+parse_payload(Data, MaskKey, Utf8State, ParsedLen, Type, Len, FragState,
+ #{inflate := Inflate, inflate_takeover := TakeOver}, << 1:1, 0:2 >>) when Type =:= text; Type =:= binary ->
+ {Data2, Rest, Eof} = split_payload(Data, Len),
+ Payload = inflate_frame(unmask(Data2, MaskKey, ParsedLen), Inflate, TakeOver, FragState, Eof),
+ validate_payload(Payload, Rest, Utf8State, ParsedLen, Type, FragState, Eof);
+%% Empty frame.
+parse_payload(Data, _, Utf8State = 0, 0, _, 0, _, _, _) ->
+ {ok, <<>>, Utf8State, Data};
+%% Start of close frame.
+parse_payload(Data, MaskKey, Utf8State, 0, Type = close, Len, FragState, _, << 0:3 >>) ->
+ {<< MaskedCode:2/binary, Data2/bits >>, Rest, Eof} = split_payload(Data, Len),
+ << CloseCode:16 >> = unmask(MaskedCode, MaskKey, 0),
+ case validate_close_code(CloseCode) of
+ ok ->
+ Payload = unmask(Data2, MaskKey, 2),
+ case validate_payload(Payload, Rest, Utf8State, 2, Type, FragState, Eof) of
+ {ok, _, Utf8State2, _} -> {ok, CloseCode, Payload, Utf8State2, Rest};
+ {more, _, Utf8State2} -> {more, CloseCode, Payload, Utf8State2};
+ Error -> Error
+ end;
+ error ->
+ {error, badframe}
+ end;
+%% Normal frame.
parse_payload(Data, MaskKey, Utf8State, ParsedLen, Type, Len, FragState, _, << 0:3 >>) ->
{Data2, Rest, Eof} = split_payload(Data, Len),
Payload = unmask(Data2, MaskKey, ParsedLen),
@@ -168,88 +368,125 @@ split_payload(Data, Len) ->
{Data2, Rest, true}
end.
+validate_close_code(Code) ->
+ if
+ Code < 1000 -> error;
+ Code =:= 1004 -> error;
+ Code =:= 1005 -> error;
+ Code =:= 1006 -> error;
+ Code > 1011, Code < 3000 -> error;
+ Code > 4999 -> error;
+ true -> ok
+ end.
+
+unmask(Data, undefined, _) ->
+ Data;
unmask(Data, MaskKey, 0) ->
- do_unmask(Data, MaskKey, <<>>);
+ mask(Data, MaskKey, <<>>);
%% We unmask on the fly so we need to continue from the right mask byte.
unmask(Data, MaskKey, UnmaskedLen) ->
Left = UnmaskedLen rem 4,
Right = 4 - Left,
MaskKey2 = (MaskKey bsl (Left * 8)) + (MaskKey bsr (Right * 8)),
- do_unmask(Data, MaskKey2, <<>>).
+ mask(Data, MaskKey2, <<>>).
-do_unmask(<<>>, _, Unmasked) ->
+mask(<<>>, _, Unmasked) ->
Unmasked;
-do_unmask(<< O:32, Rest/bits >>, MaskKey, Acc) ->
+mask(<< O:32, Rest/bits >>, MaskKey, Acc) ->
T = O bxor MaskKey,
- do_unmask(Rest, MaskKey, << Acc/binary, T:32 >>);
-do_unmask(<< O:24 >>, MaskKey, Acc) ->
+ mask(Rest, MaskKey, << Acc/binary, T:32 >>);
+mask(<< O:24 >>, MaskKey, Acc) ->
<< MaskKey2:24, _:8 >> = << MaskKey:32 >>,
T = O bxor MaskKey2,
<< Acc/binary, T:24 >>;
-do_unmask(<< O:16 >>, MaskKey, Acc) ->
+mask(<< O:16 >>, MaskKey, Acc) ->
<< MaskKey2:16, _:16 >> = << MaskKey:32 >>,
T = O bxor MaskKey2,
<< Acc/binary, T:16 >>;
-do_unmask(<< O:8 >>, MaskKey, Acc) ->
+mask(<< O:8 >>, MaskKey, Acc) ->
<< MaskKey2:8, _:24 >> = << MaskKey:32 >>,
T = O bxor MaskKey2,
<< Acc/binary, T:8 >>.
-%% @todo Try using iodata() and see if it improves anything.
-inflate_frame(Data, Inflate, fin, true) ->
- iolist_to_binary(zlib:inflate(Inflate, << Data/binary, 0, 0, 255, 255 >>));
-inflate_frame(Data, Inflate, _, _) ->
+inflate_frame(Data, Inflate, TakeOver, FragState, true)
+ when FragState =:= undefined; element(1, FragState) =:= fin ->
+ Data2 = zlib:inflate(Inflate, << Data/binary, 0, 0, 255, 255 >>),
+ case TakeOver of
+ no_takeover -> zlib:inflateReset(Inflate);
+ takeover -> ok
+ end,
+ iolist_to_binary(Data2);
+inflate_frame(Data, Inflate, _T, _F, _E) ->
iolist_to_binary(zlib:inflate(Inflate, Data)).
%% Text frames and close control frames MUST have a payload that is valid UTF-8.
validate_payload(Payload, Rest, Utf8State, _, Type, _, Eof) when Type =:= text; Type =:= close ->
- case validate_utf8(<< Utf8State/binary, Payload/binary >>) of
- false -> error;
- Utf8State when not Eof -> {more, Payload, Utf8State};
- <<>> when Eof -> {ok, Payload, <<>>, Rest};
- _ -> error
+ case validate_utf8(Payload, Utf8State) of
+ 1 -> {error, badencoding};
+ Utf8State2 when not Eof -> {more, Payload, Utf8State2};
+ 0 when Eof -> {ok, Payload, 0, Rest};
+ _ -> {error, badencoding}
end;
-validate_payload(Payload, Rest, Utf8State, _, fragment, {Fin, text}, Eof) ->
- case validate_utf8(<< Utf8State/binary, Payload/binary >>) of
- false -> error;
- <<>> when Eof -> {ok, Payload, <<>>, Rest};
+validate_payload(Payload, Rest, Utf8State, _, fragment, {Fin, text, _}, Eof) ->
+ case validate_utf8(Payload, Utf8State) of
+ 1 -> {error, badencoding};
+ 0 when Eof -> {ok, Payload, 0, Rest};
Utf8State2 when Eof, Fin =:= nofin -> {ok, Payload, Utf8State2, Rest};
Utf8State2 when not Eof -> {more, Payload, Utf8State2};
- _ -> error
+ _ -> {error, badencoding}
end;
validate_payload(Payload, _, Utf8State, _, _, _, false) ->
{more, Payload, Utf8State};
validate_payload(Payload, Rest, Utf8State, _, _, _, true) ->
{ok, Payload, Utf8State, Rest}.
-%% Returns <<>> if the argument is valid UTF-8, false if not,
-%% or the incomplete part of the argument if we need more data.
-validate_utf8(Valid = <<>>) ->
- Valid;
-validate_utf8(<< _/utf8, Rest/bits >>) ->
- validate_utf8(Rest);
-%% 2 bytes. Codepages C0 and C1 are invalid; fail early.
-validate_utf8(<< 2#1100000:7, _/bits >>) ->
- false;
-validate_utf8(Incomplete = << 2#110:3, _:5 >>) ->
- Incomplete;
-%% 3 bytes.
-validate_utf8(Incomplete = << 2#1110:4, _:4 >>) ->
- Incomplete;
-validate_utf8(Incomplete = << 2#1110:4, _:4, 2#10:2, _:6 >>) ->
- Incomplete;
-%% 4 bytes. Codepage F4 may have invalid values greater than 0x10FFFF.
-validate_utf8(<< 2#11110100:8, 2#10:2, High:6, _/bits >>) when High >= 2#10000 ->
- false;
-validate_utf8(Incomplete = << 2#11110:5, _:3 >>) ->
- Incomplete;
-validate_utf8(Incomplete = << 2#11110:5, _:3, 2#10:2, _:6 >>) ->
- Incomplete;
-validate_utf8(Incomplete = << 2#11110:5, _:3, 2#10:2, _:6, 2#10:2, _:6 >>) ->
- Incomplete;
-%% Invalid.
-validate_utf8(_) ->
- false.
+%% Based on the Flexible and Economical UTF-8 Decoder algorithm by
+%% Bjoern Hoehrmann <[email protected]> (http://bjoern.hoehrmann.de/utf-8/decoder/dfa/).
+%%
+%% The original algorithm has been unrolled into all combinations of values for C and State
+%% each with a clause. The common clauses were then grouped together.
+%%
+%% This function returns 0 on success, 1 on error, and 2..8 on incomplete data.
+validate_utf8(<<>>, State) -> State;
+validate_utf8(<< C, Rest/bits >>, 0) when C < 128 -> validate_utf8(Rest, 0);
+validate_utf8(<< C, Rest/bits >>, 2) when C >= 128, C < 144 -> validate_utf8(Rest, 0);
+validate_utf8(<< C, Rest/bits >>, 3) when C >= 128, C < 144 -> validate_utf8(Rest, 2);
+validate_utf8(<< C, Rest/bits >>, 5) when C >= 128, C < 144 -> validate_utf8(Rest, 2);
+validate_utf8(<< C, Rest/bits >>, 7) when C >= 128, C < 144 -> validate_utf8(Rest, 3);
+validate_utf8(<< C, Rest/bits >>, 8) when C >= 128, C < 144 -> validate_utf8(Rest, 3);
+validate_utf8(<< C, Rest/bits >>, 2) when C >= 144, C < 160 -> validate_utf8(Rest, 0);
+validate_utf8(<< C, Rest/bits >>, 3) when C >= 144, C < 160 -> validate_utf8(Rest, 2);
+validate_utf8(<< C, Rest/bits >>, 5) when C >= 144, C < 160 -> validate_utf8(Rest, 2);
+validate_utf8(<< C, Rest/bits >>, 6) when C >= 144, C < 160 -> validate_utf8(Rest, 3);
+validate_utf8(<< C, Rest/bits >>, 7) when C >= 144, C < 160 -> validate_utf8(Rest, 3);
+validate_utf8(<< C, Rest/bits >>, 2) when C >= 160, C < 192 -> validate_utf8(Rest, 0);
+validate_utf8(<< C, Rest/bits >>, 3) when C >= 160, C < 192 -> validate_utf8(Rest, 2);
+validate_utf8(<< C, Rest/bits >>, 4) when C >= 160, C < 192 -> validate_utf8(Rest, 2);
+validate_utf8(<< C, Rest/bits >>, 6) when C >= 160, C < 192 -> validate_utf8(Rest, 3);
+validate_utf8(<< C, Rest/bits >>, 7) when C >= 160, C < 192 -> validate_utf8(Rest, 3);
+validate_utf8(<< C, Rest/bits >>, 0) when C >= 194, C < 224 -> validate_utf8(Rest, 2);
+validate_utf8(<< 224, Rest/bits >>, 0) -> validate_utf8(Rest, 4);
+validate_utf8(<< C, Rest/bits >>, 0) when C >= 225, C < 237 -> validate_utf8(Rest, 3);
+validate_utf8(<< 237, Rest/bits >>, 0) -> validate_utf8(Rest, 5);
+validate_utf8(<< C, Rest/bits >>, 0) when C =:= 238; C =:= 239 -> validate_utf8(Rest, 3);
+validate_utf8(<< 240, Rest/bits >>, 0) -> validate_utf8(Rest, 6);
+validate_utf8(<< C, Rest/bits >>, 0) when C =:= 241; C =:= 242; C =:= 243 -> validate_utf8(Rest, 7);
+validate_utf8(<< 244, Rest/bits >>, 0) -> validate_utf8(Rest, 8);
+validate_utf8(_, _) -> 1.
+
+%% @doc Return a frame tuple from parsed state and data.
+
+-spec make_frame(frame_type(), binary(), close_code(), frag_state()) -> frame().
+%% Fragmented frame.
+make_frame(fragment, Payload, _, {Fin, Type, _}) -> {fragment, Fin, Type, Payload};
+make_frame(text, Payload, _, _) -> {text, Payload};
+make_frame(binary, Payload, _, _) -> {binary, Payload};
+make_frame(close, <<>>, undefined, _) -> close;
+make_frame(close, Payload, CloseCode, _) -> {close, CloseCode, Payload};
+make_frame(ping, <<>>, _, _) -> ping;
+make_frame(ping, Payload, _, _) -> {ping, Payload};
+make_frame(pong, <<>>, _, _) -> pong;
+make_frame(pong, Payload, _, _) -> {pong, Payload}.
%% @doc Construct an unmasked Websocket frame.
@@ -276,12 +513,12 @@ frame({pong, Payload}, _) ->
true = Len =< 125,
[<< 1:1, 0:3, 10:4, 0:1, Len:7 >>, Payload];
%% Data frames, deflate-frame extension.
-frame({text, Payload}, #{deflate_frame := Deflate}) ->
- Payload2 = deflate_frame(Payload, Deflate),
+frame({text, Payload}, #{deflate := Deflate, deflate_takeover := TakeOver}) ->
+ Payload2 = deflate_frame(Payload, Deflate, TakeOver),
Len = payload_length(Payload2),
[<< 1:1, 1:1, 0:2, 1:4, 0:1, Len/bits >>, Payload2];
-frame({binary, Payload}, #{deflate_frame := Deflate}) ->
- Payload2 = deflate_frame(Payload, Deflate),
+frame({binary, Payload}, #{deflate := Deflate, deflate_takeover := TakeOver}) ->
+ Payload2 = deflate_frame(Payload, Deflate, TakeOver),
Len = payload_length(Payload2),
[<< 1:1, 1:1, 0:2, 2:4, 0:1, Len/bits >>, Payload2];
%% Data frames.
@@ -292,6 +529,56 @@ frame({binary, Payload}, _) ->
Len = payload_length(Payload),
[<< 1:1, 0:3, 2:4, 0:1, Len/bits >>, Payload].
+%% @doc Construct a masked Websocket frame.
+%%
+%% We use a mask key of 0 if there is no payload for close, ping and pong frames.
+
+-spec masked_frame(frame(), extensions()) -> iodata().
+%% Control frames. Control packets must not be > 125 in length.
+masked_frame(close, _) ->
+ << 1:1, 0:3, 8:4, 1:1, 0:39 >>;
+masked_frame(ping, _) ->
+ << 1:1, 0:3, 9:4, 1:1, 0:39 >>;
+masked_frame(pong, _) ->
+ << 1:1, 0:3, 10:4, 1:1, 0:39 >>;
+masked_frame({close, Payload}, Extensions) ->
+ frame({close, 1000, Payload}, Extensions);
+masked_frame({close, StatusCode, Payload}, _) ->
+ Len = 2 + iolist_size(Payload),
+ true = Len =< 125,
+ MaskKeyBin = << MaskKey:32 >> = crypto:rand_bytes(4),
+ [<< 1:1, 0:3, 8:4, 1:1, Len:7 >>, MaskKeyBin, mask(iolist_to_binary([<< StatusCode:16 >>, Payload]), MaskKey, <<>>)];
+masked_frame({ping, Payload}, _) ->
+ Len = iolist_size(Payload),
+ true = Len =< 125,
+ MaskKeyBin = << MaskKey:32 >> = crypto:rand_bytes(4),
+ [<< 1:1, 0:3, 9:4, 1:1, Len:7 >>, MaskKeyBin, mask(iolist_to_binary(Payload), MaskKey, <<>>)];
+masked_frame({pong, Payload}, _) ->
+ Len = iolist_size(Payload),
+ true = Len =< 125,
+ MaskKeyBin = << MaskKey:32 >> = crypto:rand_bytes(4),
+ [<< 1:1, 0:3, 10:4, 1:1, Len:7 >>, MaskKeyBin, mask(iolist_to_binary(Payload), MaskKey, <<>>)];
+%% Data frames, deflate-frame extension.
+masked_frame({text, Payload}, #{deflate := Deflate, deflate_takeover := TakeOver}) ->
+ MaskKeyBin = << MaskKey:32 >> = crypto:rand_bytes(4),
+ Payload2 = mask(deflate_frame(Payload, Deflate, TakeOver), MaskKey, <<>>),
+ Len = payload_length(Payload2),
+ [<< 1:1, 1:1, 0:2, 1:4, 1:1, Len/bits >>, MaskKeyBin, Payload2];
+masked_frame({binary, Payload}, #{deflate := Deflate, deflate_takeover := TakeOver}) ->
+ MaskKeyBin = << MaskKey:32 >> = crypto:rand_bytes(4),
+ Payload2 = mask(deflate_frame(Payload, Deflate, TakeOver), MaskKey, <<>>),
+ Len = payload_length(Payload2),
+ [<< 1:1, 1:1, 0:2, 2:4, 1:1, Len/bits >>, MaskKeyBin, Payload2];
+%% Data frames.
+masked_frame({text, Payload}, _) ->
+ MaskKeyBin = << MaskKey:32 >> = crypto:rand_bytes(4),
+ Len = payload_length(Payload),
+ [<< 1:1, 0:3, 1:4, 1:1, Len/bits >>, MaskKeyBin, mask(iolist_to_binary(Payload), MaskKey, <<>>)];
+masked_frame({binary, Payload}, _) ->
+ MaskKeyBin = << MaskKey:32 >> = crypto:rand_bytes(4),
+ Len = payload_length(Payload),
+ [<< 1:1, 0:3, 2:4, 1:1, Len/bits >>, MaskKeyBin, mask(iolist_to_binary(Payload), MaskKey, <<>>)].
+
payload_length(Payload) ->
case byte_size(Payload) of
N when N =< 125 -> << N:7 >>;
@@ -299,8 +586,12 @@ payload_length(Payload) ->
N when N =< 16#7fffffffffffffff -> << 127:7, N:64 >>
end.
-deflate_frame(Payload, Deflate) ->
+deflate_frame(Payload, Deflate, TakeOver) ->
Deflated = iolist_to_binary(zlib:deflate(Deflate, Payload, sync)),
+ case TakeOver of
+ no_takeover -> zlib:deflateReset(Deflate);
+ takeover -> ok
+ end,
Len = byte_size(Deflated) - 4,
case Deflated of
<< Body:Len/binary, 0:8, 0:8, 255:8, 255:8 >> -> Body;
diff --git a/src/cowlib.app.src b/src/cowlib.app.src
index ec21de0..5d358c2 100644
--- a/src/cowlib.app.src
+++ b/src/cowlib.app.src
@@ -1,4 +1,4 @@
-%% Copyright (c) 2013-2014, Loïc Hoguin <[email protected]>
+%% Copyright (c) 2013-2015, Loïc Hoguin <[email protected]>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
@@ -14,7 +14,7 @@
{application, cowlib, [
{description, "Support library for manipulating Web protocols."},
- {vsn, "1.1.0"},
+ {vsn, "1.2.0"},
{id, "git"},
{modules, []},
{registered, []},