Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,15 @@ jobs:
os:
- ubuntu-latest
otp:
- "29"
- "28"
- "27"
- "26"
- "25"
- "24"
- "23"
- "22"
- "21"
- "20"
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- run: make test
- run: make edoc
- run: REBAR=rebar make test
Expand Down
22 changes: 0 additions & 22 deletions examples/example_project/src/example_project_web.erl
Original file line number Diff line number Diff line change
Expand Up @@ -19,33 +19,11 @@ start(Options) ->

stop() -> mochiweb_http:stop(?MODULE).

%% OTP 21 is the first to define OTP_RELEASE and the first to support
%% EEP-0047 direct stack trace capture.
-ifdef(OTP_RELEASE).

-if((?OTP_RELEASE) >= 21).

-define(HAS_DIRECT_STACKTRACE, true).

-endif.

-endif.

-ifdef(HAS_DIRECT_STACKTRACE).

- define ( CAPTURE_EXC_PRE ( Type , What , Trace ) , Type : What : Trace ) .


-define(CAPTURE_EXC_GET(Trace), Trace).

-else.

-define(CAPTURE_EXC_PRE(Type, What, Trace), Type:What).

-define(CAPTURE_EXC_GET(Trace),
erlang:get_stacktrace()).

-endif.

loop ( Req , DocRoot ) -> "/" ++ Path = mochiweb_request : get ( path , Req ) , try case mochiweb_request : get ( method , Req ) of Method when Method =:= 'GET' ; Method =:= 'HEAD' -> case Path of "hello_world" -> mochiweb_request : respond ( { 200 , [ { "Content-Type" , "text/plain" } ] , "Hello world!\n" } , Req ) ; _ -> mochiweb_request : serve_file ( Path , DocRoot , Req ) end ; 'POST' -> case Path of _ -> mochiweb_request : not_found ( Req ) end ; _ -> mochiweb_request : respond ( { 501 , [ ] , [ ] } , Req ) end catch ? CAPTURE_EXC_PRE ( Type , What , Trace ) -> Report = [ "web request failed" , { path , Path } , { type , Type } , { what , What } , { trace , ? CAPTURE_EXC_GET ( Trace ) } ] , error_logger : error_report ( Report ) , mochiweb_request : respond ( { 500 , [ { "Content-Type" , "text/plain" } ] , "request failed, sorry\n" } , Req ) end .

Expand Down
8 changes: 4 additions & 4 deletions rebar.config
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
% -*- mode: erlang -*-
{erl_opts, [debug_info,
{platform_define, "^(20|21|22)", new_crypto_unavailable},
{platform_define, "^20", ssl_handshake_unavailable},
{platform_define, "^21-", otp_21}]}.
% rebar 2
{require_min_otp_vsn, "23"}.
% rebar 3
{minimum_otp_vsn, "23"}.
{cover_enabled, true}.
{eunit_opts, [verbose, {report,{eunit_surefire,[{dir,"."}]}}]}.
{edoc_opts, [{preprocess, true}]}.
Expand Down
12 changes: 7 additions & 5 deletions src/mochijson2.erl
Original file line number Diff line number Diff line change
Expand Up @@ -763,7 +763,7 @@ input_validation_test() ->
ok = try decode(X) catch invalid_utf8 -> ok end,
%% could be {ucs,{bad_utf8_character_code}} or
%% {json_encode,{bad_char,_}}
{'EXIT', _} = (catch encode(X))
{error, _} = try encode(X) catch error:E -> {error, E} end
end, Bad).

inline_json_test() ->
Expand Down Expand Up @@ -898,8 +898,9 @@ float_test() ->

handler_test() ->
?assertEqual(
{'EXIT',{json_encode,{bad_term,{x,y}}}},
catch encode({x,y})),
{exit,{json_encode,{bad_term,{x,y}}}},
try encode({x,y}) catch exit:E -> {exit, E} end
),
F = fun ({x,y}) -> [] end,
?assertEqual(
<<"[]">>,
Expand Down Expand Up @@ -934,8 +935,9 @@ array_test() ->

bad_char_test() ->
?assertEqual(
{'EXIT', {json_encode, {bad_char, 16#110000}}},
catch json_string_is_safe([16#110000])).
{exit, {json_encode, {bad_char, 16#110000}}},
try json_string_is_safe([16#110000]) catch exit:E -> {exit, E} end
).

utf8_roundtrip_test_() ->
%% These are the boundary cases for UTF8 encoding
Expand Down
9 changes: 4 additions & 5 deletions src/mochiweb.erl
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,10 @@ new_response({Request, Code, Headers}) ->
mochiweb_headers:make(Headers)).

%% @spec ensure_started(App::atom()) -> ok
%% @doc Start the given App if it has not been started already.
%% @doc Start the given App and all it
%% dependencies if it has not been started already.
ensure_started(App) ->
case application:start(App) of
ok ->
ok;
{error, {already_started, App}} ->
case application:ensure_all_started(App) of
{ok, _} ->
ok
end.
30 changes: 17 additions & 13 deletions src/mochiweb_acceptor.erl
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
-author('bob@mochimedia.com').

-include("internal.hrl").
-include_lib("kernel/include/logger.hrl").

-export([init/4, start_link/3, start_link/4]).

Expand All @@ -50,27 +51,30 @@ do_accept(Server, Listen) ->
end.

init(Server, Listen, Loop, Opts) ->
case catch do_accept(Server, Listen) of
try do_accept(Server, Listen) of
{ok, Socket} -> call_loop(Loop, Socket, Opts);
{error, Err}
when Err =:= closed orelse
Err =:= esslaccept orelse Err =:= timeout ->
exit({shutdown, Err});
Other ->
%% Mitigate out of file descriptor scenario by sleeping for a
%% short time to slow error rate
case Other of
{error, emfile} ->
receive after ?EMFILE_SLEEP_MSEC -> ok end;
_ -> ok
end,
error_logger:error_report([{application, mochiweb},
"Accept failed error",
lists:flatten(io_lib:format("~p",
[Other]))]),
exit({error, accept_failed})
accept_failure(Other)
catch _:Other ->
accept_failure(Other)
end.

%% Mitigate out of file descriptor scenario by sleeping for a
%% short time to slow error rate
emfile_backoff({error, emfile}) ->
receive after ?EMFILE_SLEEP_MSEC -> ok end;
emfile_backoff(_Error) ->
ok.

accept_failure(Error) ->
emfile_backoff(Error),
?LOG_ERROR("Accept failed error ~p", [Error]),
exit({error, accept_failed}).

call_loop({M, F}, Socket, Opts) when is_atom(M) ->
M:F(Socket, Opts);
call_loop({M, F, [A1]}, Socket, Opts) when is_atom(M) ->
Expand Down
2 changes: 1 addition & 1 deletion src/mochiweb_multipart.erl
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ with_socket_server(Transport, ServerFun, ClientFun) ->
ClientOpts1),
{ok, {ssl, SslSocket}}
end,
Res = (catch ClientFun(Client)),
Res = try ClientFun(Client) catch _:Error -> Error end,
mochiweb_socket_server:stop(Server),
Res.

Expand Down
22 changes: 0 additions & 22 deletions src/mochiweb_session.erl
Original file line number Diff line number Diff line change
Expand Up @@ -119,26 +119,6 @@ ensure_binary(B) when is_binary(B) ->
ensure_binary(L) when is_list(L) ->
iolist_to_binary(L).

-ifdef(new_crypto_unavailable).
-spec encrypt_data(binary(), binary()) -> binary().
encrypt_data(Data, Key) ->
IV = crypto:strong_rand_bytes(16),
Crypt = crypto:block_encrypt(aes_cfb128, Key, IV, Data),
<<IV/binary, Crypt/binary>>.

-spec decrypt_data(binary(), binary()) -> binary().
decrypt_data(<<IV:16/binary, Crypt/binary>>, Key) ->
crypto:block_decrypt(aes_cfb128, Key, IV, Crypt).

-spec gen_key(iolist(), iolist()) -> binary().
gen_key(ExpirationTime, ServerKey) ->
crypto:hmac(md5, ServerKey, [ExpirationTime]).

-spec gen_hmac(iolist(), binary(), iolist(), binary()) -> binary().
gen_hmac(ExpirationTime, Data, SessionKey, Key) ->
crypto:hmac(sha, Key, [ExpirationTime, Data, SessionKey]).

-else. % new crypto available (OTP 23+)
-spec encrypt_data(binary(), binary()) -> binary().
encrypt_data(Data, Key) ->
IV = crypto:strong_rand_bytes(16),
Expand All @@ -157,8 +137,6 @@ gen_key(ExpirationTime, ServerKey) ->
gen_hmac(ExpirationTime, Data, SessionKey, Key) ->
crypto:mac(hmac, sha, Key, [ExpirationTime, Data, SessionKey]).

-endif.

-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").

Expand Down
30 changes: 0 additions & 30 deletions src/mochiweb_socket.erl
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,6 @@ listen(Ssl, Port, Opts, SslOpts) ->
gen_tcp:listen(Port, Opts)
end.

-ifdef(new_crypto_unavailable).
add_unbroken_ciphers_default(Opts) ->
Default = filter_unsecure_cipher_suites(ssl:cipher_suites()),
Ciphers = filter_broken_cipher_suites(proplists:get_value(ciphers, Opts, Default)),
[{ciphers, Ciphers} | proplists:delete(ciphers, Opts)].

%% Filter old map style cipher suites
filter_unsecure_cipher_suites(Ciphers) ->
lists:filter(fun
({_,des_cbc,_}) -> false;
({_,_,md5}) -> false;
(_) -> true
end,
Ciphers).

-else.
add_unbroken_ciphers_default(Opts) ->
%% add_safe_protocol_versions/1 must have been called to ensure a {versions, _} tuple is present
Versions = proplists:get_value(versions, Opts),
Expand All @@ -61,8 +45,6 @@ filter_unsecure_cipher_suites(Ciphers) ->
{mac, fun(md5) -> false; (_) -> true end}
]).

-endif.

filter_broken_cipher_suites(Ciphers) ->
case proplists:get_value(ssl_app, ssl:versions()) of
"5.3" ++ _ ->
Expand Down Expand Up @@ -108,17 +90,6 @@ transport_accept({ssl, ListenSocket}) ->
transport_accept(ListenSocket) ->
gen_tcp:accept(ListenSocket, ?ACCEPT_TIMEOUT).

-ifdef(ssl_handshake_unavailable).
finish_accept({ssl, Socket}) ->
case ssl:ssl_accept(Socket, ?SSL_HANDSHAKE_TIMEOUT) of
ok ->
{ok, {ssl, Socket}};
{error, _} = Err ->
Err
end;
finish_accept(Socket) ->
{ok, Socket}.
-else.
finish_accept({ssl, Socket}) ->
case ssl:handshake(Socket, ?SSL_HANDSHAKE_TIMEOUT) of
{ok, SslSocket} ->
Expand All @@ -128,7 +99,6 @@ finish_accept({ssl, Socket}) ->
end;
finish_accept(Socket) ->
{ok, Socket}.
-endif.

recv({ssl, Socket}, Length, Timeout) ->
ssl:recv(Socket, Length, Timeout);
Expand Down
45 changes: 15 additions & 30 deletions src/mochiweb_socket_server.erl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
-behaviour(gen_server).

-include("internal.hrl").
-include_lib("kernel/include/logger.hrl").

-export([start/1, start_link/1, stop/1]).
-export([init/1, handle_call/3, handle_cast/2, terminate/2, code_change/3,
Expand Down Expand Up @@ -57,8 +58,7 @@ get(Name, Property) ->
set(Name, profile_fun, Fun) ->
gen_server:cast(Name, {set, profile_fun, Fun});
set(Name, Property, _Value) ->
error_logger:info_msg("?MODULE:set for ~p with ~p not implemented~n",
[Name, Property]).
?LOG_INFO("?MODULE:set for ~p with ~p not implemented~n", [Name, Property]).

stop(Name) when is_atom(Name) orelse is_pid(Name) ->
gen_server:call(Name, stop);
Expand All @@ -79,9 +79,8 @@ parse_options([], State=#mochiweb_socket_server{acceptor_pool_size=PoolSize,
max=Max}) ->
case Max < PoolSize of
true ->
error_logger:info_report([{warning, "max is set lower than acceptor_pool_size"},
{max, Max},
{acceptor_pool_size, PoolSize}]);
LogMsg = "max ~p is set lower than acceptor_pool_size ~p",
?LOG_WARNING(LogMsg, [Max, PoolSize]);
false ->
ok
end,
Expand Down Expand Up @@ -167,26 +166,8 @@ start_server(F, State=#mochiweb_socket_server{ssl=Ssl, name=Name}) ->
gen_server:F(Name, ?MODULE, State, [])
end.

-ifdef(otp_21).
check_ssl_compatibility() ->
case lists:keyfind(ssl, 1, application:loaded_applications()) of
{_, _, V} when V =:= "9.1" orelse V =:= "9.1.1" ->
{error, "ssl-" ++ V ++ " (OTP 21.2 to 21.2.2) has a regression and is not safe to use with mochiweb. See https://bugs.erlang.org/browse/ERL-830"};
_ ->
ok
end.
-else.
check_ssl_compatibility() ->
ok.
-endif.

prep_ssl(true) ->
ok = mochiweb:ensure_started(crypto),
ok = mochiweb:ensure_started(asn1),
ok = mochiweb:ensure_started(public_key),
ok = mochiweb:ensure_started(ssl),
ok = check_ssl_compatibility(),
ok;
ok = mochiweb:ensure_started(ssl);
prep_ssl(false) ->
ok.

Expand All @@ -196,7 +177,7 @@ ensure_int(S) when is_list(S) ->
list_to_integer(S).

ipv6_supported() ->
case (catch inet:getaddr("localhost", inet6)) of
case inet:getaddr("localhost", inet6) of
{ok, _Addr} ->
true;
{error, _} ->
Expand Down Expand Up @@ -319,7 +300,12 @@ handle_cast({accepted, Pid, Timing},
undefined ->
undefined;
F when is_function(F) ->
catch F([{timing, Timing} | state_to_proplist(State1)])
try
F([{timing, Timing} | state_to_proplist(State1)])
catch
_:_ ->
undefined
end
end,
{noreply, recycle_acceptor(Pid, State1)};
handle_cast({set, profile_fun, ProfileFun}, State) ->
Expand Down Expand Up @@ -380,8 +366,7 @@ handle_info({'EXIT', Pid, Reason},
case sets:is_element(Pid, Pool) of
true ->
%% If there was an unexpected error accepting, log and sleep.
error_logger:error_report({?MODULE, ?LINE,
{acceptor_error, Reason}}),
?LOG_ERROR("Accept error ~p", [Reason]),
timer:sleep(100);
false ->
ok
Expand All @@ -397,12 +382,12 @@ handle_info({From, Tag, get_modules}, State = #mochiweb_socket_server{name={loca

% If for some reason we can't get the module name, send empty list to avoid release_handler timeout:
handle_info({From, Tag, get_modules}, State) ->
error_logger:info_msg("mochiweb_socket_server replying to dynamic modules request as '[]'~n",[]),
?LOG_INFO("mochiweb_socket_server replying to dynamic modules request as '[]'~n",[]),
From ! {element(2,Tag), []},
{noreply, State};

handle_info(Info, State) ->
error_logger:info_report([{'INFO', Info}, {'State', State}]),
?LOG_INFO("handle_info Info:~p State:~p", [Info, State]),
{noreply, State}.


Expand Down
6 changes: 4 additions & 2 deletions src/mochiweb_util.erl
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ cmd_status(Argv, Options) ->
Port = cmd_port(Argv, [exit_status, stderr_to_stdout,
use_stdio, binary | Options]),
try cmd_loop(Port, [])
after catch port_close(Port)
after
try port_close(Port) catch _:Error -> Error end
end.

%% @spec cmd_loop(port(), list()) -> {ExitStatus::integer(), Stdout::binary()}
Expand Down Expand Up @@ -689,7 +690,8 @@ cmd_port_test() ->
Port = cmd_port(["echo", "$bling$ `word`!"],
[eof, stream, {line, 4096}]),
Res = try lists:append(lists:reverse(cmd_port_test_spool(Port, [])))
after catch port_close(Port)
after
try port_close(Port) catch _:Error -> Error end
end,
self() ! {Port, wtf},
try cmd_port_test_spool(Port, [])
Expand Down
Loading