Files
Loren Eteval bf27577000 Update
Signed-off-by: Loren Eteval <loren.eteval@proton.me>
2023-11-18 22:40:00 +08:00

1331 lines
40 KiB
Python

from __future__ import annotations
from Furious.Interface.Encoder import UJSONEncoder, PyBase64Encoder
from Furious.Interface.ConfigurationFactory import ConfigurationFactory
from Furious.Library.Utility import parseHostPort
import typing
import functools
import urllib.parse
quote = functools.partial(urllib.parse.quote, safe='')
unquote = functools.partial(urllib.parse.unquote)
parse_qsl = functools.partial(urllib.parse.parse_qsl)
urlparse = functools.partial(urllib.parse.urlparse)
urlunparse = functools.partial(urllib.parse.urlunparse)
class ConfigurationXrayProxyOutboundObjectV(dict):
def __init__(
self,
protocol,
remote_host,
remote_port,
uuid_,
encryption,
type_,
security,
**kwargs,
):
networkObjectArgs, securityArgs, TLSObjectArgs = {}, {}, {}
if type_ == 'h2':
networkKey = 'httpSettings'
else:
networkKey = f'{type_}Settings'
networkObjectArgs[
networkKey
] = ConfigurationXray.kwargs2ProxyStreamSettingsNetworkObject(
type_, remote_host, kwargs
)
if security:
securityArgs['security'] = security
if security != 'none':
TLSObjectArgs[
# tlsSettings, realitySettings
f'{security}Settings'
] = ConfigurationXray.kwargs2ProxyStreamSettingsTLSObject(
protocol, remote_host, security, kwargs
)
super().__init__(
**{
'tag': 'proxy',
'protocol': protocol,
'settings': {
'vnext': [
{
'address': remote_host,
'port': int(remote_port),
'users': [
ConfigurationXray.kwargs2ProxyUserObject(
protocol, uuid_, encryption, kwargs
),
],
},
]
},
'streamSettings': {
'network': type_,
**networkObjectArgs,
**securityArgs,
**TLSObjectArgs,
},
'mux': {
'enabled': False,
'concurrency': -1,
},
},
)
class ConfigurationXrayProxyOutboundObjectSS(dict):
def __init__(self, method, password, address, port):
super().__init__(
**{
'tag': 'proxy',
'protocol': 'shadowsocks',
'settings': {
'servers': [
{
'address': address,
'port': int(port),
'method': method,
'password': password,
# TODO. Extension
'email': 'example@email.com',
'ota': False,
},
]
},
'streamSettings': {
'network': 'tcp',
},
'mux': {
'enabled': False,
'concurrency': -1,
},
}
)
class ConfigurationXrayProxyOutboundObjectTrojan(dict):
def __init__(self, password, address, port, type_, security, **kwargs):
networkObjectArgs, securityArgs, TLSObjectArgs = {}, {}, {}
if type_ == 'h2':
networkKey = 'httpSettings'
else:
networkKey = f'{type_}Settings'
networkObjectArgs[
networkKey
] = ConfigurationXray.kwargs2ProxyStreamSettingsNetworkObject(
type_, address, kwargs
)
if security:
securityArgs['security'] = security
if security != 'none':
TLSObjectArgs[
# tlsSettings, realitySettings
f'{security}Settings'
] = ConfigurationXray.kwargs2ProxyStreamSettingsTLSObject(
'trojan', address, security, kwargs
)
super().__init__(
**{
'tag': 'proxy',
'protocol': 'trojan',
'settings': {
'servers': [
{
'address': address,
'port': int(port),
'password': password,
# TODO. Extension
'email': 'example@email.com',
},
]
},
'streamSettings': {
'network': type_,
**networkObjectArgs,
**securityArgs,
**TLSObjectArgs,
},
'mux': {
'enabled': False,
'concurrency': -1,
},
},
)
class ConfigurationXray(ConfigurationFactory):
def __init__(self, config: typing.Union[str, dict] = ''):
super().__init__(config)
@property
def proxyOutboundObject(self) -> dict:
try:
for outboundObject in self['outbounds']:
if outboundObject['tag'] == 'proxy':
return outboundObject
return {}
except Exception:
# Any non-exit exceptions
return {}
@property
def proxyProtocol(self) -> str:
try:
return self.proxyOutboundObject['protocol']
except Exception:
# Any non-exit exceptions
return ''
@property
def proxyServerObject(self) -> dict:
try:
if (
self.proxyProtocol.lower() == 'vmess'
or self.proxyProtocol.lower() == 'vless'
):
return self.proxyOutboundObject['settings']['vnext'][0]
if (
self.proxyProtocol.lower() == 'shadowsocks'
or self.proxyProtocol.lower() == 'trojan'
):
return self.proxyOutboundObject['settings']['servers'][0]
return {}
except Exception:
# Any non-exit exceptions
return {}
@property
def proxyUserObject(self) -> dict:
try:
return self.proxyServerObject['users'][0]
except Exception:
# Any non-exit exceptions
return {}
@property
def proxyStreamSettingsObject(self) -> dict:
try:
return self.proxyOutboundObject['streamSettings']
except Exception:
# Any non-exit exceptions
return {}
@property
def proxyStreamSettingsTLS(self) -> str:
try:
return self.proxyStreamSettingsObject.get('security', 'none')
except Exception:
# Any non-exit exceptions
return ''
@property
def proxyStreamSettingsTLSObject(self) -> dict:
try:
# tlsSettings, realitySettings
TLSKey = f'{self.proxyStreamSettingsTLS}Settings'
return self.proxyStreamSettingsObject[TLSKey]
except Exception:
# Any non-exit exceptions
return {}
@property
def proxyStreamSettingsNetwork(self) -> str:
try:
return self.proxyStreamSettingsObject['network']
except Exception:
# Any non-exit exceptions
return ''
@property
def proxyStreamSettingsNetworkObject(self) -> dict:
try:
if self.proxyStreamSettingsNetwork == 'h2':
networkKey = 'httpSettings'
else:
networkKey = f'{self.proxyStreamSettingsNetwork}Settings'
return self.proxyStreamSettingsObject[networkKey]
except Exception:
# Any non-exit exceptions
return {}
@property
def kwargsFromVMessProxyStreamSettingsNetworkObject(self) -> dict:
kwargs = {}
network = self.proxyStreamSettingsNetwork
networkObject = self.proxyStreamSettingsNetworkObject
if not networkObject:
return kwargs
def hasKey(key):
return networkObject.get(key) is not None
if network == 'tcp':
try:
kwargs['type'] = networkObject['header']['type']
except Exception:
# Any non-exit exceptions
pass
elif network == 'kcp':
try:
# Get order matters here
if hasKey('seed'):
kwargs['path'] = networkObject['seed']
kwargs['type'] = networkObject['header']['type']
except Exception:
# Any non-exit exceptions
pass
elif network == 'ws':
try:
# Get order matters here
if hasKey('path'):
kwargs['path'] = quote(networkObject['path'])
if networkObject['headers']['Host']:
kwargs['host'] = quote(networkObject['headers']['Host'])
except Exception:
# Any non-exit exceptions
pass
elif network == 'h2' or network == 'http':
try:
# Get order matters here
if hasKey('path'):
kwargs['path'] = quote(networkObject['path'])
kwargs['host'] = quote(','.join(networkObject['host']))
except Exception:
# Any non-exit exceptions
pass
elif network == 'quic':
try:
# Get order matters here
if hasKey('security'):
kwargs['host'] = networkObject['security']
if hasKey('key'):
kwargs['path'] = quote(networkObject['key'])
kwargs['type'] = networkObject['header']['type']
except Exception:
# Any non-exit exceptions
pass
elif network == 'grpc':
if hasKey('serviceName'):
kwargs['path'] = networkObject['serviceName']
return kwargs
@property
def kwargsFromVLESSProxyStreamSettingsNetworkObject(self) -> dict:
kwargs = {}
network = self.proxyStreamSettingsNetwork
networkObject = self.proxyStreamSettingsNetworkObject
if not networkObject:
return kwargs
def hasKey(key):
return networkObject.get(key) is not None
if network == 'tcp':
try:
kwargs['headerType'] = networkObject['header']['type']
except Exception:
# Any non-exit exceptions
pass
elif network == 'kcp':
try:
# Get order matters here
if hasKey('seed'):
kwargs['seed'] = networkObject['seed']
kwargs['headerType'] = networkObject['header']['type']
except Exception:
# Any non-exit exceptions
pass
elif network == 'ws':
try:
# Get order matters here
if hasKey('path'):
kwargs['path'] = quote(networkObject['path'])
if networkObject['headers']['Host']:
kwargs['host'] = quote(networkObject['headers']['Host'])
except Exception:
# Any non-exit exceptions
pass
elif network == 'h2' or network == 'http':
try:
# Get order matters here
if hasKey('path'):
kwargs['path'] = quote(networkObject['path'])
kwargs['host'] = quote(','.join(networkObject['host']))
except Exception:
# Any non-exit exceptions
pass
elif network == 'quic':
try:
# Get order matters here
if hasKey('security'):
kwargs['quicSecurity'] = networkObject['security']
if hasKey('key'):
kwargs['path'] = quote(networkObject['key'])
kwargs['headerType'] = networkObject['header']['type']
except Exception:
# Any non-exit exceptions
pass
elif network == 'grpc':
if hasKey('serviceName'):
kwargs['serviceName'] = networkObject['serviceName']
return kwargs
@staticmethod
def kwargs2ProxyStreamSettingsNetworkObject(type_, remote_host, kwargs) -> dict:
# Note:
# v2rayN share standard doesn't require unquote. Still
# unquote value according to (VMess AEAD / VLESS) standard
if type_ == 'tcp':
TcpObject = {}
if kwargs.get('headerType', 'none'):
headerType = kwargs.get('headerType', 'none')
# Some dumb share link set this value to 'auto'. Protect it
if headerType != 'auto':
TcpObject['header'] = {
'type': headerType,
}
# Request settings for HTTP
if headerType == 'http' and kwargs.get('host'):
TcpObject['header']['request'] = {
'version': '1.1',
'method': 'GET',
'path': [kwargs.get('path', '/')],
'headers': {
'Host': [unquote(kwargs.get('host'))],
'User-Agent': [
(
'Mozilla/5.0 (Windows NT 10.0; WOW64) '
'AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/53.0.2785.143 Safari/537.36'
),
(
'Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) '
'AppleWebKit/601.1 (KHTML, like Gecko) '
'CriOS/53.0.2785.109 '
'Mobile/14A456 '
'Safari/601.1.46'
),
],
'Accept-Encoding': ['gzip, deflate'],
'Connection': ['keep-alive'],
'Pragma': 'no-cache',
},
}
return TcpObject
elif type_ == 'kcp':
KcpObject = {
# Extension. From v2rayN
'uplinkCapacity': 12,
'downlinkCapacity': 100,
}
if kwargs.get('headerType', 'none'):
headerType = kwargs.get('headerType', 'none')
# Some dumb share link set this value to 'auto'. Protect it
if headerType != 'auto':
KcpObject['header'] = {
'type': headerType,
}
if kwargs.get('seed'):
KcpObject['seed'] = kwargs.get('seed')
return KcpObject
elif type_ == 'ws':
WebSocketObject = {}
if kwargs.get('path', '/'):
WebSocketObject['path'] = unquote(kwargs.get('path', '/'))
if kwargs.get('host'):
WebSocketObject['headers'] = {
'Host': unquote(kwargs.get('host')),
}
return WebSocketObject
elif type_ == 'h2' or type_ == 'http':
HttpObject = {}
if kwargs.get('host', remote_host):
# Protect "xxx," format
HttpObject['host'] = list(
filter(
lambda x: x != '',
unquote(kwargs.get('host', remote_host)).split(','),
)
)
if kwargs.get('path', '/'):
HttpObject['path'] = unquote(kwargs.get('path', '/'))
return HttpObject
elif type_ == 'quic':
QuicObject = {}
if kwargs.get('quicSecurity', 'none'):
QuicObject['security'] = kwargs.get('quicSecurity', 'none')
if kwargs.get('key'):
QuicObject['key'] = unquote(kwargs.get('key'))
if kwargs.get('headerType', 'none'):
headerType = kwargs.get('headerType', 'none')
# Some dumb share link set this value to 'auto'. Protect it
if headerType != 'auto':
QuicObject['header'] = {
'type': headerType,
}
return QuicObject
elif type_ == 'grpc':
GRPCObject = {}
if kwargs.get('serviceName'):
GRPCObject['serviceName'] = kwargs.get('serviceName')
if kwargs.get('mode', 'gun'):
GRPCObject['multiMode'] = kwargs.get('mode', 'gun') == 'multi'
return GRPCObject
@property
def kwargsFromProxyStreamSettingsTLSObject(self) -> dict:
kwargs = {}
TLS = self.proxyStreamSettingsTLS
TLSObject = self.proxyStreamSettingsTLSObject
if TLS == '' or TLS == 'none':
return kwargs
if TLS == 'reality' or TLS == 'tls':
if TLSObject.get('fingerprint'):
kwargs['fp'] = TLSObject['fingerprint']
if TLSObject.get('serverName'):
kwargs['sni'] = TLSObject['serverName']
if TLSObject.get('alpn'):
kwargs['alpn'] = quote(','.join(TLSObject['alpn']))
if TLS == 'reality':
# More kwargs for reality
if TLSObject.get('publicKey'):
kwargs['pbk'] = TLSObject['publicKey']
if TLSObject.get('shortId'):
kwargs['sid'] = TLSObject['shortId']
if TLSObject.get('spiderX'):
kwargs['spx'] = quote(TLSObject['spiderX'])
return kwargs
@staticmethod
def kwargs2ProxyStreamSettingsTLSObject(
protocol, remote_host, security, kwargs
) -> dict:
TLSObject = {}
if security == 'reality' or security == 'tls':
# Note: If specify default value 'chrome', some share link fails.
# Leave default value as empty
fp = kwargs.get('fp')
sni = kwargs.get('sni')
# Protect "xxx," format
alpn = list(
filter(
lambda x: x != '',
unquote(kwargs.get('alpn', '')).split(','),
)
)
if fp:
TLSObject['fingerprint'] = fp
if sni:
TLSObject['serverName'] = sni
else:
host = ''
if protocol == 'vmess':
host = kwargs.get('host')
if protocol == 'vless':
host = remote_host
if host:
TLSObject['serverName'] = host
if alpn:
TLSObject['alpn'] = alpn
if security == 'reality':
# More args for reality
pbk = kwargs.get('pbk')
sid = kwargs.get('sid', '')
spx = kwargs.get('spx', '')
if pbk:
TLSObject['publicKey'] = pbk
TLSObject['shortId'] = sid
TLSObject['spiderX'] = unquote(spx)
return TLSObject
@staticmethod
def kwargs2ProxyUserObject(protocol, uuid_, encryption, kwargs) -> dict:
if protocol == 'vmess':
UserObject = {
'id': uuid_,
'security': encryption,
# TODO. Extension
'email': 'example@email.com',
}
# For VMess(v2rayN share standard) only.
if kwargs.get('aid') is not None:
UserObject['alterId'] = kwargs.get('aid')
return UserObject
if protocol == 'vless':
UserObject = {
'id': uuid_,
'encryption': encryption,
# TODO. Extension
'email': 'example@email.com',
}
if kwargs.get('flow'):
# flow is empty, TLS. Otherwise, XTLS.
UserObject['flow'] = kwargs.get('flow')
return UserObject
return {}
def toURI(self, remark: str = '') -> str:
if self.proxyProtocol.lower() == 'vmess':
netloc = PyBase64Encoder.encode(
UJSONEncoder.encode(
{
'v': '2',
'ps': remark,
**{
key: self.proxyServerObject[value]
for key, value in {
'add': 'address',
'port': 'port',
}.items()
},
**{
key: self.proxyUserObject[value]
for key, value in {
'id': 'id',
'aid': 'alterId',
'scy': 'security',
}.items()
},
'net': self.proxyStreamSettingsNetwork,
'tls': self.proxyStreamSettingsTLS,
# kwargs
**self.kwargsFromVMessProxyStreamSettingsNetworkObject,
**self.kwargsFromProxyStreamSettingsTLSObject,
}
).encode()
).decode()
return urlunparse(['vmess', netloc, '', '', {}, ''])
if self.proxyProtocol.lower() == 'vless':
flowArg = {}
if self.proxyUserObject.get('flow'):
flowArg['flow'] = self.proxyUserObject['flow']
netloc = (
self.proxyUserObject['id']
+ '@'
+ self.proxyServerObject['address']
+ ':'
+ str(self.proxyServerObject['port'])
)
query = '&'.join(
f'{key}={value}'
for key, value in {
'encryption': self.proxyUserObject['encryption'],
'type': self.proxyStreamSettingsNetwork,
'security': self.proxyStreamSettingsTLS,
# kwargs
**flowArg,
**self.kwargsFromVLESSProxyStreamSettingsNetworkObject,
**self.kwargsFromProxyStreamSettingsTLSObject,
}.items()
)
return urlunparse(['vless', netloc, '', '', query, quote(remark)])
if self.proxyProtocol.lower() == 'shadowsocks':
method, password, address, port = list(
self.proxyServerObject[value]
for value in ['method', 'password', 'address', 'port']
)
netloc = f'{quote(method)}:{quote(password)}@{address}:{port}'
return urlunparse(['ss', netloc, '', '', '', quote(remark)])
if self.proxyProtocol.lower() == 'trojan':
password, address, port = list(
self.proxyServerObject[value]
for value in ['password', 'address', 'port']
)
netloc = f'{quote(password)}@{address}:{port}'
query = '&'.join(
f'{key}={value}'
for key, value in {
'type': self.proxyStreamSettingsNetwork,
'security': self.proxyStreamSettingsTLS,
# kwargs
**self.kwargsFromVLESSProxyStreamSettingsNetworkObject,
**self.kwargsFromProxyStreamSettingsTLSObject,
}.items()
)
return urlunparse(['trojan', netloc, '', '', query, quote(remark)])
raise ValueError(
f'Unrecognized protocol {self.proxyProtocol} when converting to URI'
)
@staticmethod
def URI2ProxyOutboundObjectVMess(URI: str) -> typing.Tuple[str, dict]:
try:
myHead, myBody = URI.split('://')
except Exception:
# Any non-exit exceptions
return '', {}
try:
myData = PyBase64Encoder.decode(myBody)
myJSON = UJSONEncoder.decode(myData)
except Exception:
# Any non-exit exceptions
return ConfigurationXray.URI2ProxyOutboundObjectVLESS(URI, protocol='vmess')
else:
def getOrDefault(key, default=''):
return myJSON.get(key, default)
remark = unquote(getOrDefault('ps'))
return (
remark,
# Ignore: v
ConfigurationXrayProxyOutboundObjectV(
'vmess',
getOrDefault('add'),
int(getOrDefault('port', '0')),
getOrDefault('id'),
getOrDefault('scy', 'auto'),
getOrDefault('net', 'tcp'),
getOrDefault('tls', 'none'),
# kwargs. v2rayN share standard -> (VMess AEAD / VLESS) standard
aid=int(getOrDefault('aid', '0')),
headerType=getOrDefault('type', 'none'),
host=getOrDefault('host'),
quicSecurity=getOrDefault('host', 'none'),
path=getOrDefault('path'),
key=getOrDefault('path'),
seed=getOrDefault('path'),
serviceName=getOrDefault('path'),
sni=getOrDefault('sni'),
alpn=getOrDefault('alpn'),
# Note: If specify default value 'chrome', some share link fails.
# Leave default value as empty
fp=getOrDefault('fp'),
),
)
@staticmethod
def URI2ProxyOutboundObjectVLESS(URI: str, **kwargs) -> typing.Tuple[str, dict]:
result = urlparse(URI)
remark = unquote(result.fragment)
queryObject = {key: value for key, value in parse_qsl(result.query)}
uuid_, server = result.netloc.split('@')
remote_host, remote_port = parseHostPort(server)
encryption = queryObject.pop('encryption', 'none')
type_ = queryObject.pop('type', 'tcp')
security = queryObject.pop('security', 'none')
return (
remark,
ConfigurationXrayProxyOutboundObjectV(
kwargs.pop('protocol', 'vless'),
remote_host,
int(remote_port),
uuid_,
encryption,
type_,
security,
# kwargs
**queryObject,
),
)
@staticmethod
def URI2ProxyOutboundObjectSS(URI: str) -> typing.Tuple[str, dict]:
try:
result = urlparse(URI)
remark = unquote(result.fragment)
except Exception:
# Any non-exit exceptions
return '', {}
def getSSParams():
try:
# ss://base64...#fragment
userinfo, server = (
PyBase64Encoder.decode(result.netloc).decode().split('@')
)
return [*userinfo.split(':'), *parseHostPort(server)]
except Exception:
# Any non-exit exceptions
pass
# Begin SIP002...
try:
# Try pack with 3 element
userinfo, server = result.netloc.split('@')
# Some old SS share link doesn't add padding
# in base64 encoding. Add padding to userinfo
return [
*PyBase64Encoder.decode(userinfo + '===').decode().split(':'),
*parseHostPort(server),
]
except Exception:
# Any non-exit exceptions
pass
try:
# Try pack with 4 element
userinfo, server = result.netloc.split('@')
return [*userinfo.split(':'), *parseHostPort(server)]
except Exception:
# Any non-exit exceptions
pass
raise ValueError(f'Invalid SS URI format {URI}')
return (
remark,
ConfigurationXrayProxyOutboundObjectSS(*getSSParams()),
)
@staticmethod
def URI2ProxyOutboundObjectTrojan(URI: str) -> typing.Tuple[str, dict]:
result = urlparse(URI)
remark = unquote(result.fragment)
queryObject = {key: value for key, value in parse_qsl(result.query)}
password, server = result.netloc.split('@')
address, port = parseHostPort(server)
type_ = queryObject.pop('type', 'tcp')
# For Trojan: Assign tls by default
security = queryObject.pop('security', 'tls')
return (
remark,
ConfigurationXrayProxyOutboundObjectTrojan(
password, address, port, type_, security, **queryObject
),
)
@staticmethod
def URI2ProxyOutboundObject(URI: str) -> typing.Tuple[str, dict]:
try:
myHead, myBody = URI.split('://')
except Exception as ex:
# Any non-exit exceptions
raise ValueError(f'Invalid URI format {URI}') from ex
if myHead.lower() == 'vmess':
return ConfigurationXray.URI2ProxyOutboundObjectVMess(URI)
if myHead.lower() == 'vless':
return ConfigurationXray.URI2ProxyOutboundObjectVLESS(URI)
if myHead.lower() == 'ss':
return ConfigurationXray.URI2ProxyOutboundObjectSS(URI)
if myHead.lower() == 'trojan':
return ConfigurationXray.URI2ProxyOutboundObjectTrojan(URI)
raise ValueError(f'Unrecognized URI scheme {URI}')
def fromURI(self, URI: str) -> typing.Tuple[str, bool]:
try:
remark, proxyOutboundObject = ConfigurationXray.URI2ProxyOutboundObject(URI)
dict.__init__(
self,
**{
# log
'log': {
'access': '',
'error': '',
'loglevel': 'warning',
},
# inbounds
'inbounds': [
{
'tag': 'socks',
'port': 10808,
'listen': '127.0.0.1',
'protocol': 'socks',
'sniffing': {
'enabled': True,
'destOverride': [
'http',
'tls',
],
},
'settings': {
'auth': 'noauth',
'udp': True,
'allowTransparent': False,
},
},
{
'tag': 'http',
'port': 10809,
'listen': '127.0.0.1',
'protocol': 'http',
'sniffing': {
'enabled': True,
'destOverride': [
'http',
'tls',
],
},
'settings': {
'auth': 'noauth',
'udp': True,
'allowTransparent': False,
},
},
],
# outbounds
'outbounds': [
# proxy
proxyOutboundObject,
# direct
{
'tag': 'direct',
'protocol': 'freedom',
'settings': {},
},
# block
{
'tag': 'block',
'protocol': 'blackhole',
'settings': {
'response': {
'type': 'http',
}
},
},
],
'routing': {},
},
)
return remark, True
except Exception:
# Any non-exit exceptions
return '', False
def httpProxyEndpoint(self) -> str:
try:
for inbound in self['inbounds']:
if inbound['protocol'] == 'http':
# Note: If there are multiple http inbounds
# satisfied, the first one will be chosen.
return str(inbound['listen']) + ':' + str(inbound['port'])
return ''
except Exception:
# Any non-exit exceptions
return ''
def socksProxyEndpoint(self) -> str:
try:
for inbound in self['inbounds']:
if inbound['protocol'] == 'socks':
# Note: If there are multiple socks inbounds
# satisfied, the first one will be chosen.
return str(inbound['listen']) + ':' + str(inbound['port'])
return ''
except Exception:
# Any non-exit exceptions
return ''
def setHttpProxyEndpoint(self, endpoint: str) -> bool:
try:
for inbound in self['inbounds']:
if inbound['protocol'] == 'http':
# Note: If there are multiple http inbounds
# satisfied, the first one will be chosen.
listen, port = parseHostPort(endpoint)
inbound['listen'], inbound['port'] = listen, int(port)
return True
return False
except Exception:
# Any non-exit exceptions
return False
def setSocksProxyEndpoint(self, endpoint: str) -> bool:
try:
for inbound in self['inbounds']:
if inbound['protocol'] == 'socks':
# Note: If there are multiple socks inbounds
# satisfied, the first one will be chosen.
listen, port = parseHostPort(endpoint)
inbound['listen'], inbound['port'] = listen, int(port)
return True
return False
except Exception:
# Any non-exit exceptions
return False
def logAccessPath(self) -> str:
try:
return self['log']['access']
except Exception:
# Any non-exit exceptions
return ''
def logErrorPath(self) -> str:
try:
return self['log']['error']
except Exception:
# Any non-exit exceptions
return ''
def setLogAccessPath(self, path) -> bool:
try:
self['log']['access'] = path
return True
except Exception:
# Any non-exit exceptions
return False
def setLogErrorPath(self, path) -> bool:
try:
self['log']['error'] = path
return True
except Exception:
# Any non-exit exceptions
return False
class ConfigurationHysteria1(ConfigurationFactory):
def __init__(self, config: typing.Union[str, dict] = ''):
super().__init__(config)
def toURI(self, remark: str = '') -> str:
raise NotImplementedError
def fromURI(self, URI: str) -> typing.Tuple[str, bool]:
raise NotImplementedError
def httpProxyEndpoint(self) -> str:
try:
return self['http']['listen']
except Exception:
# Any non-exit exceptions
return ''
def socksProxyEndpoint(self) -> str:
try:
return self['socks5']['listen']
except Exception:
# Any non-exit exceptions
return ''
def setHttpProxyEndpoint(self, endpoint: str) -> bool:
try:
self['http']['listen'] = endpoint
return True
except Exception:
# Any non-exit exceptions
return False
def setSocksProxyEndpoint(self, endpoint: str) -> bool:
try:
self['socks5']['listen'] = endpoint
return True
except Exception:
# Any non-exit exceptions
return False
class ConfigurationHysteria2(ConfigurationFactory):
def __init__(self, config: typing.Union[str, dict] = ''):
super().__init__(config)
def toURI(self, remark: str = '') -> str:
netloc = self['auth'] + '@' + self['server']
tlsArg, obfsArg = {}, {}
if self.get('tls'):
if self['tls'].get('sni'):
tlsArg['sni'] = self['tls']['sni']
if self['tls'].get('insecure') is True:
tlsArg['insecure'] = '1'
else:
tlsArg['insecure'] = '0'
if self['tls'].get('pinSHA256'):
tlsArg['pinSHA256'] = self['tls']['pinSHA256']
if self.get('obfs'):
obfsType = self['obfs'].get('type', 'salamander')
obfsArg['obfs'] = obfsType
obfsArg['obfs-password'] = self['obfs'][obfsType]['password']
query = '&'.join(
f'{key}={value}'
for key, value in {
**tlsArg,
**obfsArg,
}.items()
)
return urlunparse(['hysteria2', netloc, '', '', query, quote(remark)])
def fromURI(self, URI: str) -> typing.Tuple[str, bool]:
try:
result = urlparse(URI)
remark = unquote(result.fragment)
queryObject = {key: value for key, value in parse_qsl(result.query)}
if result.scheme != 'hysteria2' and result.scheme != 'hy2':
raise ValueError('Invalid hysteria2 URI scheme')
auth, server = result.netloc.split('@')
obfs = queryObject.get('obfs', '')
obfsPassword = queryObject.get('obfs-password', '')
sni = queryObject.get('sni', '')
insecure = queryObject.get('insecure', False)
if isinstance(insecure, bool):
pass
elif insecure == '1':
insecure = True
else:
insecure = False
pinSHA256 = queryObject.get('pinSHA256', '')
obfsArg = {}
pinSHA256Arg = {}
if obfs and obfsPassword:
obfsArg['obfs'] = {
'type': obfs,
obfs: {
'password': obfsPassword,
},
}
if pinSHA256:
pinSHA256Arg['pinSHA256'] = pinSHA256
dict.__init__(
self,
**{
'server': server,
'auth': auth,
'tls': {
'sni': sni,
'insecure': insecure,
**pinSHA256Arg,
},
**obfsArg,
'socks5': {
'listen': '127.0.0.1:10808',
},
'http': {
'listen': '127.0.0.1:10809',
},
},
)
return remark, True
except Exception:
# Any non-exit exceptions
return '', False
def httpProxyEndpoint(self) -> str:
try:
return self['http']['listen']
except Exception:
# Any non-exit exceptions
return ''
def socksProxyEndpoint(self) -> str:
try:
return self['socks5']['listen']
except Exception:
# Any non-exit exceptions
return ''
def setHttpProxyEndpoint(self, endpoint: str) -> bool:
try:
self['http']['listen'] = endpoint
return True
except Exception:
# Any non-exit exceptions
return False
def setSocksProxyEndpoint(self, endpoint: str) -> bool:
try:
self['socks5']['listen'] = endpoint
return True
except Exception:
# Any non-exit exceptions
return False