mirror of
https://github.com/UrloMythus/UnHided.git
synced 2026-04-11 11:50:51 +00:00
363 lines
12 KiB
Python
363 lines
12 KiB
Python
"""
|
|
aiohttp client factory with URL-based SSL verification and proxy routing.
|
|
|
|
This module provides a centralized HTTP client factory for aiohttp,
|
|
allowing per-URL configuration of SSL verification and proxy routing.
|
|
"""
|
|
|
|
import logging
|
|
import ssl
|
|
import typing
|
|
from contextlib import asynccontextmanager
|
|
from dataclasses import dataclass, field
|
|
from typing import Dict, Optional, Tuple
|
|
from urllib.parse import urlparse
|
|
|
|
import aiohttp
|
|
from aiohttp import ClientSession, ClientTimeout, TCPConnector
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@dataclass
|
|
class RouteMatch:
|
|
"""Configuration for a matched route."""
|
|
|
|
verify_ssl: bool = True
|
|
proxy_url: Optional[str] = None
|
|
|
|
|
|
@dataclass
|
|
class URLRoutingConfig:
|
|
"""
|
|
URL-based routing configuration for SSL verification and proxy settings.
|
|
|
|
Supports pattern matching:
|
|
- "all://*.example.com" - matches all protocols for *.example.com
|
|
- "https://api.example.com" - matches specific protocol and host
|
|
- "all://" - default fallback for all URLs
|
|
"""
|
|
|
|
# Pattern -> (verify_ssl, proxy_url)
|
|
routes: Dict[str, Tuple[bool, Optional[str]]] = field(default_factory=dict)
|
|
|
|
# Global defaults
|
|
default_verify_ssl: bool = True
|
|
default_proxy_url: Optional[str] = None
|
|
|
|
def add_route(
|
|
self,
|
|
pattern: str,
|
|
verify_ssl: bool = True,
|
|
proxy_url: Optional[str] = None,
|
|
) -> None:
|
|
"""
|
|
Add a route configuration.
|
|
|
|
Args:
|
|
pattern: URL pattern (e.g., "all://*.example.com", "https://api.example.com")
|
|
verify_ssl: Whether to verify SSL for this pattern
|
|
proxy_url: Proxy URL to use for this pattern (None = no proxy)
|
|
"""
|
|
self.routes[pattern] = (verify_ssl, proxy_url)
|
|
|
|
def match_url(self, url: str) -> RouteMatch:
|
|
"""
|
|
Find the best matching route for a URL.
|
|
|
|
Args:
|
|
url: The URL to match
|
|
|
|
Returns:
|
|
RouteMatch with SSL and proxy settings
|
|
"""
|
|
if not url:
|
|
return RouteMatch(
|
|
verify_ssl=self.default_verify_ssl,
|
|
proxy_url=self.default_proxy_url,
|
|
)
|
|
|
|
parsed = urlparse(url)
|
|
scheme = parsed.scheme.lower()
|
|
host = parsed.netloc.lower()
|
|
|
|
# Remove port from host for matching
|
|
if ":" in host:
|
|
host = host.split(":")[0]
|
|
|
|
best_match: Optional[RouteMatch] = None
|
|
best_specificity = -1
|
|
|
|
for pattern, (verify_ssl, proxy_url) in self.routes.items():
|
|
specificity = self._match_pattern(pattern, scheme, host)
|
|
if specificity > best_specificity:
|
|
best_specificity = specificity
|
|
best_match = RouteMatch(verify_ssl=verify_ssl, proxy_url=proxy_url)
|
|
|
|
if best_match:
|
|
return best_match
|
|
|
|
# Return defaults
|
|
return RouteMatch(
|
|
verify_ssl=self.default_verify_ssl,
|
|
proxy_url=self.default_proxy_url,
|
|
)
|
|
|
|
def _match_pattern(self, pattern: str, scheme: str, host: str) -> int:
|
|
"""
|
|
Check if a pattern matches the given scheme and host.
|
|
|
|
Returns specificity score (higher = more specific match):
|
|
- -1: No match
|
|
- 0: Default match (all://)
|
|
- 1: Scheme match only
|
|
- 2: Wildcard host match
|
|
- 3: Exact host match
|
|
"""
|
|
# Parse pattern
|
|
if "://" in pattern:
|
|
pattern_scheme, pattern_host = pattern.split("://", 1)
|
|
else:
|
|
return -1
|
|
|
|
# Check scheme
|
|
scheme_matches = pattern_scheme.lower() == "all" or pattern_scheme.lower() == scheme
|
|
|
|
if not scheme_matches:
|
|
return -1
|
|
|
|
# Empty host = default route
|
|
if not pattern_host:
|
|
return 0
|
|
|
|
# Check host with wildcard support
|
|
if pattern_host.startswith("*."):
|
|
# Wildcard subdomain match
|
|
suffix = pattern_host[1:] # Remove the *
|
|
if host.endswith(suffix) or host == pattern_host[2:]:
|
|
return 2
|
|
return -1
|
|
elif pattern_host == host:
|
|
# Exact match
|
|
return 3
|
|
else:
|
|
return -1
|
|
|
|
|
|
# Global routing configuration - will be initialized from settings
|
|
_global_routing_config: Optional[URLRoutingConfig] = None
|
|
_routing_initialized = False
|
|
|
|
|
|
def get_routing_config() -> URLRoutingConfig:
|
|
"""Get the global URL routing configuration."""
|
|
global _global_routing_config
|
|
if _global_routing_config is None:
|
|
_global_routing_config = URLRoutingConfig()
|
|
return _global_routing_config
|
|
|
|
|
|
def initialize_routing_from_config(transport_config) -> None:
|
|
"""
|
|
Initialize the global routing configuration from TransportConfig.
|
|
|
|
Args:
|
|
transport_config: The TransportConfig instance from settings
|
|
"""
|
|
global _global_routing_config, _routing_initialized
|
|
|
|
config = URLRoutingConfig(
|
|
default_verify_ssl=not transport_config.disable_ssl_verification_globally,
|
|
default_proxy_url=transport_config.proxy_url if transport_config.all_proxy else None,
|
|
)
|
|
|
|
# Add configured routes
|
|
for pattern, route in transport_config.transport_routes.items():
|
|
global_verify = not transport_config.disable_ssl_verification_globally
|
|
verify_ssl = route.verify_ssl if global_verify else False
|
|
proxy_url = route.proxy_url or transport_config.proxy_url if route.proxy else None
|
|
config.add_route(pattern, verify_ssl=verify_ssl, proxy_url=proxy_url)
|
|
|
|
# Hardcoded routes for specific domains (SSL verification disabled)
|
|
hardcoded_domains = [
|
|
"all://jxoplay.xyz",
|
|
"all://dlhd.dad",
|
|
"all://*.newkso.ru",
|
|
]
|
|
|
|
for domain in hardcoded_domains:
|
|
proxy_url = transport_config.proxy_url if transport_config.all_proxy else None
|
|
config.add_route(domain, verify_ssl=False, proxy_url=proxy_url)
|
|
|
|
# Default route for global settings
|
|
if transport_config.all_proxy or transport_config.disable_ssl_verification_globally:
|
|
default_proxy = transport_config.proxy_url if transport_config.all_proxy else None
|
|
config.add_route(
|
|
"all://",
|
|
verify_ssl=not transport_config.disable_ssl_verification_globally,
|
|
proxy_url=default_proxy,
|
|
)
|
|
|
|
_global_routing_config = config
|
|
_routing_initialized = True
|
|
|
|
logger.info(f"Initialized aiohttp routing with {len(config.routes)} routes")
|
|
|
|
|
|
def _ensure_routing_initialized():
|
|
"""Ensure routing configuration is initialized from settings."""
|
|
global _routing_initialized
|
|
if not _routing_initialized:
|
|
from mediaflow_proxy.configs import settings
|
|
|
|
initialize_routing_from_config(settings.transport_config)
|
|
|
|
|
|
def _get_ssl_context(verify: bool) -> ssl.SSLContext:
|
|
"""Get an SSL context with the specified verification setting."""
|
|
if verify:
|
|
return ssl.create_default_context()
|
|
else:
|
|
ctx = ssl.create_default_context()
|
|
ctx.check_hostname = False
|
|
ctx.verify_mode = ssl.CERT_NONE
|
|
return ctx
|
|
|
|
|
|
def create_proxy_connector(proxy_url: str, verify_ssl: bool = True) -> aiohttp.BaseConnector:
|
|
"""
|
|
Create a connector for proxy connections, supporting SOCKS5 and HTTP proxies.
|
|
|
|
Args:
|
|
proxy_url: The proxy URL (socks5://..., http://..., https://...)
|
|
verify_ssl: Whether to verify SSL certificates
|
|
|
|
Returns:
|
|
Appropriate connector for the proxy type
|
|
"""
|
|
parsed = urlparse(proxy_url)
|
|
scheme = parsed.scheme.lower()
|
|
|
|
ssl_context = _get_ssl_context(verify_ssl)
|
|
|
|
if scheme in ("socks5", "socks5h", "socks4", "socks4a"):
|
|
try:
|
|
from aiohttp_socks import ProxyConnector, ProxyType
|
|
|
|
proxy_type_map = {
|
|
"socks5": ProxyType.SOCKS5,
|
|
"socks5h": ProxyType.SOCKS5,
|
|
"socks4": ProxyType.SOCKS4,
|
|
"socks4a": ProxyType.SOCKS4,
|
|
}
|
|
|
|
return ProxyConnector(
|
|
proxy_type=proxy_type_map[scheme],
|
|
host=parsed.hostname,
|
|
port=parsed.port or 1080,
|
|
username=parsed.username,
|
|
password=parsed.password,
|
|
rdns=scheme.endswith("h"), # Remote DNS resolution for socks5h
|
|
ssl=ssl_context if not verify_ssl else None,
|
|
)
|
|
except ImportError:
|
|
logger.warning("aiohttp-socks not installed, SOCKS proxy support unavailable")
|
|
raise
|
|
else:
|
|
# HTTP/HTTPS proxy - use standard connector
|
|
# The proxy URL will be passed to the request method
|
|
return TCPConnector(
|
|
ssl=ssl_context,
|
|
limit=100,
|
|
limit_per_host=10,
|
|
)
|
|
|
|
|
|
def _create_connector(proxy_url: Optional[str], verify_ssl: bool) -> Tuple[aiohttp.BaseConnector, Optional[str]]:
|
|
"""
|
|
Create an appropriate connector based on proxy configuration.
|
|
|
|
Args:
|
|
proxy_url: The proxy URL or None
|
|
verify_ssl: Whether to verify SSL certificates
|
|
|
|
Returns:
|
|
Tuple of (connector, effective_proxy_url)
|
|
For SOCKS proxies, effective_proxy_url is None (handled by connector)
|
|
For HTTP proxies, effective_proxy_url is passed to requests
|
|
"""
|
|
if proxy_url:
|
|
parsed_proxy = urlparse(proxy_url)
|
|
if parsed_proxy.scheme in ("socks5", "socks5h", "socks4", "socks4a"):
|
|
# SOCKS proxy - use special connector, proxy handled internally
|
|
connector = create_proxy_connector(proxy_url, verify_ssl)
|
|
return connector, None
|
|
else:
|
|
# HTTP proxy - use standard connector, pass proxy to request
|
|
ssl_ctx = _get_ssl_context(verify_ssl)
|
|
connector = TCPConnector(ssl=ssl_ctx, limit=100, limit_per_host=10)
|
|
return connector, proxy_url
|
|
else:
|
|
ssl_ctx = _get_ssl_context(verify_ssl)
|
|
connector = TCPConnector(ssl=ssl_ctx, limit=100, limit_per_host=10)
|
|
return connector, None
|
|
|
|
|
|
@asynccontextmanager
|
|
async def create_aiohttp_session(
|
|
url: str = None,
|
|
timeout: typing.Union[int, float, ClientTimeout] = None,
|
|
headers: typing.Optional[typing.Dict[str, str]] = None,
|
|
verify: typing.Optional[bool] = None,
|
|
) -> typing.AsyncGenerator[typing.Tuple[ClientSession, typing.Optional[str]], None]:
|
|
"""
|
|
Create an aiohttp ClientSession with configured proxy routing and SSL settings.
|
|
|
|
This is the primary way to create HTTP sessions in the application.
|
|
It automatically applies URL-based routing for SSL verification and proxy settings.
|
|
|
|
Args:
|
|
url: The URL to configure the session for (used for routing)
|
|
timeout: Request timeout (int/float for total seconds, or ClientTimeout)
|
|
headers: Default headers for the session
|
|
verify: Override SSL verification (None = use routing config)
|
|
|
|
Yields:
|
|
Tuple of (session, proxy_url) - proxy_url should be passed to request methods
|
|
"""
|
|
_ensure_routing_initialized()
|
|
|
|
# Get routing configuration for the URL
|
|
routing_config = get_routing_config()
|
|
route_match = routing_config.match_url(url)
|
|
|
|
# Determine SSL verification
|
|
if verify is not None:
|
|
use_verify = verify
|
|
else:
|
|
use_verify = route_match.verify_ssl
|
|
|
|
# Create timeout
|
|
if timeout is None:
|
|
from mediaflow_proxy.configs import settings
|
|
|
|
timeout_config = ClientTimeout(total=settings.transport_config.timeout)
|
|
elif isinstance(timeout, (int, float)):
|
|
timeout_config = ClientTimeout(total=timeout)
|
|
else:
|
|
timeout_config = timeout
|
|
|
|
# Create connector
|
|
connector, effective_proxy_url = _create_connector(route_match.proxy_url, use_verify)
|
|
|
|
session = ClientSession(
|
|
connector=connector,
|
|
timeout=timeout_config,
|
|
headers=headers,
|
|
)
|
|
|
|
try:
|
|
yield session, effective_proxy_url
|
|
finally:
|
|
await session.close()
|