109 lines
3.1 KiB
Python
109 lines
3.1 KiB
Python
import ipaddress
|
||
import locale
|
||
import re
|
||
import subprocess
|
||
import sys
|
||
from typing import Iterable, List
|
||
|
||
|
||
CREATE_NO_WINDOW = subprocess.CREATE_NO_WINDOW if sys.platform.startswith("win") else 0
|
||
|
||
|
||
def preferred_encoding() -> str:
|
||
return locale.getpreferredencoding(False) or "utf-8"
|
||
|
||
|
||
def run_hidden(command: List[str], timeout=None) -> subprocess.CompletedProcess:
|
||
return subprocess.run(
|
||
command,
|
||
stdout=subprocess.PIPE,
|
||
stderr=subprocess.STDOUT,
|
||
text=True,
|
||
encoding=preferred_encoding(),
|
||
errors="replace",
|
||
timeout=timeout,
|
||
creationflags=CREATE_NO_WINDOW,
|
||
)
|
||
|
||
|
||
def popen_hidden(command: List[str]) -> subprocess.Popen:
|
||
return subprocess.Popen(
|
||
command,
|
||
stdout=subprocess.PIPE,
|
||
stderr=subprocess.STDOUT,
|
||
text=True,
|
||
encoding=preferred_encoding(),
|
||
errors="replace",
|
||
creationflags=CREATE_NO_WINDOW,
|
||
)
|
||
|
||
|
||
def validate_host(value: str) -> str:
|
||
host = value.strip()
|
||
if not host:
|
||
raise ValueError("请输入目标地址或域名")
|
||
return host
|
||
|
||
|
||
def validate_ip(value: str, allow_empty: bool = False) -> str:
|
||
ip = value.strip()
|
||
if allow_empty and not ip:
|
||
return ""
|
||
try:
|
||
ipaddress.ip_address(ip)
|
||
except ValueError as exc:
|
||
raise ValueError(f"IP 地址无效: {value}") from exc
|
||
return ip
|
||
|
||
|
||
def validate_port(value, label: str = "端口") -> int:
|
||
try:
|
||
port = int(str(value).strip())
|
||
except ValueError as exc:
|
||
raise ValueError(f"{label}必须是整数") from exc
|
||
if port < 1 or port > 65535:
|
||
raise ValueError(f"{label}必须在 1-65535 之间")
|
||
return port
|
||
|
||
|
||
def parse_ports(text: str) -> List[int]:
|
||
ports = set()
|
||
for part in re.split(r"[,,;\s]+", text.strip()):
|
||
part = part.strip()
|
||
if not part:
|
||
continue
|
||
if "-" in part:
|
||
start_raw, end_raw = part.split("-", 1)
|
||
start = validate_port(start_raw, "起始端口")
|
||
end = validate_port(end_raw, "结束端口")
|
||
if start > end:
|
||
raise ValueError("端口范围的起始值不能大于结束值")
|
||
ports.update(range(start, end + 1))
|
||
else:
|
||
ports.add(validate_port(part))
|
||
if not ports:
|
||
raise ValueError("请输入至少一个端口")
|
||
return sorted(ports)
|
||
|
||
|
||
def prefix_to_netmask(prefix_length) -> str:
|
||
if prefix_length in ("", None):
|
||
return ""
|
||
try:
|
||
prefix = int(prefix_length)
|
||
return str(ipaddress.IPv4Network(f"0.0.0.0/{prefix}").netmask)
|
||
except Exception:
|
||
return ""
|
||
|
||
|
||
def iter_hosts(prefix: str, start: int, end: int) -> Iterable[str]:
|
||
if not prefix.endswith("."):
|
||
raise ValueError("网段前缀必须以点号结尾,例如 192.168.1.")
|
||
for value in (start, end):
|
||
if value < 1 or value > 254:
|
||
raise ValueError("主机号范围必须在 1-254 之间")
|
||
if start > end:
|
||
raise ValueError("起始主机号不能大于结束主机号")
|
||
for host_id in range(start, end + 1):
|
||
yield validate_ip(f"{prefix}{host_id}")
|