Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Skip calling Windows console api with piped stdin #51

Merged
merged 3 commits into from Jun 18, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions appveyor.yml
Expand Up @@ -3,8 +3,8 @@ environment:
- TARGET: x86_64-pc-windows-gnu
- TARGET: i686-pc-windows-gnu
install:
- ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-1.33.0-${env:TARGET}.exe"
- rust-1.33.0-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust"
- ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-1.44.0-${env:TARGET}.exe"
- rust-1.44.0-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust"
- SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin
- SET PATH=%PATH%;C:\MinGW\bin
- rustc -V
Expand Down
33 changes: 20 additions & 13 deletions src/lib.rs
Expand Up @@ -151,9 +151,9 @@ mod windows {
use winapi::um::winnt::{
GENERIC_READ, GENERIC_WRITE, FILE_SHARE_READ, FILE_SHARE_WRITE,
};
use winapi::um::fileapi::{CreateFileA, OPEN_EXISTING};
use winapi::um::fileapi::{CreateFileA, GetFileType, OPEN_EXISTING};
use winapi::um::processenv::GetStdHandle;
use winapi::um::winbase::STD_INPUT_HANDLE;
use winapi::um::winbase::{FILE_TYPE_PIPE, STD_INPUT_HANDLE};
use winapi::um::handleapi::INVALID_HANDLE_VALUE;
use winapi::um::consoleapi::{GetConsoleMode, SetConsoleMode};
use winapi::shared::minwindef::LPDWORD;
Expand Down Expand Up @@ -181,16 +181,21 @@ mod windows {
return Err(::std::io::Error::last_os_error());
}

// Get the old mode so we can reset back to it when we are done
let mut mode = 0;
if unsafe { GetConsoleMode(handle, &mut mode as LPDWORD) } == 0 {
return Err(::std::io::Error::last_os_error());
}

// We want to be able to read line by line, and we still want backspace to work
let new_mode_flags = ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT;
if unsafe { SetConsoleMode(handle, new_mode_flags) } == 0 {
return Err(::std::io::Error::last_os_error());
// Console mode does not apply when stdin is piped
let handle_type = unsafe { GetFileType(handle) };
if handle_type != FILE_TYPE_PIPE {
// Get the old mode so we can reset back to it when we are done
if unsafe { GetConsoleMode(handle, &mut mode as LPDWORD) } == 0 {
return Err(::std::io::Error::last_os_error());
}

// We want to be able to read line by line, and we still want backspace to work
let new_mode_flags = ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT;
if unsafe { SetConsoleMode(handle, new_mode_flags) } == 0 {
return Err(::std::io::Error::last_os_error());
}
}

// Read the password.
Expand All @@ -201,9 +206,11 @@ mod windows {
// Check the response.
let _ = input?;

// Set the the mode back to normal
if unsafe { SetConsoleMode(handle, mode) } == 0 {
return Err(::std::io::Error::last_os_error());
if handle_type != FILE_TYPE_PIPE {
// Set the the mode back to normal
if unsafe { SetConsoleMode(handle, mode) } == 0 {
return Err(::std::io::Error::last_os_error());
}
}

super::fixes_newline(&mut password);
Expand Down
35 changes: 35 additions & 0 deletions tests/piped.rs
@@ -0,0 +1,35 @@
//! This test checks that piped input is handled correctly.

use std::env;
use std::io::Write;
use std::process::{Command, Stdio};

#[test]
fn piped_password() {
// Find target directory
let target_dir = env::current_exe()
.unwrap()
.parent()
.unwrap()
.parent()
.unwrap()
.to_path_buf();

// Run an example that reads a password and prints it
let mut out = Command::new(target_dir.join("examples/read-password"))
.stdout(Stdio::piped())
.stdin(Stdio::piped())
.spawn()
.unwrap();

// Write "secret" as the password into stdin
let stdin = out.stdin.as_mut().unwrap();
stdin.write_all("secret".as_bytes()).unwrap();

let out = out.wait_with_output().unwrap();
assert!(out.status.success());

let stdout = String::from_utf8(out.stdout).unwrap();
println!("stdout: {}", stdout);
assert!(stdout.ends_with("Password: secret\n"));
}