Merge pull request #239 from mochi/minimum-otp-18

Set minimum OTP to 18
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d385746..f04e537 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -13,20 +13,16 @@
         os:
           - ubuntu-latest
         otp:
-          - "24.0.2"
-          - "23.3.1"
+          - "24.3.3"
+          - "23.3.4.5"
           - "22.3.4.9"
           - "21.3.8.17"
           - "20.3.8.26"
         include:
           - os: ubuntu-18.04
-            otp: "21.2.7"
-          - os: ubuntu-18.04
             otp: "19.3.6.13"
           - os: ubuntu-18.04
             otp: "18.3.4.11"
-          - os: ubuntu-18.04
-            otp: "16.b.2.basho10"
     steps:
       - uses: actions/checkout@v2.3.2
       - run: |
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 00984a8..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-sudo: false
-language: erlang
-notifications:
-  email: false
-matrix:
-  include:
-  - otp_release: 17.5
-    dist: trusty
-  - otp_release: R16B03-1
-    dist: trusty
-  - otp_release: R15B03
-    dist: trusty
diff --git a/CHANGES.md b/CHANGES.md
index b625183..3216a7e 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,10 @@
-New version
+Version 3.0.0 released 2022-XX-XX
 
+* Minimum OTP version is now 18, which
+  allows us to remove a number of backwards
+  compatibility hacks while still supporting
+  almost 7 years of Erlang releases.
+  https://github.com/mochi/mochiweb/pull/239
 * Crashing client processes now exit with reason
   `{shutdown, Error}`. This ensures processes
   linked to the connection process are also
diff --git a/README.md b/README.md
index e1e1040..78a4d81 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@
 Information about Rebar (Erlang build tool) is available at https://github.com/rebar/rebar
 
 MochiWeb is currently tested with Erlang/OTP 18.3 through 24.0,
-but may still be compatible back to R15B-03.
+versions older than 3.0.0 may still be compatible back to R15B-03.
 
 # OTP 21.2, 21.2.1, 21.2.2 warning
 
diff --git a/rebar.config b/rebar.config
index 3176eda..07df66e 100644
--- a/rebar.config
+++ b/rebar.config
@@ -1,13 +1,8 @@
 % -*- mode: erlang -*-
 {erl_opts, [debug_info,
-            {platform_define, "^R15", 'gen_tcp_r15b_workaround'},
-            {platform_define, "^(R14|R15|R16B-)", 'crypto_compatibility'},
-            {platform_define, "^(R14|R15|R16|17|18|19|20|21|22)", new_crypto_unavailable},
-            {platform_define, "^(R14|R15|R16B|17)", 'rand_mod_unavailable'},
-            {platform_define, "^(R14|R15|R16B|17)", 'sni_unavailable'},
-            {platform_define, "^(R14|R15|R16)", 'map_unavailable'},
-            {platform_define, "^(R14|R15|R16|17|18|19|20)", 'ssl_handshake_unavailable'},
-            {platform_define, "^21-", 'otp_21'}]}.
+            {platform_define, "^(18|19|20|21|22)", new_crypto_unavailable},
+            {platform_define, "^(18|19|20)", ssl_handshake_unavailable},
+            {platform_define, "^21-", otp_21}]}.
 {cover_enabled, true}.
 {eunit_opts, [verbose, {report,{eunit_surefire,[{dir,"."}]}}]}.
 {dialyzer_opts, [{warnings, [no_return,
diff --git a/src/mochijson2.erl b/src/mochijson2.erl
index 94ac2f8..57933e5 100644
--- a/src/mochijson2.erl
+++ b/src/mochijson2.erl
@@ -82,11 +82,6 @@
 -define(IS_WHITESPACE(C),
         (C =:= $\s orelse C =:= $\t orelse C =:= $\r orelse C =:= $\n)).
 
--ifdef(map_unavailable).
--define(IS_MAP(_), false).
--else.
--define(IS_MAP(X), is_map(X)).
--endif.
 
 %% @type json_string() = atom | binary()
 %% @type json_number() = integer() | float()
@@ -159,14 +154,8 @@
   when Format =:= struct orelse Format =:= eep18 orelse Format =:= proplist ->
     parse_decoder_options(Rest, State#decoder{object_hook=Format}).
 
--ifdef(map_unavailable).
-make_object_hook_for_map() ->
-    exit({json_decode, {bad_format, map_unavailable}}).
--else.
 make_object_hook_for_map() ->
     fun ({struct, P}) -> maps:from_list(P) end.
--endif.
-
 
 json_encode(true, _State) ->
     <<"true">>;
@@ -194,7 +183,7 @@
     json_encode_array(Array, State);
 json_encode({array, Array}, State) when is_list(Array) ->
     json_encode_array(Array, State);
-json_encode(M, State) when ?IS_MAP(M) ->
+json_encode(M, State) when is_map(M) ->
     json_encode_map(M, State);
 json_encode({json, IoList}, _State) ->
     IoList;
@@ -223,11 +212,6 @@
     [$, | Acc1] = lists:foldl(F, "{", Props),
     lists:reverse([$\} | Acc1]).
 
--ifdef(map_unavailable).
-json_encode_map(Bad, _State) ->
-  %% IS_MAP definition guarantees that this branch is dead
-  exit({json_encode, {bad_term, Bad}}).
--else.
 json_encode_map(Map, _State) when map_size(Map) =:= 0 ->
     <<"{}">>;
 json_encode_map(Map, State) ->
@@ -238,7 +222,6 @@
           end,
     [$, | Acc1] = maps:fold(F, "{", Map),
     lists:reverse([$\} | Acc1]).
--endif.
 
 json_encode_string(A, State) when is_atom(A) ->
     json_encode_string(atom_to_binary(A, latin1), State);
@@ -977,8 +960,6 @@
     [{"roundtrip escaped", ?_assertEqual(S, decode(encode(S)))},
      {"roundtrip utf8", ?_assertEqual(S, decode((encoder([{utf8, true}]))(S)))}].
 
--ifndef(map_unavailable).
-
 decode_map_test() ->
     Json = "{\"var1\": 3, \"var2\": {\"var3\": 7}}",
     M = #{<<"var1">> => 3,<<"var2">> => #{<<"var3">> => 7}},
@@ -992,5 +973,3 @@
     ?assertEqual(<<"{}">>, encode(#{})).
 
 -endif.
-
--endif.
diff --git a/src/mochiweb.app.src b/src/mochiweb.app.src
index f0059da..6baf6bf 100644
--- a/src/mochiweb.app.src
+++ b/src/mochiweb.app.src
@@ -1,7 +1,7 @@
 %% This is generated from src/mochiweb.app.src
 {application, mochiweb,
  [{description, "MochiMedia Web Server"},
-  {vsn, "2.22.0"},
+  {vsn, "3.0.0"},
   {modules, []},
   {registered, []},
   {env, []},
diff --git a/src/mochiweb_http.erl b/src/mochiweb_http.erl
index dc9364f..7d5af66 100644
--- a/src/mochiweb_http.erl
+++ b/src/mochiweb_http.erl
@@ -43,16 +43,6 @@
 
 -define(DEFAULTS, [{name, ?MODULE}, {port, 8888}]).
 
--ifdef(gen_tcp_r15b_workaround).
-
-r15b_workaround() -> false.
-
--else.
-
-r15b_workaround() -> false.
-
--endif.
-
 parse_options(Options) ->
     {loop, HttpLoop} = proplists:lookup(loop, Options),
     Loop = {?MODULE, loop, [HttpLoop]},
@@ -118,8 +108,8 @@
 	  request(Socket, Opts, Body);
       {tcp_closed = Error, _} ->
 	  mochiweb_socket:close(Socket), exit({shutdown, Error});
-      {tcp_error, _, emsgsize} = Other ->
-	  handle_invalid_msg_request(Other, Socket, Opts);
+      {tcp_error, _, emsgsize} ->
+	  handle_invalid_request(Socket, Opts);
       {ssl_closed = Error, _} ->
 	  mochiweb_socket:close(Socket), exit({shutdown, Error})
       after ?REQUEST_RECV_TIMEOUT ->
@@ -155,9 +145,8 @@
 		  [{Name, Value} | Headers], Body, 1 + HeaderCount);
       {tcp_closed = Error, _} ->
 	  mochiweb_socket:close(Socket), exit({shutdown, Error});
-      {tcp_error, _, emsgsize} = Other ->
-	  handle_invalid_msg_request(Other, Socket, Opts, Request,
-				     Headers)
+      {tcp_error, _, emsgsize} ->
+	  handle_invalid_request(Socket, Opts, Request, Headers)
       after ?HEADERS_RECV_TIMEOUT ->
 		mochiweb_socket:close(Socket), exit({shutdown, headers_recv_timeout})
     end.
@@ -167,27 +156,11 @@
 call_body({M, F}, Req) when is_atom(M) -> M:F(Req);
 call_body(Body, Req) -> Body(Req).
 
--spec handle_invalid_msg_request(term(), term(),
-				 term()) -> no_return().
-
-handle_invalid_msg_request(Msg, Socket, Opts) ->
-    handle_invalid_msg_request(Msg, Socket, Opts,
+-spec handle_invalid_request(term(), term()) -> no_return().
+handle_invalid_request(Socket, Opts) ->
+    handle_invalid_request(Socket, Opts,
 			       {'GET', {abs_path, "/"}, {0, 9}}, []).
 
--spec handle_invalid_msg_request(term(), term(), term(),
-				 term(), term()) -> no_return().
-
-handle_invalid_msg_request(Msg, Socket, Opts, Request,
-			   RevHeaders) ->
-    case {Msg, r15b_workaround()} of
-      {{tcp_error, _, emsgsize}, true} ->
-	  %% R15B02 returns this then closes the socket, so close and exit
-	  mochiweb_socket:close(Socket),
-         exit({shutdown, {tcp_error, emsgsize}});
-      _ ->
-	  handle_invalid_request(Socket, Opts, Request,
-				 RevHeaders)
-    end.
 
 -spec handle_invalid_request(term(), term(), term(),
 			     term()) -> no_return().
diff --git a/src/mochiweb_session.erl b/src/mochiweb_session.erl
index bf7a589..042731f 100644
--- a/src/mochiweb_session.erl
+++ b/src/mochiweb_session.erl
@@ -119,26 +119,6 @@
 ensure_binary(L) when is_list(L) ->
     iolist_to_binary(L).
 
--ifdef(crypto_compatibility).
--spec encrypt_data(binary(), binary()) -> binary().
-encrypt_data(Data, Key) ->
-    IV = crypto:strong_rand_bytes(16),
-    Crypt = crypto:aes_cfb_128_encrypt(Key, IV, Data),
-    <<IV/binary, Crypt/binary>>.
-
--spec decrypt_data(binary(), binary()) -> binary().
-decrypt_data(<<IV:16/binary, Crypt/binary>>, Key) ->
-    crypto:aes_cfb_128_decrypt(Key, IV, Crypt).
-
--spec gen_key(iolist(), iolist()) -> binary().
-gen_key(ExpirationTime, ServerKey) ->
-    crypto:md5_mac(ServerKey, [ExpirationTime]).
-
--spec gen_hmac(iolist(), binary(), iolist(), binary()) -> binary().
-gen_hmac(ExpirationTime, Data, SessionKey, Key) ->
-    crypto:sha_mac(Key, [ExpirationTime, Data, SessionKey]).
-
--else.
 -ifdef(new_crypto_unavailable).
 -spec encrypt_data(binary(), binary()) -> binary().
 encrypt_data(Data, Key) ->
@@ -178,7 +158,6 @@
     crypto:mac(hmac, sha, Key, [ExpirationTime, Data, SessionKey]).
 
 -endif.
--endif.
 
 -ifdef(TEST).
 -include_lib("eunit/include/eunit.hrl").
diff --git a/src/mochiweb_util.erl b/src/mochiweb_util.erl
index 9c6ce2e..5340945 100644
--- a/src/mochiweb_util.erl
+++ b/src/mochiweb_util.erl
@@ -627,13 +627,8 @@
 normalize_path([C|Path], Acc) ->
         normalize_path(Path, [C|Acc]).
 
--ifdef(rand_mod_unavailable).
-rand_uniform(Start, End) ->
-    crypto:rand_uniform(Start, End).
--else.
 rand_uniform(Start, End) ->
     Start + rand:uniform(End - Start) - 1.
--endif.
 
 %%
 %% Tests
diff --git a/test/mochiweb_base64url_tests.erl b/test/mochiweb_base64url_tests.erl
index 4f73666..8ca98a8 100644
--- a/test/mochiweb_base64url_tests.erl
+++ b/test/mochiweb_base64url_tests.erl
@@ -9,15 +9,10 @@
        X,
        mochiweb_base64url:decode(
          binary_to_list(mochiweb_base64url:encode(binary_to_list(X))))).
--ifdef(rand_mod_unavailable).
-random_binary(Short,Long) ->
-    << <<(random:uniform(256) - 1)>>
-     || _ <- lists:seq(1, Short + random:uniform(1 + Long - Short) - 1) >>.
--else.
+
 random_binary(Short,Long) ->
     << <<(rand:uniform(256) - 1)>>
      || _ <- lists:seq(1, Short + rand:uniform(1 + Long - Short) - 1) >>.
--endif.
 
 empty_test() ->
     id(<<>>).
diff --git a/test/mochiweb_http_tests.erl b/test/mochiweb_http_tests.erl
index d58971a..35be11d 100644
--- a/test/mochiweb_http_tests.erl
+++ b/test/mochiweb_http_tests.erl
@@ -2,16 +2,6 @@
 
 -include_lib("eunit/include/eunit.hrl").
 
--ifdef(gen_tcp_r15b_workaround).
-
--define(SHOULD_HAVE_BUG, true).
-
--else.
-
--define(SHOULD_HAVE_BUG, false).
-
--endif.
-
 has_acceptor_bug_test_() ->
     {setup, fun start_server/0, fun mochiweb_http:stop/1,
      fun has_acceptor_bug_tests/1}.
@@ -27,7 +17,7 @@
     [{"1000 should be fine even with the bug",
       ?_assertEqual(false, (has_bug(Port, 1000)))},
      {"10000 should trigger the bug if present",
-      ?_assertEqual((?SHOULD_HAVE_BUG),
+      ?_assertEqual(false,
 		    (has_bug(Port, 10000)))}].
 
 responder(Req) ->
@@ -47,5 +37,6 @@
        {{"HTTP/1.1", 200, "OK"}, _,
 	"<html><body>Hello</body></html>"}} ->
 	  false;
+      %% It is expected that the request will fail because the header is too long
       {ok, {{"HTTP/1.1", 400, "Bad Request"}, _, []}} -> false
     end.
diff --git a/test/mochiweb_test_util.erl b/test/mochiweb_test_util.erl
index c4feaed..c56faf7 100644
--- a/test/mochiweb_test_util.erl
+++ b/test/mochiweb_test_util.erl
@@ -25,13 +25,8 @@
     mochiweb_http:stop(Server),
     Res.
 
--ifdef(sni_unavailable).
-ssl_client_opts(Opts) ->
-  [{ssl_imp, new} | Opts].
--else.
 ssl_client_opts(Opts) ->
   [{server_name_indication, disable} | Opts].
--endif.
 
 sock_fun(Transport, Port) ->
     Opts = [binary, {active, false}, {packet, http}],