58 lines
1.8 KiB
Python
58 lines
1.8 KiB
Python
"""
|
||
Shared utilities for FunguyBot plugins.
|
||
"""
|
||
import html
|
||
import ipaddress
|
||
import socket
|
||
import logging
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# Networks considered unsafe for outbound connections
|
||
_PRIVATE_RANGES = [
|
||
ipaddress.ip_network('10.0.0.0/8'),
|
||
ipaddress.ip_network('172.16.0.0/12'),
|
||
ipaddress.ip_network('192.168.0.0/16'),
|
||
ipaddress.ip_network('127.0.0.0/8'),
|
||
ipaddress.ip_network('169.254.0.0/16'),
|
||
ipaddress.ip_network('0.0.0.0/8'),
|
||
ipaddress.ip_network('::1/128'),
|
||
ipaddress.ip_network('fc00::/7'),
|
||
ipaddress.ip_network('fe80::/10'),
|
||
ipaddress.ip_network('::/128'),
|
||
]
|
||
|
||
def html_escape(text: str) -> str:
|
||
"""Escape HTML special characters for safe embedding in messages."""
|
||
return html.escape(str(text), quote=False)
|
||
|
||
def collapsible_summary(title: str, body: str, expanded: bool = False) -> str:
|
||
"""Wrap content in a collapsible HTML details block."""
|
||
open_attr = ' open' if expanded else ''
|
||
return f"<details{open_attr}>\n<summary><strong>{title}</strong></summary>\n{body}\n</details>"
|
||
|
||
def is_public_destination(target: str) -> bool:
|
||
"""
|
||
Returns True if `target` (hostname or IP) does NOT resolve to any
|
||
private, loopback, or link‑local address.
|
||
"""
|
||
try:
|
||
addr = ipaddress.ip_address(target)
|
||
if any(addr in net for net in _PRIVATE_RANGES):
|
||
return False
|
||
return True
|
||
except ValueError:
|
||
pass
|
||
|
||
try:
|
||
addrinfo = socket.getaddrinfo(target, None)
|
||
for _, _, _, _, sockaddr in addrinfo:
|
||
ip = sockaddr[0]
|
||
addr = ipaddress.ip_address(ip)
|
||
if any(addr in net for net in _PRIVATE_RANGES):
|
||
return False
|
||
return True
|
||
except Exception as e:
|
||
logger.warning(f"Cannot resolve {target}: {e}")
|
||
return False
|