mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-09 07:48:57 +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,
|
update_checksum_file,
|
||||||
)
|
)
|
||||||
from .password_prompt import prompt_for_password
|
from .password_prompt import prompt_for_password
|
||||||
|
from .seed_prompt import masked_input
|
||||||
from .input_utils import timed_input
|
from .input_utils import timed_input
|
||||||
from .memory_protection import InMemorySecret
|
from .memory_protection import InMemorySecret
|
||||||
from .clipboard import copy_to_clipboard
|
from .clipboard import copy_to_clipboard
|
||||||
@@ -58,6 +59,7 @@ __all__ = [
|
|||||||
"exclusive_lock",
|
"exclusive_lock",
|
||||||
"shared_lock",
|
"shared_lock",
|
||||||
"prompt_for_password",
|
"prompt_for_password",
|
||||||
|
"masked_input",
|
||||||
"timed_input",
|
"timed_input",
|
||||||
"InMemorySecret",
|
"InMemorySecret",
|
||||||
"copy_to_clipboard",
|
"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