mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-08 07:18:47 +00:00
Add cross-platform masked input utility with tests
This commit is contained in:
30
src/tests/test_seed_prompt.py
Normal file
30
src/tests/test_seed_prompt.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import types
|
||||
from utils import seed_prompt
|
||||
|
||||
|
||||
def test_masked_input_posix_backspace(monkeypatch, capsys):
|
||||
seq = iter(["a", "b", "\x7f", "c", "\n"])
|
||||
monkeypatch.setattr(seed_prompt.sys.stdin, "read", lambda n=1: next(seq))
|
||||
monkeypatch.setattr(seed_prompt.sys.stdin, "fileno", lambda: 0)
|
||||
monkeypatch.setattr(seed_prompt.termios, "tcgetattr", lambda fd: None)
|
||||
monkeypatch.setattr(seed_prompt.termios, "tcsetattr", lambda fd, *_: None)
|
||||
monkeypatch.setattr(seed_prompt.tty, "setraw", lambda fd: None)
|
||||
|
||||
result = seed_prompt.masked_input("Enter: ")
|
||||
assert result == "ac"
|
||||
out = capsys.readouterr().out
|
||||
assert out.startswith("Enter: ")
|
||||
assert out.count("*") == 3
|
||||
|
||||
|
||||
def test_masked_input_windows_space(monkeypatch, capsys):
|
||||
seq = iter(["x", "y", " ", "z", "\r"])
|
||||
fake_msvcrt = types.SimpleNamespace(getwch=lambda: next(seq))
|
||||
monkeypatch.setattr(seed_prompt, "msvcrt", fake_msvcrt)
|
||||
monkeypatch.setattr(seed_prompt.sys, "platform", "win32", raising=False)
|
||||
|
||||
result = seed_prompt.masked_input("Password: ")
|
||||
assert result == "xy z"
|
||||
out = capsys.readouterr().out
|
||||
assert out.startswith("Password: ")
|
||||
assert out.count("*") == 4
|
@@ -25,6 +25,7 @@ try:
|
||||
update_checksum_file,
|
||||
)
|
||||
from .password_prompt import prompt_for_password
|
||||
from .seed_prompt import masked_input
|
||||
from .input_utils import timed_input
|
||||
from .memory_protection import InMemorySecret
|
||||
from .clipboard import copy_to_clipboard
|
||||
@@ -58,6 +59,7 @@ __all__ = [
|
||||
"exclusive_lock",
|
||||
"shared_lock",
|
||||
"prompt_for_password",
|
||||
"masked_input",
|
||||
"timed_input",
|
||||
"InMemorySecret",
|
||||
"copy_to_clipboard",
|
||||
|
73
src/utils/seed_prompt.py
Normal file
73
src/utils/seed_prompt.py
Normal file
@@ -0,0 +1,73 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
import msvcrt # type: ignore
|
||||
except ImportError: # pragma: no cover - Windows only
|
||||
msvcrt = None # type: ignore
|
||||
|
||||
try:
|
||||
import termios
|
||||
import tty
|
||||
except ImportError: # pragma: no cover - POSIX only
|
||||
termios = None # type: ignore
|
||||
tty = None # type: ignore
|
||||
|
||||
|
||||
def _masked_input_windows(prompt: str) -> str:
|
||||
"""Windows implementation using ``msvcrt``."""
|
||||
if msvcrt is None: # pragma: no cover - should not happen
|
||||
return input(prompt)
|
||||
|
||||
sys.stdout.write(prompt)
|
||||
sys.stdout.flush()
|
||||
buffer: list[str] = []
|
||||
while True:
|
||||
ch = msvcrt.getwch()
|
||||
if ch in ("\r", "\n"):
|
||||
sys.stdout.write("\n")
|
||||
return "".join(buffer)
|
||||
if ch in ("\b", "\x7f"):
|
||||
if buffer:
|
||||
buffer.pop()
|
||||
sys.stdout.write("\b \b")
|
||||
else:
|
||||
buffer.append(ch)
|
||||
sys.stdout.write("*")
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def _masked_input_posix(prompt: str) -> str:
|
||||
"""POSIX implementation using ``termios`` and ``tty``."""
|
||||
if termios is None or tty is None: # pragma: no cover - should not happen
|
||||
return input(prompt)
|
||||
|
||||
fd = sys.stdin.fileno()
|
||||
old_settings = termios.tcgetattr(fd)
|
||||
sys.stdout.write(prompt)
|
||||
sys.stdout.flush()
|
||||
buffer: list[str] = []
|
||||
try:
|
||||
tty.setraw(fd)
|
||||
while True:
|
||||
ch = sys.stdin.read(1)
|
||||
if ch in ("\r", "\n"):
|
||||
sys.stdout.write("\n")
|
||||
return "".join(buffer)
|
||||
if ch in ("\x7f", "\b"):
|
||||
if buffer:
|
||||
buffer.pop()
|
||||
sys.stdout.write("\b \b")
|
||||
else:
|
||||
buffer.append(ch)
|
||||
sys.stdout.write("*")
|
||||
sys.stdout.flush()
|
||||
finally:
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
||||
|
||||
|
||||
def masked_input(prompt: str) -> str:
|
||||
"""Return input from the user while masking typed characters."""
|
||||
if sys.platform == "win32":
|
||||
return _masked_input_windows(prompt)
|
||||
return _masked_input_posix(prompt)
|
Reference in New Issue
Block a user