Files
webssh/webssh/utils.py
T
Jocay c013a389fe feat: Implement directory validation and shell command quoting
- Added is_valid_directory function to validate directory paths, ensuring they do not contain control characters and are within a specified length.
- Introduced quote_shell_arg to safely quote shell arguments, preventing command injection.
- Created build_cd_command to generate a command for changing directories in a shell.
- Enhanced the LoginHandler to utilize a login rate limiter, preventing brute-force attacks by tracking failed login attempts.
- Implemented an EncodingCache to optimize encoding detection for SSH connections.
- Updated the UI to include an input field for specifying an initial directory upon login, with appropriate validation and hints.
- Added a quickbar in the terminal interface for easy access to copy and paste functionality.
- Introduced a toast notification system to provide feedback on copy actions.
- Refactored connection storage to encrypt passwords at rest, improving security.
- Updated various templates and styles to accommodate new features and improve user experience.
2026-08-10 00:07:52 +08:00

166 lines
3.4 KiB
Python

import ipaddress
import re
try:
from types import UnicodeType
except ImportError:
UnicodeType = str
try:
from urllib.parse import urlparse
except ImportError:
from urlparse import urlparse
numeric = re.compile(r'[0-9]+$')
allowed = re.compile(r'(?!-)[a-z0-9-]{1,63}(?<!-)$', re.IGNORECASE)
control_chars = re.compile(r'[\x00-\x1f\x7f]')
MAX_DIRECTORY_LENGTH = 1024
def to_str(bstr, encoding='utf-8'):
if isinstance(bstr, bytes):
return bstr.decode(encoding)
return bstr
def to_bytes(ustr, encoding='utf-8'):
if isinstance(ustr, UnicodeType):
return ustr.encode(encoding)
return ustr
def to_int(string):
try:
return int(string)
except (TypeError, ValueError):
pass
def to_ip_address(ipstr):
ip = to_str(ipstr)
if ip.startswith('fe80::'):
ip = ip.split('%')[0]
return ipaddress.ip_address(ip)
def is_valid_ip_address(ipstr):
try:
to_ip_address(ipstr)
except ValueError:
return False
return True
def is_valid_port(port):
return 0 < port < 65536
def is_valid_encoding(encoding):
try:
u'test'.encode(encoding)
except LookupError:
return False
except ValueError:
return False
return True
def is_ip_hostname(hostname):
it = iter(hostname)
if next(it) == '[':
return True
for ch in it:
if ch != '.' and not ch.isdigit():
return False
return True
def is_valid_hostname(hostname):
if hostname[-1] == '.':
# strip exactly one dot from the right, if present
hostname = hostname[:-1]
if len(hostname) > 253:
return False
labels = hostname.split('.')
# the TLD must be not all-numeric
if numeric.match(labels[-1]):
return False
return all(allowed.match(label) for label in labels)
def is_valid_directory(directory):
if not directory or len(directory) > MAX_DIRECTORY_LENGTH:
return False
return not control_chars.search(directory)
def quote_shell_arg(value):
"""Wrap a value in single quotes so a POSIX shell treats it literally."""
return "'{}'".format(value.replace("'", "'\\''"))
def build_cd_command(directory):
return 'cd {}\r'.format(quote_shell_arg(directory))
def is_same_primary_domain(domain1, domain2):
i = -1
dots = 0
l1 = len(domain1)
l2 = len(domain2)
m = min(l1, l2)
while i >= -m:
c1 = domain1[i]
c2 = domain2[i]
if c1 == c2:
if c1 == '.':
dots += 1
if dots == 2:
return True
else:
return False
i -= 1
if l1 == l2:
return True
if dots == 0:
return False
c = domain1[i] if l1 > m else domain2[i]
return c == '.'
def parse_origin_from_url(url):
url = url.strip()
if not url:
return
if not (url.startswith('http://') or url.startswith('https://') or
url.startswith('//')):
url = '//' + url
parsed = urlparse(url)
port = parsed.port
scheme = parsed.scheme
if scheme == '':
scheme = 'https' if port == 443 else 'http'
if port == 443 and scheme == 'https':
netloc = parsed.netloc.replace(':443', '')
elif port == 80 and scheme == 'http':
netloc = parsed.netloc.replace(':80', '')
else:
netloc = parsed.netloc
return '{}://{}'.format(scheme, netloc)