%%% This file is part of RefactorErl.
%%%
%%% RefactorErl is free software: you can redistribute it and/or modify
%%% it under the terms of the GNU Lesser General Public License as published
%%% by the Free Software Foundation, either version 3 of the License, or
%%% (at your option) any later version.
%%%
%%% RefactorErl is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%%% GNU Lesser General Public License for more details.
%%%
%%% You should have received a copy of the GNU Lesser General Public License
%%% along with RefactorErl.  If not, see <http://plc.inf.elte.hu/erlang/>.
%%%
%%% The Original Code is RefactorErl.
%%%
%%% The Initial Developer of the Original Code is Eötvös Loránd University.
%%% Portions created  by Eötvös Loránd University and ELTE-Soft Ltd.
%%% are Copyright 2007-2025 Eötvös Loránd University, ELTE-Soft Ltd.
%%% and Ericsson Hungary. All Rights Reserved.


%%% @author Brigitta Baranyai <baranyai.brigitta26@gmail.com>

-module(reflib_secure).
-vsn("$Rev: 17551 $").

-export([get_unsecure_calls/1, get_calls_for_deprecated/1,
	get_calls_for_removed/1, get_calls_for_to_be_removed/1,
    get_calls_for_os/1, get_calls_for_os_tuple/1, 
	get_calls_for_port/1, get_calls_for_port_tuple/1, 
	get_calls_for_unstable_funs/1, get_calls_for_unstable_funs_tuple/1, 
	get_calls_for_file/1, get_calls_for_nif/1, get_calls_for_port_driver/1, 
	get_calls_for_old_crypto_api/1, get_calls_for_old_crypto_api_help/1, 
	unsecure_compile_and_load_operations/1, 
	unsecure_process_linkage/1, unsecure_prioritization/1, unsecure_prioritization_tuple/1, 
	get_unsecure_concurrent_calls/1, unsecure_ets_traversal/1, unsafe_network/1, 
	get_unsecure_interoperability/1, get_unsecure_xml_usage/1, check_unsecure_communication/1]).

-export([get_vulnerabilities/1, severity_level/1, get_candidates/0, get_candidate_forms/0]).

-include("lib.hrl").

-define(removed_28, [{disk_log, [{inc_wrap_file, 1}]}]).

-define(removed_27, [{crypto, [{crypto_dyn_iv_init, 3}, {crypto_dyn_iv_update, 3}]},
                     {ct_slave}, {slave},
                     {dbg, [{stop_clear, 0}]},
					 {file, [{pid2name, 1}]},
					 {http_uri, [{decode, 1}, {encode, 1}]},
					 {zlib, [{adler32, 2}, {adler32, 3}, {adler32_combine, 4}, {getBufSize, 1}, {setBufSize, 2},
					         {crc32, 1}, {crc32, 2}, {crc32, 3}, {crc32_combine, 4}, 
							 {inflateChunk, 1}, {inflateChunk, 2}]}]).

-define(removed_26, [{code, [{is_module_native, 1}, {rehash, 0}]}, 
                     {disk_log, [{accessible_logs, 0}, {lclose, 1}, {lclose, 2}]}, 
					 {erts_alloc_config},
					 %{erts_alloc_config, [make_config, save_scenario, state, stop]}, 
					 {ftp, [{start_service, 1}, {stop_service, 1}]}, 
					 {httpd_util, [{decode_hex, 1}, {encode_hex, 1}, {flatlength, 1}, {strip, 1}, 
					               {suffix, 1}, {hexlist_to_integer, 1}, {integer_to_hexlist, 1}]}]).

-define(removed_25, [{filename, [{safe_relative_path, 1}]}, 
                     {http_uri, [{parse, 1}, {parse, 2}, {scheme_defaults, 0}]}, 
					 {public_key, [{ssh_decode, 2}, {ssh_encode, 2}, {ssh_hostkey_fingerprint, 1},
					               {ssh_hostkey_fingerprint, 2}]}]).

-define(removed_24, [{igor}, {erl_tidy}, {pg2},
					 {filename, [find_src]},
					 {crypto, [{block_decrypt, 3}, {block_decrypt, 4}, {block_encrypt, 3}, {block_encrypt, 4},
					           {cmac, 3}, {cmac, 4}, {hmac, 3}, {hmac, 4}, {hmac_final, 1}, {hmac_final_n, 2},
							   {hmac_init, 2}, {hmac_update, 2}, next_iv, {poly1305, 2}, 
							   {stream_decrypt, 2}, {stream_encrypt, 2}, stream_init]},
					 {ssl, [{cipher_suites, 0}, {cipher_suites, 1}, ssl_accept]}]).

-define(removed_23, [{erlang, [{get_stacktrace, 0}]},
					 {htttpd_conf, [{check_enum, 2}, {clean, 1}, {custom_clean, 3}, {is_directory, 1},
					                {is_file, 1}, {make_integer, 1}]}]).

-define(removed_22, [{os_mon_mib}]).

-define(removed_20, [{asn1ct, [decode, encode]},
                     {erlang, [{hash, 2}]},
					 {ssl, [{connection_info, 1}, {negotiated_next_protocol, 1}]}]).

-define(removed_19, [{core_lib, [{get_anno, 1}, {is_literal, 1}, {is_literal_list, 1}, 
                                 {literal_value, 1}, {set_anno, 2}]},
                     {erl_lint, [{modify_line, 2}]},
					 {erl_parse, [{get_attribute, 2}, {get_attributes, 1}, {set_line, 2}]},
					 {erl_scan, [attributes_info, token_info, {set_attribute, 3}]},
					 {rpc, [{safe_multi_server_call, 2}, {safe_multi_server_call, 3}]}]).

-define(removed, ?removed_19++?removed_20++?removed_22++?removed_23++?removed_24++?removed_25++?removed_26).

-define(deprecated, ?deprecated_12++?deprecated_16++?deprecated_18++?deprecated_19++?deprecated_20++
                    ?deprecated_22++?deprecated_23++?deprecated_24++?deprecated_25++?deprecated_26).

-define(deprecated_26, [{dbg, [{stop_clear, 0}]},
						{file, [{pid2name, 1}]},
						{disk_log, [{inc_wrap_file, 1}]}]).
-define(deprecated_25, [{crypto, [{crypto_dyn_iv_init, 3}, {crypto_dyn_iv_update, 3}]},
						{ct_slave}, {slave}]).
-define(deprecated_24, [{erlang, [{phash, 2}]},
						{zlib, [{adler32, 2}, {adler32, 3}, {adler32_combine, 4}, 
						        {getBufSize, 1}, {setBufSize, 2},
						        {crc32, 1}, {crc32, 2}, {crc32, 3}, {crc32_combine, 4}, 
								{inflateChunk, 1}, {inflateChunk, 2}]}]).
-define(deprecated_23, [{http_uri, [{decode, 1}, {encode, 1}]},
						{httpd, [{parse_query, 1}]}]).					
-define(deprecated_22, [{net, [{broadcast, 3}, {call, 4}, {cast, 4}, {ping, 1}, {sleep, 1}]},
						{sys, [{get_debug, 3}]}]).					
-define(deprecated_20, [{crypto, [{rand_uniform, 2}]},
						{gen_fsm}]).
-define(deprecated_19, [{queue, [{lait, 1}]},
						{random}]).
-define(deprecated_18, [{erlang, [{now, 0}]}]).					
-define(deprecated_16, [{wxCalendarCtrl, [{enableYearChange, 1}, {enableYearChange, 2}]}]).
-define(deprecated_12, [{auth, [{cookie, 0}, {cookie, 1}, {is_auth, 1}, node_cookie]},
     					{calendar, [{local_time_to_universal_time, 1}]}]).

-define(os_call, [{os, [cmd, putenv]}]).
-define(port_creation_call_erl, [{erlang, [open_port]}]).
-define(nif_call, [{erlang, [load_nif]}]).
-define(port_driver_call, [{erl_ddll, [load, load_driver, reload, reload_driver, try_load]}]).
-define(file_operation, ?file_operation_eval ++ ?file_operation_read).
-define(file_operation_read, [{file, [consult, path_consult]}]).
-define(file_operation_eval, [{file, [eval, path_eval, path_script, script]}]).
-define(unstable_atom_call, [{erlang, [binary_to_atom, binary_to_term, list_to_atom]},
							 {http_uri, [parse]}]).
-define(crypto_call, [{crypto, [sign, verify]}]).
-define(old_crypto_api_call, [{crypto, [block_encrypt, block_decrypt, stream_init, stream_encrypt, 
							stream_decrypt, cmac, hmac, hmac_init, hmac_update, hmac_final, 
							hmac_final_n, poly1305]}]).
-define(compile_and_load_call, [{compile, [file, noenv_file]}, 
								{code, [atomic_load, load_abs, load_binary, load_file]}]).
-define(process_linkage_call, [{erlang, [link]}]).
-define(process_flag_call, [{erlang, [process_flag]}]).
-define(ets_traversal_call, [{ets, [first, next, {match, 3}, {match, 1}, {match_object, 3}, {match_object, 1}, {select,3}, {select, 1},
									slot]}]).
-define(network_call, [{net_kernel, [allow, connect_node, start]},
						{net_adm, [host_file, world]}]).
-define(xml_usage, [{xmerl_scan, [file, string]}, 
					{xmerl_sax_parser, [file, stream]}]).
-define(unsecure_communication, [{ssl, [connect, handshake, handshake_continue, listen, ssl_accept]}]).

%%% Helper for candidate detection
%%% 
-define(all, ?unsecure_communication ++ ?xml_usage ++ ?network_call ++ ?ets_traversal_call ++ ?process_flag_call
			 ++ ?process_linkage_call ++ ?compile_and_load_call ++ ?old_crypto_api_call ++ ?crypto_call 
			 ++ ?unstable_atom_call ++ ?file_operation_eval ++ ?file_operation_read ++ ?port_driver_call
			 ++ ?nif_call ++ ?port_creation_call_erl ++ ?os_call ++ ?deprecated 
			 ++ ?removed ++ ?removed_27 ++ ?removed_28).

-define(iterator_hofs, [{lists, map, 2}, {lists, foreach, 2}, {lists, foldl, 3}, {lists, foldr, 3},
						{lists, filter, 2}, {lists, filtermap, 2}, {lists, mapfoldl, 3}, {lists, mapfoldr, 3} ]).

get_candidate_forms() ->
	lists:uniq(get_candidates(?all, [?Query:all(?Fun:implicits(), ?Fun:applications()), ?Expr:clause(), ?Clause:form()])).

get_candidates() ->
	get_candidates(?all, [?Query:all(?Fun:implicits(), ?Fun:applications()), ?Expr:clause(), ?Clause:form(), ?Form:func()]).

get_candidates([{Mod} | Rest], Seq) -> 
	?Query:exec(?Query:seq([?Mod:find(Mod), ?Mod:locals() | Seq])) ++ get_candidates(Rest, Seq);
get_candidates([{Mod, [{Fun, Arity} | Funs]} | Rest], Seq) ->
	?Query:exec(?Query:seq([?Mod:find(Mod), ?Fun:find(Fun, Arity) | Seq])) ++ get_candidates([{Mod, Funs} | Rest], Seq);
get_candidates([{Mod, [Fun | Funs]} | Rest], Seq) ->
	?Query:exec(?Query:seq([?Mod:find(Mod), [{func, {name, '==', Fun}}] | Seq])) ++ get_candidates([{Mod, Funs} | Rest], Seq);
get_candidates([{_Mod, []} | Rest], Seq) ->
	get_candidates(Rest, Seq);
get_candidates([], _) ->
	[].

get_vulnerabilities(Fun) ->
	[
	%% Injection
	 #{type=> unsafe_os,             value => get_calls_for_os(Fun)},
	 #{type=> unsafe_port,           value => get_calls_for_port(Fun)},
	 #{type=> unsafe_nif,            value => get_calls_for_nif(Fun)},
	 #{type=> unsafe_port_driver,    value => get_calls_for_port_driver(Fun)},
	 #{type=> usnsafe_compile_load,  value => unsecure_compile_and_load_operations(Fun)},
	 #{type=> unsafe_file_eval,      value => get_calls_for_file(Fun, ?file_operation_eval)},
    %% DoS
	 #{type=> unsafe_atom,           value => get_calls_for_unstable_funs(Fun)},
	 #{type=> unsafe_xml,            value => get_unsecure_xml_usage(Fun)},
	 #{type=> unsafe_file_read,      value => get_calls_for_file(Fun, ?file_operation_read)},
    %% Race condition vulnerability, TOCTTOU, time-of-check to time-of-use
	 #{type=> unsafe_prioritisation, value => unsecure_prioritization(Fun)}, %?DoS
	 #{type=> unsafe_linking,        value => unsecure_process_linkage(Fun)},
	 #{type=> unsafe_ets_calls,      value => unsecure_ets_traversal(Fun)},
    %% Deprecated calls
	 #{type=> deprecated,            value => get_calls_for(Fun, ?deprecated)}, %% crypto???
     #{type=> to_be_removed_28,      value => get_calls_for(Fun, ?removed_28)}, 
     #{type=> to_be_removed_27,      value => get_calls_for(Fun, ?removed_27)}, 
     #{type=> removed_26,            value => get_calls_for(Fun, ?removed_26)},
     #{type=> removed_25,            value => get_calls_for(Fun, ?removed_25)},
     #{type=> removed_24,            value => get_calls_for(Fun, ?removed_24)},
     #{type=> removed_23,            value => get_calls_for(Fun, ?removed_23)},
     #{type=> removed_22,            value => get_calls_for(Fun, ?removed_22)},
     #{type=> removed_20,            value => get_calls_for(Fun, ?removed_20)},
     #{type=> removed_19,            value => get_calls_for(Fun, ?removed_19)},
	%% MITM (Man-in-the-middle)
	 #{type=> unsafe_crypto,         value => get_calls_for_old_crypto_api(Fun)},
	 #{type=> unsafe_network,        value => unsafe_network(Fun)}, %% net_kernel, net_adm
	 #{type=> unsafe_communication,  value => check_unsecure_communication(Fun)} %% ssl
	].

get_unsecure_calls(Fun) ->
	Result = [ get_calls_for_commons(Fun), get_calls_for_port(Fun), get_unsecure_concurrent_calls(Fun),
			get_calls_for_unstable_funs(Fun), unsecure_compile_and_load_operations(Fun), 
			get_calls_for_old_crypto_api(Fun), get_unsecure_xml_usage(Fun), 
			check_unsecure_communication(Fun), get_calls_for_deprecated(Fun),
			get_calls_for_removed(Fun), get_calls_for_to_be_removed(Fun)],
	lists:flatten(Result).

get_unsecure_interoperability(Fun) ->
	lists:flatten([ get_calls_for_port(Fun), get_calls_for_nif(Fun), get_calls_for_port_driver(Fun) ]).

get_unsecure_concurrent_calls(Fun) ->
	lists:flatten([ unsecure_process_linkage(Fun), unsecure_prioritization(Fun), unsecure_ets_traversal(Fun) ]).

get_calls_for_commons(Fun) ->
	Result = [get_calls_for_os(Fun), get_calls_for_nif(Fun), get_calls_for_port_driver(Fun),
			  get_calls_for_file(Fun), unsafe_network(Fun)],
	lists:flatten(Result).
	% Types = ?os_call ++ ?nif_call ++ ?port_driver_call ++ ?file_operation ++ ?network_call,
	% {_Impl, DataFlowTuples} = analyze_funs(Fun, Types, fun get_all_children_for/1),
	% NonSafeDataFlowTuples = get_unsafe_funs(DataFlowTuples),
	% lists:uniq(get_funs_without_body(NonSafeDataFlowTuples) ++ get_exported_funs(NonSafeDataFlowTuples)).

get_calls_for_deprecated(Fun) ->
	get_calls_for(Fun, ?deprecated). 

get_calls_for_removed(Fun) ->
	get_calls_for(Fun, ?removed). 

get_calls_for_to_be_removed(Fun) ->
	get_calls_for(Fun, ?removed_27++?removed_28). 

get_calls_for_os_tuple(Fun) ->
	Res = get_calls_for_os(Fun),
	Msg = "Use validators for the external commands, or use open_port/2: https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/external_executables",
	[{E, Msg} || E <-Res].

get_calls_for_os(Fun) ->
	{Impl, DataFlowTuples} = analyze_funs(Fun, ?os_call, fun get_all_children_for/1),
	NonSafeDataFlowTuples = get_unsafe_funs(DataFlowTuples),
	UnsafeApps = get_funs_without_body(NonSafeDataFlowTuples) ++ get_exported_funs(NonSafeDataFlowTuples) 
					++ get_fun_expression_patterns(NonSafeDataFlowTuples),
	Impl ++ lists:uniq(UnsafeApps).
	
get_calls_for_port_tuple(Fun) ->
	Res = get_calls_for_port(Fun),
	Msg = "Validate the spawned command and its arguments: https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/external_executables",
	[{E, Msg} || E <-Res].

get_calls_for_port(Fun) ->
	{Impl, DataFlowTuples} = analyze_funs(Fun, ?port_creation_call_erl, fun get_children_for_port/1),
	NonSafeDataFlowTuples = get_unsafe_funs(DataFlowTuples),
	UnsafeApps = get_funs_without_body(NonSafeDataFlowTuples) ++ get_exported_funs(NonSafeDataFlowTuples) 
					++ get_fun_expression_patterns(NonSafeDataFlowTuples),
	Impl ++ lists:uniq(UnsafeApps).

get_calls_for_nif(Fun) ->
	{Impl, DataFlowTuples} = analyze_funs(Fun, ?nif_call, fun get_all_children_for/1),
	NonSafeDataFlowTuples = get_unsafe_funs(DataFlowTuples),
	UnsafeApps = get_funs_without_body(NonSafeDataFlowTuples) ++ get_exported_funs(NonSafeDataFlowTuples) 
					++ get_fun_expression_patterns(NonSafeDataFlowTuples),
	Impl ++ lists:uniq(UnsafeApps).

get_calls_for_port_driver(Fun) ->
	{Impl, DataFlowTuples} = analyze_funs(Fun, ?port_driver_call, fun get_all_children_for/1),
	NonSafeDataFlowTuples = get_unsafe_funs(DataFlowTuples),
	UnsafeApps = get_funs_without_body(NonSafeDataFlowTuples) ++ get_exported_funs(NonSafeDataFlowTuples) 
					++ get_fun_expression_patterns(NonSafeDataFlowTuples),
	Impl ++ lists:uniq(UnsafeApps).

get_calls_for_file(Fun) ->
	get_calls_for_file(Fun, ?file_operation).

get_calls_for_file(Fun, FileOp) ->
	{Impl, DataFlowTuples} = analyze_funs(Fun, FileOp, fun get_all_children_for/1),
	NonSafeDataFlowTuples = get_unsafe_funs(DataFlowTuples),
	UnsafeApps = get_funs_without_body(NonSafeDataFlowTuples) ++ get_exported_funs(NonSafeDataFlowTuples) 
					++ get_fun_expression_patterns(NonSafeDataFlowTuples),
	Impl ++ lists:uniq(UnsafeApps).

get_calls_for_unstable_funs_tuple(Fun) ->
	Res = get_calls_for_unstable_funs(Fun),
	Msg = "Atom creation might overload the atom table and lead to DoS vulnerability: https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/atom_exhaustion",
	[{E, Msg} || E <-Res].

get_calls_for_unstable_funs(Fun) ->
	{Impl, DataFlowTuples} = analyze_funs(Fun, ?unstable_atom_call, fun get_all_children_for/1),
	NonSafeDataFlowTuples = get_unsafe_funs(DataFlowTuples),
	FunsWithSafeOption = [ {O, {P, D}} || {O, {P, D}} <- NonSafeDataFlowTuples, 
								contains_specific_options(D, fun get_options/1, fun safe_options/1) ],
	FunsWithoutSafeOption = NonSafeDataFlowTuples -- FunsWithSafeOption,
	% References = [I || I <- Impl, {_Mod, MFA} <- check_reference(I), lists:member(MFA, ?iterator_hofs)],
	UnsafeApps = lists:uniq(get_funs_without_body(FunsWithoutSafeOption) ++ get_exported_funs(FunsWithoutSafeOption) 
							  ++ get_fun_expression_patterns(FunsWithoutSafeOption)),
	Impl ++ UnsafeApps.

safe_options(safe) ->
	true;
safe_options({scheme_validation_fun, _}) ->
	true;
safe_options(_Arg1) ->
	false. 

get_calls_for_old_crypto_api(Fun) ->
	get_calls_for(Fun, lists:uniq(?old_crypto_api_call ++ ?crypto_call)).

%% TODO: implicit and fun expression check!
get_calls_for_old_crypto_api_help(Fun) ->
	Expressions = get_calls_for(Fun, lists:uniq(?old_crypto_api_call ++ ?crypto_call)),
	ExprFunTuple = [ {E, ?Fun:name(hd(?Query:exec(E, ?Expr:function())))} || E <- Expressions ],
	[ {E, string:concat("Please use the following functions instead: ", 
							string:join(handle_old_crypto_api(F), ", "))} 
				|| {E, F} <- ExprFunTuple ].

handle_old_crypto_api(Name) ->
	case Name of
		block_encrypt -> ["crypto_one_time", "crypto_one_time_aead",
					      "crypto_init", "crypto_update",
					      "crypto_dyn_iv_init", "crypto_dyn_iv_update"];
		block_decrypt -> ["crypto_one_time", "crypto_one_time_aead",
					      "crypto_init", "crypto_update",
					      "crypto_dyn_iv_init", "crypto_dyn_iv_update"];
		stream_init -> ["crypto_one_time", "crypto_one_time_aead",
					      "crypto_init", "crypto_update",
					      "crypto_dyn_iv_init", "crypto_dyn_iv_update"];
		stream_encrypt -> ["crypto_one_time", "crypto_one_time_aead",
					      "crypto_init", "crypto_update", 
					      "crypto_dyn_iv_init", "crypto_dyn_iv_update"];
		stream_decrypt -> ["crypto_one_time", "crypto_one_time_aead",
					      "crypto_init", "crypto_update",
					      "crypto_dyn_iv_init", "crypto_dyn_iv_update"];
		cmac -> ["mac", "macN"];
		hmac -> ["mac", "macN"];
		hmac_init -> ["mac_init"];
		hmac_update -> ["mac_update"];
		hmac_final -> ["mac_final"];
		hmac_final_n -> ["mac_finalN"];
		poly1305 -> ["mac", "macN"];
		sign -> ["public_key:sign"];
		verify -> ["public_key:verify"];
		_Else -> []
	end.

unsecure_compile_and_load_operations(Fun) ->
	{Impl, DataFlowTuples} = analyze_funs(Fun, ?compile_and_load_call, fun get_all_children_for/1),
	NonSafeDataFlowTuples = get_unsafe_funs(DataFlowTuples),
	UnsafeApps = get_funs_without_body(NonSafeDataFlowTuples) ++ get_exported_funs(NonSafeDataFlowTuples) 
					++ check_for_loaded_modules(NonSafeDataFlowTuples) 
					++ get_fun_expression_patterns(NonSafeDataFlowTuples),
	Impl ++ lists:uniq(UnsafeApps).


%% TODO: implicit + fun_expr handler
unsecure_process_linkage(Fun) ->
	{_Impl, DataFlowTuples} = analyze_funs(Fun, ?process_linkage_call, fun get_all_children_for/1),
	ConvertedFunParamTuples = [ {O, ?Query:exec(D, ?Expr:functions())} || {O, {_, D}} <- DataFlowTuples ],
	[ O || {O, P} <- ConvertedFunParamTuples, P =/= [] andalso 
			(?Mod:name(hd(?Query:exec(P, ?Fun:module()))) == erlang) andalso 
			(?Fun:name(hd(P)) == spawn) ].

unsecure_prioritization_tuple(Fun) ->
	Res = unsecure_prioritization(Fun),
	Msg = "Priority level max is reserved for internal use in the Erlang runtime system, and is not to be used by others: https://www.erlang.org/doc/man/erlang.html#process_flag-2",
	[{E, Msg} || E <-Res].

%% TODO: implicit + fun_expr handler
unsecure_prioritization(Fun) ->
	{_Impl, DataFlowTuples} = analyze_funs(Fun, ?process_flag_call, fun get_all_children_for/1),
	ConvertedFunParamTuples = [ {O, get_term_of_params(D)} || {O, {_, D}} <- DataFlowTuples ],
	[ O || {O, P} <- ConvertedFunParamTuples, P =/= [] andalso lists:member(priority, P) ].


%% TODO: implicit + fun_expr handler
unsecure_ets_traversal(Fun) ->
	MatchingExpressions = get_calls_for(Fun, ?ets_traversal_call),
	FunParamTuples = [ {E, get_expression_params_for(E, ?ets_traversal_call, fun get_all_children_for/1)} 
					|| E <- MatchingExpressions ],
	DataFlowTuples = get_origins(FunParamTuples, [{back, true}]),
	Functions = [ {O, {P, ?Query:exec(D, ?Query:seq([?Expr:clause(), ?Clause:form(), ?Form:func()]))}}
					|| {O, {P, D}} <- DataFlowTuples ],
	FunctionCalls = [ {O, {P, get_embedded_funs(F)}} || {O, {P, F}} <- Functions ],
	%FunctionCalls = [ {O, {P, lists:usort(get_embedded_funs2(F))}} || {O, {P, F}} <- Functions ],
	SafeFixTableExpressions = [ {O, {P, get_calls_for(F, [{ets, [safe_fixtable]}])}} 
					|| {O, {P, F}} <- FunctionCalls ],
	SafeFixTableExprWithValues = [ {O, {P, get_expression_params_for(F, [{ets, [safe_fixtable]}], 
					fun get_all_children_for/1)}} || {O, {P, F}} <- SafeFixTableExpressions ],
	IsTrue = fun(E) -> E == true end,
	MatchingExprWithSafeFixTableExpr = [ O || {O, {_, F}} <- SafeFixTableExprWithValues, 
					lists:any(IsTrue, lists:flatten(check_for_params(F))) ],
	[ E || E <- MatchingExpressions, not lists:member(E, MatchingExprWithSafeFixTableExpr) ].

get_embedded_funs(Funs) ->
	get_embedded_funs(Funs, Funs).

get_embedded_funs([], Res) -> Res;
get_embedded_funs(Funs, Res) ->
	FunctionCalls = ?Query:exec(Funs, ?Query:all([?Fun:funcalls(), ?Dynfun:dyncalls()])),
	Result = [ F || F <- FunctionCalls, not lists:member(F, Res)],
	get_embedded_funs(Result, lists:uniq(Res ++ Result)).

check_for_params(ParamList) ->
	[ get_term_of_params(A2) || [_|A2] <- ParamList ].

unsafe_network(Fun) ->
	{Impl, DataFlowTuples} = analyze_funs(Fun, ?network_call, fun get_all_children_for/1),
	NonSafeDataFlowTuples = get_unsafe_funs(DataFlowTuples),
	UnsafeApps = get_funs_without_body(NonSafeDataFlowTuples) ++ get_exported_funs(NonSafeDataFlowTuples) 
					++ get_fun_expression_patterns(NonSafeDataFlowTuples),
	Impl ++ lists:uniq(UnsafeApps).

get_unsecure_xml_usage(Fun) ->
	{Impl, DataFlowTuples} = analyze_funs(Fun, ?xml_usage, fun get_all_children_for/1),
	NonSafeDataFlowTuples = get_unsafe_funs(DataFlowTuples),
	FunsWithSafeOption = [ {O, {P, D}} || {O, {P, D}} <- NonSafeDataFlowTuples, 
								contains_specific_options(D, fun get_options_for_xml/1, 
										fun entity_expansion_check/1) ],
	FunsWithoutSafeOption = NonSafeDataFlowTuples -- FunsWithSafeOption,
	UnsafeApps = get_funs_without_body(FunsWithoutSafeOption) ++ get_exported_funs(FunsWithoutSafeOption) 
					++ get_fun_expression_patterns(FunsWithoutSafeOption),
	Impl ++ lists:uniq(UnsafeApps).

get_options_for_xml(Expr) ->
	case ?Expr:type(Expr) of 
		tuple -> 
			lists:zip(get_term_of_params(?Query:exec(Expr, ?Expr:child(1))), 
					?Query:exec(Expr, ?Expr:child(2)));
		_ ->
			Expr
	end.

entity_expansion_check({event_fun, EventFun}) ->
	entity_expansion_check_(EventFun);
entity_expansion_check(_Node) ->
	false.
	% Todo: check whether this is needed
	%entity_expansion_check_(Node).

entity_expansion_check_(EventFun) ->
	DataFlow = ?Dataflow:reach_1st([EventFun], [{back, true}]),
	TupleExprs = [ ?Query:exec(D, ?Expr:clauses()) || D <- DataFlow ],
	OptionTuples = [ ?Dataflow:reach_1st(?Query:exec(T, ?Clause:pattern(1)), [{back, false}]) 
					|| T <- lists:flatten(TupleExprs), T =/= [] ],
	TupleValues = [ get_term_of_params(?Query:exec(T, ?Expr:child(1)))
					|| T <- lists:flatten(OptionTuples) ],
	check_for_entity_event(lists:flatten(TupleValues)).

check_for_entity_event([internalEntityDecl|_]) ->
	true;
check_for_entity_event([externalEntityDecl|_]) ->
	true;
check_for_entity_event([_|L]) ->
	check_for_entity_event(L);
check_for_entity_event([]) ->
	false.

%% TODO: implicit + fun_expr handler
check_unsecure_communication(Fun) ->
	{_Impl, DataFlowTuples} = analyze_funs(Fun, ?unsecure_communication, fun get_all_children_for/1),
	NonSafeDataFlowTuples = get_unsafe_funs(DataFlowTuples),
	[ O || {O, {_, D}} <- NonSafeDataFlowTuples, 
					contains_specific_options(D, fun get_options/1, fun unsafe_options/1) ].

contains_specific_options(DataFlow, OptionGetter, OptionChecker) ->
	TupleExprs = [ ?Query:exec(D, ?Expr:children()) || D <- DataFlow ],
	OptionTuples = [ ?Query:exec(T, ?Expr:children()) || T <- lists:flatten(TupleExprs) ],
	TupleValues = [ OptionGetter(T) || T <- lists:flatten(OptionTuples) ],
	UnsafeOptions = [ OptionChecker(T) || T <- lists:flatten(TupleValues) ],
	IsTrue = fun(E) -> E == true end,
	lists:any(IsTrue, UnsafeOptions).

get_options(Expr) ->
	case {?Expr:type(Expr), ?Query:exec(Expr, ?Expr:children())} of 
		{tuple, [Ch1, Ch2]} -> 
			[{?Expr:value(Ch1), ?Expr:value(Ch2)}];
		{atom, _} ->
			?Expr:value(Expr);
		_ ->
			Expr
	end.

unsafe_options({padding_check, false}) ->
	true;
unsafe_options({beast_mitigation, disabled}) ->
	true;
unsafe_options({fallback, true}) ->
	true;
unsafe_options({dh, _}) ->
	true;
unsafe_options({dhfile, _}) ->
	true;
unsafe_options({client_renegotiation, true}) ->
	true;
unsafe_options({verify, verify_none}) ->
	true;
unsafe_options({crl_check, false}) ->
	true;
unsafe_options({_, _}) ->
	false;
unsafe_options(_) ->
	false.

analyze_funs(Fun, Type, FunDefForParam) ->
	FunExpressions = get_calls_for(Fun, Type),
	{Impl, Apps} = lists:partition(fun(Node) -> ?Expr:type(Node) == implicit_fun end, FunExpressions),
	FunParamTuples = [ {E, get_expression_params_for(E, Type, FunDefForParam)} || E <- Apps ],
	{Impl, get_origins(FunParamTuples, [{back, true}])}.

get_calls_for(Fun, ModFnListTuples) ->
	Expressions = ?Query:exec(Fun, ?Query:seq([?Fun:definition(), ?Form:clauses(), ?Clause:exprs(), ?Expr:deep_sub()])),
	Tuples = [ {E, ?Query:exec(E, ?Expr:function())} || E <- Expressions ], 
		%% TODO: does this reduce the function's list to a singleton?
		%% if so, get_functios_for can be simplified
	get_functions_for(Tuples, ModFnListTuples).

get_origins(FunParamTuples, Opt) ->
	%% TODO: make it recursive
	FirstDataFlowTuples = [ {O, {P, ?Dataflow:reach_1st(P, Opt)}} || {O, P} <- FunParamTuples ],
		%?d(FirstDataFlowTuples),
	SelDataFlowTuples = [ {O, {P, {D, ?Query:exec(D, ?Query:any([[{sel, back}], [{sel_e, back}], [{cons_e, back}], [cons_back]]))}}} 
				|| {O, {P, D}} <- FirstDataFlowTuples ],
	SecondDataFlowTuples = [ {O, {P, lists:uniq(D ++ ?Dataflow:reach_1st(S, Opt))}} 
				|| {O, {P, {D, S}}} <- SelDataFlowTuples ],
	SecondSelDataFlowTuples = [ {O, {P, {D, ?Query:exec(D, ?Query:any([[{sel, back}], [{sel_e, back}], [{cons_e, back}], [cons_back]]))}}} 
				|| {O, {P, D}} <- SecondDataFlowTuples ],
	[ {O, {P, lists:uniq(D ++ ?Dataflow:reach_1st(S, Opt))}} 
				|| {O, {P, {D, S}}} <- SecondSelDataFlowTuples ].

get_expression_params_for([], _, _) -> [];
get_expression_params_for(ExprList, Type, FunDef) when is_list(ExprList) ->
	[ get_expression_params_for(Expr, Type, FunDef) || Expr <- ExprList ];
get_expression_params_for(Expr, Type, FunDef) ->
	case ?Expr:type(Expr) of 
		% this check might not needed now: see changes in get_calls_for...
		application ->
			SubExpr = ?Query:exec(Expr, ?Expr:deep_sub()),
			FilteredSubExpr = [ Sub || Sub <- SubExpr, ?Expr:type(Sub) =:= application andalso (is_same_fun(Sub, Type)) ],
			FunDef(FilteredSubExpr);
		match_expr ->
			get_expression_params_for(hd(?Query:exec(Expr, ?Expr:child(2))), Type, FunDef);
		cons ->
			?Query:exec(Expr, ?Query:all(?Query:seq(?Expr:child(1), ?Expr:children()), ?Expr:child(2)));
		_ ->
			?Query:exec(Expr, ?Expr:children())
%%TODO: check this
	end.

is_same_fun(Expr, ModFnListTuples) ->
	Funs = ?Query:exec(Expr, ?Expr:function()),
	is_funs_equals(Funs, ModFnListTuples).

get_children_for_port(Expr) ->
	?Query:exec(Expr, ?Query:seq([?Expr:child(2),
							?Expr:child(1),
							?Expr:child(2)])).

get_all_children_for(Expr) ->
	?Query:exec(Expr, ?Query:seq([?Expr:child(2), ?Expr:children()])).


%% do not have added value, if a constant name is used by the developer, that must be intentional	
check_for_loaded_modules(Tuples) ->
	LoadedModules =  [ M || {M, _} <- code:all_loaded() ],
	IsTrue = fun(E) -> E == true end,
	[ O || {O, {_, D}} <- Tuples, 
				lists:any(IsTrue, is_member(get_term_of_params(D), LoadedModules)) ].

get_fun_expression_patterns(Tuples) -> 
	[ O|| {O, {_P, D}} <- Tuples, Cl <- ?Query:exec(D, [top, {pattern, back}]),  ?Clause:type(Cl) == funexpr ].

get_funs_without_body([]) -> [];
get_funs_without_body(Tuples) ->
	Functions = [ {O, {P, ?Query:exec(D, ?Expr:functions())}} || {O, {P, D}} <- Tuples ],
	FunctionBodies = [ 
		begin
			%LeadsTo = {?Syn:flat_text(O), [ element(2, ?Fun:mod_fun_arity(F)) || F <- Fun]},
			%?d(LeadsTo),
			{O, {P, [ ?Query:exec(F, ?Query:seq([?Fun:definition(),
												?Form:clauses(), 
								  				?Clause:body()])) || F <- Fun ]}}
		end
		|| {O, {P, Fun}} <- Functions, Fun =/= [] ],
	FunctionWithoutBody = [ O || {O, {_, B}} <- FunctionBodies, lists:member([], B) ],
	OriginalDataFlows = [ ?Dataflow:reach_1st(P, [{back, true}]) || {_, {P, _}} <- Tuples ],
	IsMember = fun(E) -> lists:member(E, lists:flatten(OriginalDataFlows)) end,
	FunctionWithBodies = [ {O, {P, lists:filter(IsMember, B)}} || {O, {P, B}} <- FunctionBodies, B =/= [] ],
	lists:uniq(FunctionWithoutBody ++ get_funs_without_body(FunctionWithBodies)).

get_exported_funs(Tuples) -> 
	Functions = [ {O, {P, ?Query:exec(D, [top, {pattern, back}, {funcl, back}, fundef]), D}} 
			  || {O, {P, D}} <- Tuples ],
	[ begin
		%IFace = {?Syn:flat_text(O), 
		%		  [element(2, ?Fun:mod_fun_arity(FF)) || FF <- F, ?Fun:is_exported(FF)]},
		%?d(IFace),
		O
	  end || {O, {_P, F, _D}} <- Functions, ?Fun:is_exported(F) ].			
	% Functions = [ {O, {P, ?Query:exec(D, ?Query:seq([?Expr:clause(), 
	% 						 ?Clause:form(), 
	% 						 ?Form:func()]))}} 
	% 		  || {O, {P, D}} <- Tuples ],
	% ExportedFuns = [ {O, {P, F}} || {O, {P, F}} <- Functions, ?Fun:is_exported(F) ],
	% FuncWithParam = [ {O, {get_term_of_params(P), 
	% 		       get_term_of_params(?Query:exec(F, ?Query:seq([?Fun:definition(),
	% 								     ?Form:clauses(),
	% 								     ?Clause:patterns()])))}} 
	% 		  || {O, {P, F}} <- ExportedFuns ],
	% IsTrue = fun(E) -> E == true end,
	% [ O || {O, {P, A}} <- FuncWithParam, lists:any(IsTrue, is_member(P, A)) ].

get_unsafe_funs(Tuples) ->
	Functions = [ {O, ?Query:exec(D, ?Expr:functions())} || {O, {_, D}} <- Tuples ],
	SafeFuns = lists:flatten(?Syn:get_env(safe_funs)),
	NonSafeFuns = [ E || {E, F} <- Functions, not is_funs_equals(F, SafeFuns)],	
	[ {E, {P, D}} || {E, {P, D}} <- Tuples, lists:member(E, lists:flatten(NonSafeFuns)) ].

is_member(P1, P2) ->
	[ lists:member(Item, P2) || Item <- P1, Item =/= undefined ].

get_term_of_params(Params) ->
	[ ?Expr:value(P) || P <- Params ].

get_functions_for(Tuples, ModFnListTuples) ->
	lists:foldl(fun({E, FunList}, Acc) ->
					   case is_funs_equals(FunList, ModFnListTuples) of
						   true -> [E | Acc];
						   _ -> Acc
					   end
				end, [], Tuples).

is_funs_equals(Funs, ModFnListTuples) -> 
	lists:any(fun(Fun) -> 
					{_, {M, F, A}} = ?Fun:mod_fun_arity(Fun),
					lists:any(fun({Mod}) -> M == Mod;
						         ({Mod, VulnFunList}) -> (M == Mod) andalso (lists:member(F, VulnFunList) orelse lists:member({F,A}, VulnFunList))
				              end, ModFnListTuples)
			  end, Funs).

check_reference(Node) ->
	Reach = ?Dataflow:reach_1st([Node], [{back, false}]),
		                   %% inner check might needed: is the parent an application?
	Usages = [?Query:exec(R, ?Query:seq([?Expr:parent(), ?Expr:parent(), ?Expr:function()])) || R <- Reach], 
	Called = [Node || R <- Reach, Parent <- ?Query:exec(R, ?Expr:parent()), ?Expr:type(Parent) == application],
	{[?Fun:mod_fun_arity(F) || F <- lists:uniq(lists:flatten(Usages))], Called}.

severity_level(App) ->
	severity_level(App, ?Expr:type(App)).

severity_level(App, implicit_fun) ->
	case check_reference(App) of %[App || {_Mod, MFA} <- check_reference(App), lists:member(MFA, ?iterator_hofs)] of
		{[], []} -> low;
		{[], _} -> moderate;
		{References, _} ->
			case [App || {_Mod, MFA} <- References, lists:member(MFA, ?iterator_hofs)] of
				[] -> moderate;
				_ -> high
			end
	end;
severity_level(App, _) ->	
	Fun = ?Query:exec1(App, ?Expr:function(), bad_application),
	case ?Fun:mod_fun_arity(Fun) of
		{_, {erlang, list_to_atom, _}} ->
			check_iteration(App);
		{_, {erlang, binary_to_atom, _}} ->
			check_iteration(App);
		{_, {erlang, binary_to_term, _}} ->
			moderate;
		{_, {http_uri, parse, 1}} ->
			high;
		{_, {os, cmd, 1}} ->
			high;
		{_, {erlang, now, 0}} ->
			moderate;
		{_, {erlang, get_stacktrace, 0}} ->
			high;
		{_, MFA} -> 
			case deprecated(MFA) of
				true -> moderate;
			    _ -> high %not_sepcified
			end
	end.

deprecated({M, F, A}) ->
	lists:any(fun({Mod}) -> M == Mod;
				 ({Mod, VulnFunList}) -> (M == Mod) andalso (lists:member(F, VulnFunList) orelse lists:member({F,A}, VulnFunList))
			  end, ?deprecated).

check_iteration(Expr)->	
	case ?Query:exec(Expr, ?Query:seq([?Expr:top(), ?Expr:parent()])) of
		[TopParent] -> 
			case ?Expr:type(TopParent) of
				list_comp -> high;
				fun_expr -> 	
					References = [?Fun:mod_fun_arity(F) || F <- ?Query:exec(TopParent, ?Query:seq([?Expr:parent(), ?Expr:parent(), ?Expr:function()]))],
					case [MFA || {_Mod, MFA} <- References, lists:member(MFA, ?iterator_hofs)] of
						[] -> low;
						_  -> high
					end;
				_ -> 
					check_iteration(TopParent)
			end;
		[] ->  
			F = ?Query:exec1(Expr, ?Query:seq([?Expr:clause(), ?Clause:form(), ?Form:func()]), bad_application),
			check_iteration([F], [])
	end.

	% LComp = ?Query:exec(App, [top, {body, back}, {exprcl, back}]),
	% ?d(LComp),
	% case LComp of
	% 	[Expr] ->  check_compr(Expr, ?Expr:type(Expr));
	% 	_ -> check_iteration([F], [])
	% end.

% check_compr(_E, list_comp) ->
% 	high;
% check_compr(Expr, _Type) ->
% 	?d(Expr),
% 	References = [?Fun:mod_fun_arity(F) || F <- ?Query:exec(Expr, ?Query:seq([?Expr:parent(), ?Expr:parent(), ?Expr:function()]))],
% 	case [MFA || {_Mod, MFA} <- References, lists:member(MFA, ?iterator_hofs)] of
% 		[] -> low;
% 		_  -> high
% 	end.

%% TODO: think about F is calle somewhere in a list comp or in a HOF
check_iteration([], _) -> low;
check_iteration([Fun | Funs], Visited) ->
	case refusr_metrics:is_tail_recursive({function,Fun}) of
		-1 -> 
			Callers = ?Query:exec(Fun, ?Fun:called()) -- Visited,
			check_iteration(Funs ++ Callers, [Fun|Visited]);
		_ -> high
	end.	


% get_first_param(App) -> 
% 	?Query:exec(App, ?Query:seq([?Expr:child(2), ?Expr:child(1)])).

% get_second_param(App) ->
% 	?Query:exec(App, ?Query:seq([?Expr:child(2), ?Expr:child(2)])).		

% get_third_param(App) ->
% 	?Query:exec(App, ?Query:seq([?Expr:child(2), ?Expr:child(3)])).	
