Files
xingrin/backend/apps/common/validators.py

143 lines
3.7 KiB
Python
Raw Normal View History

2025-12-12 18:04:57 +08:00
"""域名、IP、端口和目标验证工具函数"""
import ipaddress
import logging
import validators
logger = logging.getLogger(__name__)
def validate_domain(domain: str) -> None:
"""
验证域名格式使用 validators
Args:
domain: 域名字符串应该已经规范化
Raises:
ValueError: 域名格式无效
"""
if not domain:
raise ValueError("域名不能为空")
# 使用 validators 库验证域名格式
# 支持国际化域名IDN和各种边界情况
if not validators.domain(domain):
raise ValueError(f"域名格式无效: {domain}")
def validate_ip(ip: str) -> None:
"""
验证 IP 地址格式支持 IPv4 IPv6
Args:
ip: IP 地址字符串应该已经规范化
Raises:
ValueError: IP 地址格式无效
"""
if not ip:
raise ValueError("IP 地址不能为空")
try:
ipaddress.ip_address(ip)
except ValueError:
raise ValueError(f"IP 地址格式无效: {ip}")
def validate_cidr(cidr: str) -> None:
"""
验证 CIDR 格式支持 IPv4 IPv6
Args:
cidr: CIDR 字符串应该已经规范化
Raises:
ValueError: CIDR 格式无效
"""
if not cidr:
raise ValueError("CIDR 不能为空")
try:
ipaddress.ip_network(cidr, strict=False)
except ValueError:
raise ValueError(f"CIDR 格式无效: {cidr}")
def detect_target_type(name: str) -> str:
"""
检测目标类型不做规范化只验证
Args:
name: 目标名称应该已经规范化
Returns:
str: 目标类型 ('domain', 'ip', 'cidr') - 使用 Target.TargetType 枚举值
Raises:
ValueError: 如果无法识别目标类型
"""
# 在函数内部导入模型,避免 AppRegistryNotReady 错误
from apps.targets.models import Target
if not name:
raise ValueError("目标名称不能为空")
# 检查是否是 CIDR 格式(包含 /
if '/' in name:
validate_cidr(name)
return Target.TargetType.CIDR
# 检查是否是 IP 地址
try:
validate_ip(name)
return Target.TargetType.IP
except ValueError:
pass
# 检查是否是合法域名
try:
validate_domain(name)
return Target.TargetType.DOMAIN
except ValueError:
pass
# 无法识别的格式
raise ValueError(f"无法识别的目标格式: {name}必须是域名、IP地址或CIDR范围")
def validate_port(port: any) -> tuple[bool, int | None]:
"""
验证并转换端口号
Args:
port: 待验证的端口号可能是字符串整数或其他类型
Returns:
tuple: (is_valid, port_number)
- is_valid: 端口是否有效
- port_number: 有效时为整数端口号无效时为 None
验证规则
1. 必须能转换为整数
2. 必须在 1-65535 范围内
示例
>>> is_valid, port_num = validate_port(8080)
>>> is_valid, port_num
(True, 8080)
>>> is_valid, port_num = validate_port("invalid")
>>> is_valid, port_num
(False, None)
"""
try:
port_num = int(port)
if 1 <= port_num <= 65535:
return True, port_num
else:
logger.warning("端口号超出有效范围 (1-65535): %d", port_num)
return False, None
except (ValueError, TypeError):
logger.warning("端口号格式错误,无法转换为整数: %s", port)
return False, None