diff --git a/Basis/Filter.py b/Basis/Filter.py index 76bdf7d..36860f5 100644 --- a/Basis/Filter.py +++ b/Basis/Filter.py @@ -54,7 +54,7 @@ filterObject = { 'errMsg': { 'type': str, 'optional': True, # `errMsg` is not force require - 'default': 'filter error', + 'default': 'Filter error', 'allowNone': False, # `errMsg` couldn't be None } } @@ -75,7 +75,7 @@ def Filter(raw: dict, rules: dict) -> dict: # pretreatment process (raw --[copy / default value]--> data) if key not in raw: # key not exist if not rule['optional']: # force require key not exist - raise RuntimeError('Miss `%s` field' % key) + raise RuntimeError('Missing `%s` field' % key) data[key] = rule['default'] # set default value else: # key exist data[key] = raw[key] @@ -97,11 +97,21 @@ def Filter(raw: dict, rules: dict) -> dict: elif type(data[key]) not in rule['type']: # type not in allow list raise RuntimeError('Invalid `%s` field' % key) elif type(rule['type']) == dict: # check subObject + if type(data[key]) != dict: + raise RuntimeError('Invalid sub object in `%s`' % key) # subObject content should be dict if not rule['multiSub']: # single subObject - data[key] = Filter(data[key], rule['type']) + subRules = rule['type'] else: # multi subObject - # TODO: multi sub filter - pass + if rule['indexKey'] not in data[key]: # confirm index key exist + raise RuntimeError('Index key not found in `%s`' % key) + subType = data[key][rule['indexKey']] + if subType not in rule['type']: # confirm subObject rule exist + raise RuntimeError('Unknown index `%s` in key `%s`' % (subType, key)) + subRules = rule['type'][subType] + try: + data[key] = Filter(data[key], subRules) + except RuntimeError as exp: + raise RuntimeError('%s (in `%s`)' % (exp, key)) # add located info continue elif rule['type'] != any: # type == any -> skip type filter raise RuntimeError('Unknown `type` in rules') @@ -117,4 +127,4 @@ def rulesFilter(rules: dict) -> dict: return result -filterObject = rulesFilter(filterObject) # format itself +filterObject = rulesFilter(filterObject) # self-format diff --git a/Basis/Functions.py b/Basis/Functions.py index 544de97..b718065 100644 --- a/Basis/Functions.py +++ b/Basis/Functions.py @@ -10,6 +10,17 @@ from IPy import IP from Basis.Logger import logging +def isHost(raw) -> bool: + # TODO: isHost function + return True + + +def isPort(port: int) -> bool: + if type(port) != int: + return False + return 1 <= port <= 65535 # 1 ~ 65535 + + def md5Sum(data: str, encode: str = 'utf-8') -> str: return hashlib.md5(data.encode(encoding = encode)).hexdigest() @@ -42,6 +53,36 @@ def genUUID() -> str: # generate uuid v5 )) +def toInt(raw) -> int: + try: + return int(raw) + except: + raise RuntimeError('Unable convert to int') + + +def toStr(raw, acceptNone: bool = True) -> str: + if raw is None and acceptNone: # None -> '' + return '' + if isinstance(raw, bytes): # bytes -> str + return str(raw, encoding = 'utf-8') + try: + return str(raw) + except: + raise RuntimeError('Unable convert to str') + + +def toBool(raw) -> bool: + if isinstance(raw, (bool, int, float)): + return bool(raw) + try: + raw = toStr(raw).strip().lower() + if raw in ['true', 'false']: + return True if raw == 'true' else False + return int(raw) != 0 + except: + raise RuntimeError('Unable convert to bool') + + def getAvailablePort(rangeStart: int = 1024, rangeEnd: int = 65535, waitTime: int = 10) -> int: # get available port if rangeStart > rangeEnd or rangeStart < 1 or rangeEnd > 65535: raise RuntimeError('Invalid port range') @@ -86,23 +127,3 @@ def networkStatus() -> list: # get all network connections }) logging.debug('Network status -> found %i connections' % len(result)) return result - - -def toInt(raw) -> int: - pass - - -def toStr(raw) -> str: - pass - - -def toBool(raw) -> bool: - pass - - -def isHost(raw) -> bool: - pass - - -def isPort(raw) -> bool: - pass diff --git a/Filter/Plugin.py b/Filter/Plugin.py index 2b98da0..492b500 100644 --- a/Filter/Plugin.py +++ b/Filter/Plugin.py @@ -1,5 +1,58 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import copy +from Basis.Functions import toStr +from Basis.Filter import rulesFilter +from Basis.Constant import pluginClients + +pluginAlias = { + 'obfs-local': {'obfs', 'simple-obfs'}, + 'simple-tls': {'tls', 'simple-tls'}, + 'v2ray-plugin': {'v2ray'}, + 'xray-plugin': {'xray'}, + 'kcptun-client': {'kcptun'}, + 'gost-plugin': {'gost'}, + 'ck-client': {'ck', 'cloak'}, + 'gq-client': {'gq', 'goquiet', 'go-quiet'}, + 'mtt-client': {'mtt', 'mos-tls-tunnel'}, + 'rabbit-plugin': {'rabbit', 'rabbit-tcp'}, + 'qtun-client': {'qtun'}, + 'gun-plugin': {'gun'}, +} + +pluginObject = rulesFilter({ + 'type': { + 'type': str, + 'format': lambda s: pluginFormat(toStr(s)), + 'filter': lambda s: s in pluginClients, + 'errMsg': 'Unknown SIP003 plugin' + }, + 'param': { + 'optional': True, + 'default': '', + 'type': str, + 'format': toStr, + 'errMsg': 'Invalid SIP003 param' + } +}) + + +def loadAlias() -> None: + for plugin in pluginAlias: + for alias in copy.copy(pluginAlias[plugin]): + pluginAlias[plugin].update({ # better compatibility + alias + '-local', alias + '-plugin', + alias + '-client', alias + '-server', + }) + + def pluginFormat(pluginName: str) -> str: - return pluginName + pluginName = pluginName.strip().lower().replace('_', '-') + for plugin, alias in pluginAlias.items(): + if pluginName in alias: + return plugin + return pluginName # alias not found + + +loadAlias() diff --git a/Filter/Shadowsocks.py b/Filter/Shadowsocks.py index 0f038ec..1053eee 100644 --- a/Filter/Shadowsocks.py +++ b/Filter/Shadowsocks.py @@ -1,27 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from Filter.Plugin import pluginFormat +from Basis.Filter import rulesFilter +from Filter.Plugin import pluginObject +from Basis.Constant import ssAllMethods from Basis.Functions import toInt, toStr from Basis.Functions import isHost, isPort -from Basis.Filter import Filter, rulesFilter -from Basis.Constant import ssMethods, pluginClients - -pluginObject = rulesFilter({ - 'type': { - 'type': str, - 'format': lambda s: pluginFormat(toStr(s).strip().lower()), - 'filter': lambda s: s in pluginClients, - 'errMsg': 'Unknown SIP003 plugin' - }, - 'param': { - 'optional': False, - 'default': '', - 'type': str, - 'format': toStr, - 'errMsg': 'Invalid SIP003 param' - } -}) ssObject = rulesFilter({ 'server': { @@ -39,7 +23,7 @@ ssObject = rulesFilter({ 'method': { 'type': str, 'format': lambda s: toStr(s).strip().lower().replace('_', '-'), - 'filter': lambda s: s in ssMethods, + 'filter': lambda s: s in ssAllMethods, 'errMsg': 'Unknown Shadowsocks method' }, 'passwd': { @@ -48,13 +32,10 @@ ssObject = rulesFilter({ 'errMsg': 'Invalid password content' }, 'plugin': { - 'optional': False, + 'optional': True, 'default': None, 'allowNone': True, 'type': pluginObject, 'errMsg': 'Invalid pluginObject' } }) - -from pprint import pprint -pprint(ssObject, sort_dicts = False) diff --git a/demo.py b/demo.py index 7be03fd..ddaa726 100755 --- a/demo.py +++ b/demo.py @@ -1,7 +1,22 @@ #!/usr/bin/env python -import Filter.Shadowsocks +from pprint import pprint +from Basis.Filter import Filter +from Basis.Filter import filterObject +from Filter.Shadowsocks import ssObject -# from pprint import pprint -# from Basis.Filter import filterObject +# pprint(ssObject, sort_dicts = False) # pprint(filterObject, sort_dicts = False) + +ssProxy = { + 'server': '1.1.1.1', + 'port': '12345', + 'method': 'none', + 'passwd': 'dnomd343', + 'plugin': { + 'type': 'obfs', + + } +} +ret = Filter(ssProxy, ssObject) +pprint(ret, sort_dicts = False)