diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a2efc705cd..a241f876a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,7 @@ jobs: ## The following is needed for examples/wkhtmltopdf.v wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.focal_amd64.deb sudo apt-get install --quiet -y xfonts-75dpi xfonts-base + sudo apt-get install --quiet -y expect sudo dpkg -i wkhtmltox_0.12.6-1.focal_amd64.deb - name: Build v run: | @@ -92,6 +93,11 @@ jobs: ./v2 -o v3 -usecache cmd/v ./v3 version ./v3 -o tetris -usecache examples/tetris/tetris.v + - name: Test password input + run: | + ./v examples/password + ./v examples/password/password_ci.vsh + ubuntu-tcc-boehm-gc: runs-on: ubuntu-20.04 @@ -239,6 +245,10 @@ jobs: ./v2 -o v3 -usecache cmd/v ./v3 version ./v3 -o tetris -usecache examples/tetris/tetris.v + - name: Test password input + run: | + ./v examples/password + ./v examples/password/password_ci.vsh ubuntu: runs-on: ubuntu-20.04 diff --git a/examples/password/correct.expect b/examples/password/correct.expect new file mode 100755 index 0000000000..1ab9c47060 --- /dev/null +++ b/examples/password/correct.expect @@ -0,0 +1,8 @@ +#!/usr/bin/expect +spawn ./password +expect "Enter your password : " +send "Sample\r" +expect "Confirm password : " +send "Sample\r" +expect "Password confirmed! You entered: Sample ." +expect eof diff --git a/examples/password/incorrect.expect b/examples/password/incorrect.expect new file mode 100755 index 0000000000..61bf9e9247 --- /dev/null +++ b/examples/password/incorrect.expect @@ -0,0 +1,8 @@ +#!/usr/bin/expect +spawn ./password +expect "Enter your password : " +send "Sample123\r" +expect "Confirm password : " +send "Sample234\r" +expect "Passwords do not match ." +expect eof diff --git a/examples/password/password.v b/examples/password/password.v new file mode 100644 index 0000000000..32fd0d0680 --- /dev/null +++ b/examples/password/password.v @@ -0,0 +1,14 @@ +module main + +import os + +fn main() { + original_password := os.input_password('Enter your password : ')! + repeated_password := os.input_password('Confirm password : ')! + + if original_password == repeated_password { + println('Password confirmed! You entered: $original_password .') + } else { + println('Passwords do not match .') + } +} diff --git a/examples/password/password_ci.vsh b/examples/password/password_ci.vsh new file mode 100644 index 0000000000..a0df1e3478 --- /dev/null +++ b/examples/password/password_ci.vsh @@ -0,0 +1,3 @@ +chdir('examples/password')? +assert execute('./correct.expect').exit_code == 0 +assert execute('./incorrect.expect').exit_code == 0 diff --git a/vlib/os/password_nix.c.v b/vlib/os/password_nix.c.v new file mode 100644 index 0000000000..dc9bbfaf6e --- /dev/null +++ b/vlib/os/password_nix.c.v @@ -0,0 +1,42 @@ +module os + +#include + +pub struct C.termios { +mut: + c_iflag int + c_oflag int + c_cflag int + c_lflag int + c_cc [20]u8 +} + +fn C.tcgetattr(fd int, ptr &C.termios) int + +fn C.tcsetattr(fd int, action int, const_ptr &C.termios) + +// input_password prompts the user for a password-like secret. It disables +// the terminal echo during user input and resets it back to normal when done. +pub fn input_password(prompt string) !string { + if is_atty(1) <= 0 || getenv('TERM') == 'dumb' { + return error('Could not obtain password discretely.') + } + + old_state := C.termios{} + if unsafe { C.tcgetattr(0, &old_state) } != 0 { + return last_error() + } + defer { + unsafe { C.tcsetattr(0, C.TCSANOW, &old_state) } + println('') + } + + mut new_state := old_state + + new_state.c_lflag &= int(~u32(C.ECHO)) // Disable echoing of characters + unsafe { C.tcsetattr(0, C.TCSANOW, &new_state) } + + password := input_opt(prompt) or { return error('Failed to read password') } + + return password +} diff --git a/vlib/os/password_windows.c.v b/vlib/os/password_windows.c.v new file mode 100644 index 0000000000..273c444bc6 --- /dev/null +++ b/vlib/os/password_windows.c.v @@ -0,0 +1,26 @@ +module os + +#include + +// input_password prompts the user for a password-like secret. It disables +// the terminal echo during user input and resets it back to normal when done. +pub fn input_password(prompt string) !string { + if is_atty(1) <= 0 || getenv('TERM') == 'dumb' { + return error('Could not obtain password discretely.') + } + + std_handle := C.GetStdHandle(C.STD_INPUT_HANDLE) + mut mode := u32(0) + + unsafe { C.GetConsoleMode(std_handle, &mode) } + unsafe { C.SetConsoleMode(std_handle, mode & (~u32(C.ENABLE_ECHO_INPUT))) } + + defer { + unsafe { C.SetConsoleMode(std_handle, &mode) } + println('') + } + + password := input_opt(prompt) or { return error('Failed to read password') } + + return password +}