function password = readPassword(prompt, opts)
; %#ok Undocumented
%readPassword - prompt the user for a password on the command line.
% Allows for entry of sensitive data, e.g. a password. User input is not
% echoed to stdout and is not stored in the command history.
%
% This function is intended to be used when MATLAB is running on the
% command line, such as when started in -nodisplay mode on Unix systems or
% when run from a compiled app. It cannot hide the input when running on
% Windows with the desktop open. In that case a dialog should be used
% instead.
%
% PASSWORD = readPassword(PROMPT) displays the PROMPT string on the screen
% and reads input from the command line. Returns the password string or, in
% case of failure, throws an MException containing the error message.
%
% readPassword(..., Method=METHOD) forces this function to use either the
% "Builtin" or "System" method of reading the password.
% - "Builtin" uses a builtin function that reads characters from the
%   terminal. It does not work if the desktop is available.
% - "System" calls command line utilities using the MATLAB SYSTEM function.
%   It is unable to hide the input on Windows and is not guaranteed to work
%   for all Unix shells.
% By default, "Builtin" is used if the desktop is not available and
% "System" is used otherwise.

% Copyright 2010-2023 The MathWorks, Inc.

arguments
    prompt {mustBeTextScalar}
    opts.Method {mustBeMember(opts.Method, ["Builtin", "System"])}
end

if ~isfield(opts, "Method")
    if iIsDesktopAvailable
        opts.Method = "System";
    else
        opts.Method = "Builtin";
    end
end

% Draw now to allow queued stdout and stderr to be displayed before prompting
drawnow();

fprintf('%s', prompt);
if opts.Method == "System"
    password = iGetPasswordUsingSystemCall();
else
    password = matlabshared.internal.readSecret();
end

% Make output type match input type
if ischar(prompt)
    password = convertStringsToChars(password);
end

end

function tf = iIsDesktopAvailable()
import matlab.internal.capability.Capability
tf = Capability.isSupported(Capability.ModalDialogs);
end

function pw = iGetPasswordUsingSystemCall()
if ispc
    % Note: this is echoing the user input back onto stdout. We never
    % expect this method to be used on Windows.
    command = 'cmd /v:on /c "set /p pass= && echo !pass!"';
else
    % We must be explicit here that we are using bash functionality.
    % The '-s' option is not Posix, and not supported by all shells
    % (including the default for /bin/sh in Debian and Ubuntu)
    % see g889884
    command = "/usr/bin/env bash -c 'read -s password; echo $password'";
end
[status, output] = system(command);
fprintf("\n");
output = strtrim(output);
if status ~= 0
    error(message("matlabshared:internal:ReadPasswordErrorWithCause", output));
end
pw = string(iProcessBackspaces(output));
end

function strOut = iProcessBackspaces(strIn)
backspace = char(8);

if ~contains(strIn, backspace)
    strOut = strIn;
    return
end

strOut = '';
for c = strIn
    if c == backspace
        if ~isempty(strOut)
            strOut(end) = [];
        end
    else
        strOut(end+1) = c; %#ok<AGROW>
    end
end

end
