<commit_before>%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2005-2013. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%

%%
%% Description: a gen_server implementing a simple
%% terminal (using the group module) for a CLI
%% over SSH

-module(ssh_cli).

-behaviour(ssh_daemon_channel).

-include("ssh.hrl").
-include("ssh_connect.hrl").

%% ssh_channel callbacks
-export([init/1, handle_ssh_msg/2, handle_msg/2, terminate/2]).

%% backwards compatibility
-export([listen/1, listen/2, listen/3, listen/4, stop/1]).

%% state
-record(state, {
	  cm,
	  channel,
	  pty,
	  group,
	  buf,
	  shell,
	  exec
	 }).

%%====================================================================
%% ssh_channel callbacks
%%====================================================================

%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} 
%%                        
%% Description: Initiates the CLI
%%--------------------------------------------------------------------
init([Shell, Exec]) ->
    {ok, #state{shell = Shell, exec = Exec}};
init([Shell]) ->
    {ok, #state{shell = Shell}}.

%%--------------------------------------------------------------------
%% Function: handle_ssh_msg(Args) -> {ok, State} | {stop, ChannelId, State}
%%                        
%% Description: Handles channel messages received on the ssh-connection.
%%--------------------------------------------------------------------
handle_ssh_msg({ssh_cm, _ConnectionManager, 
		{data, _ChannelId, _Type, Data}}, 
	       #state{group = Group} = State) ->
    Group ! {self(), {data, binary_to_list(Data)}},
    {ok, State};

handle_ssh_msg({ssh_cm, ConnectionManager, 
		{pty, ChannelId, WantReply, 
		 {TermName, Width, Height, PixWidth, PixHeight, Modes}}}, 
	       State0) ->
    State = State0#state{pty = 
			 #ssh_pty{term = TermName,
				  width =  not_zero(Width, 80),
				  height = not_zero(Height, 24),
				  pixel_width = PixWidth,
				  pixel_height = PixHeight,
                  modes = Modes},
             buf = empty_buf()},
    set_echo(State),
    ssh_connection:reply_request(ConnectionManager, WantReply, 
				 success, ChannelId),
    {ok, State};

handle_ssh_msg({ssh_cm, ConnectionManager, 
	    {env, ChannelId, WantReply, _Var, _Value}}, State) ->
    ssh_connection:reply_request(ConnectionManager, 
				 WantReply, failure, ChannelId),
    {ok, State};

handle_ssh_msg({ssh_cm, ConnectionManager,
	    {window_change, ChannelId, Width, Height, PixWidth, PixHeight}},
	   #state{buf = Buf, pty = Pty0} = State) ->
    Pty = Pty0#ssh_pty{width = Width, height = Height,
		       pixel_width = PixWidth,
		       pixel_height = PixHeight},
    {Chars, NewBuf} = io_request({window_change, Pty0}, Buf, Pty),
    write_chars(ConnectionManager, ChannelId, Chars),
    {ok, State#state{pty = Pty, buf = NewBuf}};

handle_ssh_msg({ssh_cm, ConnectionManager, 
	    {shell, ChannelId, WantReply}}, State) ->
    NewState = start_shell(ConnectionManager, State),
    ssh_connection:reply_request(ConnectionManager, WantReply, 
				 success, ChannelId),
    {ok, NewState#state{channel = ChannelId,
			cm = ConnectionManager}};

handle_ssh_msg({ssh_cm, ConnectionManager, 
		{exec, ChannelId, WantReply, Cmd}}, #state{exec=undefined} = State) ->
    {Reply, Status} = exec(Cmd),
    write_chars(ConnectionManager, 
		ChannelId, io_lib:format("~p\n", [Reply])),
    ssh_connection:reply_request(ConnectionManager, WantReply, 
				 success, ChannelId),
    ssh_connection:exit_status(ConnectionManager, ChannelId, Status),
    ssh_connection:send_eof(ConnectionManager, ChannelId),
    {stop, ChannelId, State#state{channel = ChannelId, cm = ConnectionManager}};
handle_ssh_msg({ssh_cm, ConnectionManager,
		{exec, ChannelId, WantReply, Cmd}}, State) ->
    NewState = start_shell(ConnectionManager, Cmd, State),
    ssh_connection:reply_request(ConnectionManager, WantReply,
				 success, ChannelId),
    {ok, NewState#state{channel = ChannelId,
			cm = ConnectionManager}};

handle_ssh_msg({ssh_cm, _ConnectionManager, {eof, _ChannelId}}, State) ->
    {ok, State};

handle_ssh_msg({ssh_cm, _, {signal, _, _}}, State) ->
    %% Ignore signals according to RFC 4254 section 6.9.
    {ok, State};

handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, Error, _}}, State) ->
    Report = io_lib:format("Connection closed by peer ~n Error ~p~n",
			   [Error]),
    error_logger:error_report(Report),
    {stop, ChannelId,  State};

handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, 0}}, State) ->
    {stop, ChannelId, State};

handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, Status}}, State) ->
    
    Report = io_lib:format("Connection closed by peer ~n Status ~p~n",
			   [Status]),
    error_logger:error_report(Report),
    {stop, ChannelId, State}.

%%--------------------------------------------------------------------
%% Function: handle_msg(Args) -> {ok, State} | {stop, ChannelId, State}
%%                        
%% Description: Handles other channel messages.
%%--------------------------------------------------------------------
handle_msg({ssh_channel_up, ChannelId, ConnectionManager},
	   #state{channel = ChannelId,
		  cm = ConnectionManager} = State) ->
    {ok,  State};

handle_msg({Group, Req}, #state{group = Group, buf = Buf, pty = Pty,
				 cm = ConnectionManager,
				 channel = ChannelId} = State) ->
    {Chars, NewBuf} = io_request(Req, Buf, Pty),
    write_chars(ConnectionManager, ChannelId, Chars),
    {ok, State#state{buf = NewBuf}};

handle_msg({'EXIT', Group, _Reason}, #state{group = Group,
					     channel = ChannelId} = State) ->
    {stop, ChannelId, State};

handle_msg(_, State) ->
    {ok, State}.

%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: Called when the channel process is trminated
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
    ok.

%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------

exec(Cmd) ->
    eval(parse(scan(Cmd))).

scan(Cmd) ->
    erl_scan:string(Cmd). 

parse({ok, Tokens, _}) ->
    erl_parse:parse_exprs(Tokens);
parse(Error) ->
    Error.

eval({ok, Expr_list}) ->
    case (catch erl_eval:exprs(Expr_list,
 			       erl_eval:new_bindings())) of
 	{value, Value, _NewBindings} ->
 	    {Value, 0};
 	{'EXIT', {Error, _}} -> 
 	    {Error, -1};
 	Error -> 
 	    {Error, -1}
    end;
eval(Error) ->
    {Error, -1}.

%%% io_request, handle io requests from the user process,
%%% Note, this is not the real I/O-protocol, but the mockup version
%%% used between edlin and a user_driver. The protocol tags are
%%% similar, but the message set is different. <commit_msg>ssh: If not valid expression, rather than generate a error, try to use the cmd.<commit_after>%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2005-2013. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%

%%
%% Description: a gen_server implementing a simple
%% terminal (using the group module) for a CLI
%% over SSH

-module(ssh_cli).

-behaviour(ssh_daemon_channel).

-include("ssh.hrl").
-include("ssh_connect.hrl").

%% ssh_channel callbacks
-export([init/1, handle_ssh_msg/2, handle_msg/2, terminate/2]).

%% backwards compatibility
-export([listen/1, listen/2, listen/3, listen/4, stop/1]).

%% state
-record(state, {
	  cm,
	  channel,
	  pty,
	  group,
	  buf,
	  shell,
	  exec
	 }).

%%====================================================================
%% ssh_channel callbacks
%%====================================================================

%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} 
%%                        
%% Description: Initiates the CLI
%%--------------------------------------------------------------------
init([Shell, Exec]) ->
    {ok, #state{shell = Shell, exec = Exec}};
init([Shell]) ->
    {ok, #state{shell = Shell}}.

%%--------------------------------------------------------------------
%% Function: handle_ssh_msg(Args) -> {ok, State} | {stop, ChannelId, State}
%%                        
%% Description: Handles channel messages received on the ssh-connection.
%%--------------------------------------------------------------------
handle_ssh_msg({ssh_cm, _ConnectionManager, 
		{data, _ChannelId, _Type, Data}}, 
	       #state{group = Group} = State) ->
    Group ! {self(), {data, binary_to_list(Data)}},
    {ok, State};

handle_ssh_msg({ssh_cm, ConnectionManager, 
		{pty, ChannelId, WantReply, 
		 {TermName, Width, Height, PixWidth, PixHeight, Modes}}}, 
	       State0) ->
    State = State0#state{pty = 
			 #ssh_pty{term = TermName,
				  width =  not_zero(Width, 80),
				  height = not_zero(Height, 24),
				  pixel_width = PixWidth,
				  pixel_height = PixHeight,
                  modes = Modes},
             buf = empty_buf()},
    set_echo(State),
    ssh_connection:reply_request(ConnectionManager, WantReply, 
				 success, ChannelId),
    {ok, State};

handle_ssh_msg({ssh_cm, ConnectionManager, 
	    {env, ChannelId, WantReply, _Var, _Value}}, State) ->
    ssh_connection:reply_request(ConnectionManager, 
				 WantReply, failure, ChannelId),
    {ok, State};

handle_ssh_msg({ssh_cm, ConnectionManager,
	    {window_change, ChannelId, Width, Height, PixWidth, PixHeight}},
	   #state{buf = Buf, pty = Pty0} = State) ->
    Pty = Pty0#ssh_pty{width = Width, height = Height,
		       pixel_width = PixWidth,
		       pixel_height = PixHeight},
    {Chars, NewBuf} = io_request({window_change, Pty0}, Buf, Pty),
    write_chars(ConnectionManager, ChannelId, Chars),
    {ok, State#state{pty = Pty, buf = NewBuf}};

handle_ssh_msg({ssh_cm, ConnectionManager, 
	    {shell, ChannelId, WantReply}}, State) ->
    NewState = start_shell(ConnectionManager, State),
    ssh_connection:reply_request(ConnectionManager, WantReply, 
				 success, ChannelId),
    {ok, NewState#state{channel = ChannelId,
			cm = ConnectionManager}};

handle_ssh_msg({ssh_cm, ConnectionManager, 
		{exec, ChannelId, WantReply, Cmd}}, #state{exec=undefined} = State) ->
    {Reply, Status} = exec(Cmd),
    write_chars(ConnectionManager, 
		ChannelId, io_lib:format("~p\n", [Reply])),
    ssh_connection:reply_request(ConnectionManager, WantReply, 
				 success, ChannelId),
    ssh_connection:exit_status(ConnectionManager, ChannelId, Status),
    ssh_connection:send_eof(ConnectionManager, ChannelId),
    {stop, ChannelId, State#state{channel = ChannelId, cm = ConnectionManager}};
handle_ssh_msg({ssh_cm, ConnectionManager,
		{exec, ChannelId, WantReply, Cmd}}, State) ->
    NewState = start_shell(ConnectionManager, Cmd, State),
    ssh_connection:reply_request(ConnectionManager, WantReply,
				 success, ChannelId),
    {ok, NewState#state{channel = ChannelId,
			cm = ConnectionManager}};

handle_ssh_msg({ssh_cm, _ConnectionManager, {eof, _ChannelId}}, State) ->
    {ok, State};

handle_ssh_msg({ssh_cm, _, {signal, _, _}}, State) ->
    %% Ignore signals according to RFC 4254 section 6.9.
    {ok, State};

handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, Error, _}}, State) ->
    Report = io_lib:format("Connection closed by peer ~n Error ~p~n",
			   [Error]),
    error_logger:error_report(Report),
    {stop, ChannelId,  State};

handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, 0}}, State) ->
    {stop, ChannelId, State};

handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, Status}}, State) ->
    
    Report = io_lib:format("Connection closed by peer ~n Status ~p~n",
			   [Status]),
    error_logger:error_report(Report),
    {stop, ChannelId, State}.

%%--------------------------------------------------------------------
%% Function: handle_msg(Args) -> {ok, State} | {stop, ChannelId, State}
%%                        
%% Description: Handles other channel messages.
%%--------------------------------------------------------------------
handle_msg({ssh_channel_up, ChannelId, ConnectionManager},
	   #state{channel = ChannelId,
		  cm = ConnectionManager} = State) ->
    {ok,  State};

handle_msg({Group, Req}, #state{group = Group, buf = Buf, pty = Pty,
				 cm = ConnectionManager,
				 channel = ChannelId} = State) ->
    {Chars, NewBuf} = io_request(Req, Buf, Pty),
    write_chars(ConnectionManager, ChannelId, Chars),
    {ok, State#state{buf = NewBuf}};

handle_msg({'EXIT', Group, _Reason}, #state{group = Group,
					     channel = ChannelId} = State) ->
    {stop, ChannelId, State};

handle_msg(_, State) ->
    {ok, State}.

%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: Called when the channel process is trminated
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
    ok.

%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------

exec(Cmd) ->
    case eval(parse(scan(Cmd))) of
	{error, _} ->
	    {Cmd, 0}; %% This should be an external call
	Term ->
	    Term
    end.

scan(Cmd) ->
    erl_scan:string(Cmd). 

parse({ok, Tokens, _}) ->
    erl_parse:parse_exprs(Tokens);
parse(Error) ->
    Error.

eval({ok, Expr_list}) ->
    case (catch erl_eval:exprs(Expr_list,
 			       erl_eval:new_bindings())) of
 	{value, Value, _NewBindings} ->
 	    {Value, 0};
 	{'EXIT', {Error, _}} -> 
 	    {Error, -1};
 	Error -> 
 	    {Error, -1}
    end;
eval(Error) ->
    {Error, -1}.

%%% io_request, handle io requests from the user process,
%%% Note, this is not the real I/O-protocol, but the mockup version
%%% used between edlin and a user_driver. The protocol tags are
%%% similar, but the message set is different. 
{'fraction_non_alphanumeric': 0.23665290906670217, 'fraction_numerical': 0.0052589535348156035, 'mean_word_length': 2.5974616858237547, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 69, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4787876', 'n_tokens_mistral': 4744, 'n_tokens_neox': 4315, 'n_words': 1138}