diff --git a/ProxyFilter/VMess.py b/ProxyFilter/VMess.py index 2d7aa33..2a7c82a 100644 --- a/ProxyFilter/VMess.py +++ b/ProxyFilter/VMess.py @@ -29,7 +29,7 @@ quicMethodList = [ vmessFilterRules = { 'rootObject': { 'remark': { - 'optional': True, + 'optional': False, 'default': '', 'type': str, 'format': baseFunc.toStr @@ -66,7 +66,6 @@ vmessFilterRules = { 'default': 0, 'type': int, 'format': baseFunc.toInt, - # 'filter': 123, 'filter': lambda aid: aid in range(0, 65536), # 0 ~ 65535 'errMsg': 'Illegal alter Id' }, @@ -89,9 +88,10 @@ vmessFilterRules = { 'type': { 'optional': True, 'type': str, + 'indexKey': True, 'format': baseFunc.toStrTidy, 'filter': lambda streamType: streamType == 'tcp', - 'errMsg': 'Unknown stream type' + 'errMsg': 'Unexpected stream type' }, 'obfs': { 'optional': False, @@ -110,9 +110,10 @@ vmessFilterRules = { 'type': { 'optional': True, 'type': str, + 'indexKey': True, 'format': baseFunc.toStrTidy, 'filter': lambda streamType: streamType == 'kcp', - 'errMsg': 'Unknown stream type' + 'errMsg': 'Unexpected stream type' }, 'seed': { 'optional': False, @@ -140,9 +141,10 @@ vmessFilterRules = { 'type': { 'optional': True, 'type': str, + 'indexKey': True, 'format': baseFunc.toStrTidy, 'filter': lambda streamType: streamType == 'ws', - 'errMsg': 'Unknown stream type' + 'errMsg': 'Unexpected stream type' }, 'host': { 'optional': False, @@ -158,7 +160,8 @@ vmessFilterRules = { }, 'ed': { 'optional': False, - 'default': 2048, + 'default': None, + 'allowNone': True, 'type': int, 'format': baseFunc.toInt, 'filter': lambda ed: ed > 0, @@ -175,9 +178,10 @@ vmessFilterRules = { 'type': { 'optional': True, 'type': str, + 'indexKey': True, 'format': baseFunc.toStrTidy, 'filter': lambda streamType: streamType == 'h2', - 'errMsg': 'Unknown stream type' + 'errMsg': 'Unexpected stream type' }, 'host': { 'optional': False, @@ -202,9 +206,10 @@ vmessFilterRules = { 'type': { 'optional': True, 'type': str, + 'indexKey': True, 'format': baseFunc.toStrTidy, 'filter': lambda streamType: streamType == 'quic', - 'errMsg': 'Unknown stream type' + 'errMsg': 'Unexpected stream type' }, 'method': { 'optional': False, @@ -238,9 +243,10 @@ vmessFilterRules = { 'type': { 'optional': True, 'type': str, + 'indexKey': True, 'format': baseFunc.toStrTidy, 'filter': lambda streamType: streamType == 'grpc', - 'errMsg': 'Unknown stream type' + 'errMsg': 'Unexpected stream type' }, 'service': { 'optional': True, @@ -279,7 +285,7 @@ vmessFilterRules = { 'optional': False, 'default': 'h2,http/1.1', 'type': str, - 'format': baseFunc.toStr, + 'format': baseFunc.toStrTidy, 'filter': lambda alpn: alpn in ['h2', 'http/1.1', 'h2,http/1.1'], 'errMsg': 'Illegal alpn option' }, @@ -287,7 +293,7 @@ vmessFilterRules = { 'optional': False, 'default': True, 'type': bool, - 'format': lambda b: b + 'format': baseFunc.toBool } } } @@ -308,8 +314,14 @@ def vmessFilter(rawInfo: dict, isExtra: bool) -> tuple[bool, str or dict]: try: if not isExtra: vmessFilterRules['rootObject'].pop('remark') - return baseFunc.ruleFilter(rawInfo, vmessFilterRules, { + status, result = baseFunc.ruleFilter(rawInfo, vmessFilterRules, { 'type': 'vmess' }) + if not status: + return False, result + + # TODO: host -> sni + + return True, result except: return False, 'Unknown error' diff --git a/ProxyFilter/baseFunc.py b/ProxyFilter/baseFunc.py index 743cba2..01b0956 100644 --- a/ProxyFilter/baseFunc.py +++ b/ProxyFilter/baseFunc.py @@ -5,6 +5,7 @@ import re import IPy import copy + def isHost(host: str) -> bool: """ 判断host是否合法 @@ -31,6 +32,7 @@ def isHost(host: str) -> bool: except: # 异常错误 return False + def isPort(port: int) -> bool: """ 判断端口是否合法 @@ -48,6 +50,7 @@ def isPort(port: int) -> bool: pass return False + def toInt(raw) -> int: # change to int if isinstance(raw, (int, float)): # int / float -> int return int(raw) @@ -60,6 +63,7 @@ def toInt(raw) -> int: # change to int except: raise Exception('not a integer') + def toStr(raw) -> str: # change to str if raw is None: return '' @@ -70,42 +74,70 @@ def toStr(raw) -> str: # change to str else: raise Exception('type not allowed') + +def toBool(raw) -> bool: # change to bool + if isinstance(raw, bool): + return raw + if isinstance(raw, int): + raw = str(raw) + elif isinstance(raw, bytes): + raw = str(raw, encoding='utf-8') + elif not isinstance(raw, str): + raise Exception('type not allowed') + raw = raw.strip().lower() + if raw == 'true': + return True + elif raw == 'false': + return False + else: + try: + raw = int(raw) + return raw != 0 + except: + raise Exception('not a boolean') + + def toStrTidy(raw) -> str: # change to str with trim and lower return toStr(raw).strip().lower() + class filterException(Exception): # 检测异常 def __init__(self, reason): self.reason = reason + def __dictCheck(data: dict, objectList: dict, limitRules: dict, keyPrefix: str) -> dict: # 递归检查dict result = {} for key, option in limitRules.items(): # 遍历规则 keyName = key if keyPrefix == '' else keyPrefix + '.' + key - keyName = '`' + keyName + '`' # 检查必选key 补全可选key if key not in data: if option['optional']: # 必选 - raise filterException('Missing ' + keyName + ' option') # 必选值缺失 + raise filterException('Missing `' + keyName + '` option') # 必选值缺失 else: # 可选 data[key] = option['default'] # 补全可选值 - if 'format' in option: # 预处理数据 - try: - data[key] = option['format'](data[key]) - except Exception as reason: - raise filterException('Illegal ' + keyName + ': ' + str(reason)) # 格式化错误 - - # 检查value类型 allowNone = False if 'allowNone' in option and option['allowNone']: # 允许为None allowNone = True + + if not (data[key] is None and allowNone): # 忽略允许None且为None的情况 + if 'format' in option: # 预处理数据 + try: + data[key] = option['format'](data[key]) + except Exception as reason: + raise filterException('Illegal `' + keyName + '`: ' + str(reason)) # 格式化错误 + + # 检查value类型 if data[key] is None: # 值为None if not allowNone: # 不允许为None - raise filterException('Unexpected None in ' + keyName) + raise filterException('Unexpected None in `' + keyName + '`') result[key] = None else: dataValue = copy.deepcopy(data[key]) + if isinstance(option['type'], (str, list)) and not isinstance(dataValue, dict): # 子对象下必为dict + raise filterException('Illegal `' + keyName + '`: should be dictionary') if isinstance(option['type'], str): # 单子对象 result[key] = __dictCheck(dataValue, objectList, objectList[option['type']], keyName) # 检查子对象 elif isinstance(option['type'], list): # 多子对象 @@ -116,26 +148,36 @@ def __dictCheck(data: dict, objectList: dict, limitRules: dict, keyPrefix: str) subResult = __dictCheck(dataValue, objectList, objectList[valueType], keyName) # 尝试检查子对象 except filterException as reason: errMsg = str(reason) # 捕获抛出信息 - temp = None except: - temp = None + continue else: # 子对象匹配成功 break if subResult is None: # 无匹配子级 if errMsg is not None: # 存在子级异常信息 raise filterException(errMsg) - raise filterException('Error in ' + keyName + ' option') + raise filterException('Error in `' + keyName + '` option') result[key] = subResult elif not isinstance(data[key], option['type']): # 类型不匹配 - raise filterException('Illegal ' + keyName + ' option') + raise filterException('Illegal `' + keyName + '` option') else: # 检查无误 result[key] = dataValue if result[key] is not None: # allowNone为False - if 'filter' in option and not option['filter'](result[key]): # 格式检查 - raise filterException(option['errMsg']) + if 'filter' in option: + errFlag = False + try: + if not option['filter'](result[key]): # 格式检查 + errFlag = True + except: + raise filterException('Filter error in `' + keyName + '`') + else: + if errFlag: + if 'indexKey' in option and option['indexKey']: + raise Exception('Filter index key') + raise filterException(option['errMsg']) return result + def __ruleCheck(ruleSet: dict) -> None: # 规则集合法性检查 if 'rootObject' not in ruleSet: # 根对象检查 raise Exception('Miss root object') @@ -195,6 +237,10 @@ def __ruleCheck(ruleSet: dict) -> None: # 规则集合法性检查 if 'errMsg' in option and not isinstance(option['errMsg'], str): # errMsg必须为str raise Exception('Error message must be str in ' + keyName) + if 'indexKey' in option and not isinstance(option['indexKey'], bool): # indexKey必为bool + raise Exception('Illegal indexKey in ' + keyName) + + def ruleFilter(rawData: dict, ruleSet: dict, header: dict) -> tuple[bool, dict or str]: """ 使用规则集检查原始数据 diff --git a/demo.py b/demo.py index 23c4b1f..a16424f 100644 --- a/demo.py +++ b/demo.py @@ -2,16 +2,28 @@ import ProxyFilter as Filter info = { 'type': 'vmess', - 'remark': None, - 'server': b'127.0.0.1', + 'remark': 'ok', + 'server': b'127.0.0.1 ', 'port': b"12345", 'method': 'aes-128_gcm', 'id': 'eb6273f1-a98f-59f6-ba52-945f11dee100', + 'aid': '64', 'stream': { - 'type': 'grpc', - 'service': 'test', + 'type': 'tcp', + 'obfs': { + 'host': '343.re', + 'path': '/test' + }, 'secure': {} } + # 'stream': { + # 'type': 'grpc', + # 'service': 'test', + # 'secure': { + # 'sni': 'ip.343.re', + # 'verify': False + # } + # } } status, data = Filter.filte(info, isExtra = True) diff --git a/docs/ProxyObject.md b/docs/ProxyObject.md index 409f6e2..ba0b050 100644 --- a/docs/ProxyObject.md +++ b/docs/ProxyObject.md @@ -227,7 +227,8 @@ + 类型:*str* + 说明:VMess认证ID + 缺省:必选 -+ 可选值:合法的UUID ++ 可选值:不限 ++ 建议值:合法的UUID **aid** @@ -330,9 +331,9 @@ **ed** -+ 类型:*int* ++ 类型:*None* / *int* + 说明:`Early Data`长度阈值 -+ 缺省:2048 ++ 缺省:None + 可选值:>0 + 建议值:2048