Proxy auth UI, verbose Live logs, build script, docs
- Fixed exit and manual chain: optional user/pass fields with URL merge and redaction in UI/logs - config: split_proxy_for_edit, merge_proxy_credentials, redact_proxy_url, IPv6-style host bracketing - Live tab: UILogHandler to stream proxy_chain_manager logs; Verbose toggle; Clear log; httpx quiet - service/validator/fetcher: structured INFO/DEBUG for pool, GOST, validation, fetches - setup_and_build.ps1: pip upgrade, deps, PyInstaller, desktop copy + shortcut; build_exe.bat delegates - README: clone/pull to one-script desktop build flow Made-with: Cursor
This commit is contained in:
@@ -3,9 +3,22 @@ from __future__ import annotations
|
||||
import json
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from pathlib import Path
|
||||
from urllib.parse import quote, unquote, urlparse, urlunparse
|
||||
|
||||
from .paths import app_data_dir
|
||||
|
||||
|
||||
def _host_port_for_url(host: str, port: int | None) -> str:
|
||||
"""``host`` and optional ``port`` as used in proxy URLs (bracket IPv6 literals)."""
|
||||
if not host:
|
||||
return ""
|
||||
if ":" in host and "." not in host:
|
||||
inner = f"[{host}]"
|
||||
else:
|
||||
inner = host
|
||||
return f"{inner}:{port}" if port else inner
|
||||
|
||||
|
||||
# Always bind the local forwarder to loopback. VM clones and NIC/IP changes (DHCP)
|
||||
# do not affect 127.0.0.1; avoid storing LAN IPs or hostnames for the listener.
|
||||
LISTEN_HOST = "127.0.0.1"
|
||||
@@ -21,6 +34,69 @@ def normalize_proxy_url(raw: str) -> str:
|
||||
return t.rstrip("/")
|
||||
|
||||
|
||||
def split_proxy_for_edit(raw: str) -> tuple[str, str, str]:
|
||||
"""Split stored proxy URL into (url_without_credentials, username, password).
|
||||
|
||||
Used to populate fixed-exit / form fields. Credentials are percent-decoded.
|
||||
"""
|
||||
t = normalize_proxy_url((raw or "").strip())
|
||||
if not t:
|
||||
return "", "", ""
|
||||
p = urlparse(t)
|
||||
if not p.hostname:
|
||||
return t, "", ""
|
||||
host = p.hostname
|
||||
port = p.port
|
||||
scheme = p.scheme or "http"
|
||||
base = f"{scheme}://{_host_port_for_url(host, port)}"
|
||||
u = unquote(p.username) if p.username else ""
|
||||
pw = unquote(p.password) if p.password else ""
|
||||
return base, u, pw
|
||||
|
||||
|
||||
def merge_proxy_credentials(raw_url: str, username: str = "", password: str = "") -> str:
|
||||
"""Build a single proxy URL: host/port from ``raw_url`` (any embedded auth ignored) plus optional auth.
|
||||
|
||||
User and password are URL-encoded for ``@`` and ``:`` inside values.
|
||||
"""
|
||||
base = normalize_proxy_url((raw_url or "").strip())
|
||||
if not base:
|
||||
return ""
|
||||
p = urlparse(base)
|
||||
if not p.hostname:
|
||||
return base
|
||||
host = p.hostname
|
||||
port = p.port
|
||||
scheme = p.scheme or "http"
|
||||
user = (username or "").strip()
|
||||
pw = (password or "").strip()
|
||||
if not user and not pw:
|
||||
# Leave credentials embedded in URL when form fields are blank (paste user:pass@host).
|
||||
return base
|
||||
hp = _host_port_for_url(host, port)
|
||||
netloc = f"{quote(user, safe='')}:{quote(pw, safe='')}@{hp}"
|
||||
return urlunparse((scheme, netloc, "", "", "", "")).rstrip("/")
|
||||
|
||||
|
||||
def redact_proxy_url(url: str) -> str:
|
||||
"""Same URL shape for logs/UI, with credentials replaced by ``***``."""
|
||||
t = (url or "").strip()
|
||||
if not t:
|
||||
return ""
|
||||
if "://" not in t:
|
||||
t = "http://" + t
|
||||
p = urlparse(t.rstrip("/"))
|
||||
if not p.hostname:
|
||||
return t
|
||||
has_auth = bool(p.username) or bool(p.password)
|
||||
if not has_auth:
|
||||
return t.rstrip("/")
|
||||
scheme = p.scheme or "http"
|
||||
hp = _host_port_for_url(p.hostname, p.port)
|
||||
netloc = f"***:***@{hp}"
|
||||
return urlunparse((scheme, netloc, "", "", "", "")).rstrip("/")
|
||||
|
||||
|
||||
OBFUSCATION_MODES = ["auto", "http_only", "socks5_only", "random_mix"]
|
||||
OBFUSCATION_LABELS = {
|
||||
"auto": "Auto (best available)",
|
||||
|
||||
Reference in New Issue
Block a user