diff --git a/Basis/Filter.py b/Basis/Filter.py index 36860f5..27e7731 100644 --- a/Basis/Filter.py +++ b/Basis/Filter.py @@ -80,14 +80,14 @@ def Filter(raw: dict, rules: dict) -> dict: else: # key exist data[key] = raw[key] # format process (data --[format]--> data) - try: - data[key] = rule['format'](data[key]) # run format - except: - raise RuntimeError(rule['errMsg']) # format error if data[key] is None: # key content is None if not rule['allowNone']: # key is not allow None raise RuntimeError('Field `%s` shouldn\'t be None' % key) continue # skip following process + try: + data[key] = rule['format'](data[key]) # run format + except: + raise RuntimeError(rule['errMsg']) # format error # filter process (data --[type check (& filter check)]--> pass / non-pass) if type(rule['type']) == type: # str / int / bool / ... rule['type'] = [rule['type']] # str -> [str] / int -> [int] / ... @@ -104,7 +104,7 @@ def Filter(raw: dict, rules: dict) -> dict: else: # multi subObject 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']] + subType = data[key][rule['indexKey']].lower() if subType not in rule['type']: # confirm subObject rule exist raise RuntimeError('Unknown index `%s` in key `%s`' % (subType, key)) subRules = rule['type'][subType] diff --git a/Basis/Functions.py b/Basis/Functions.py index 923c439..daefe4f 100644 --- a/Basis/Functions.py +++ b/Basis/Functions.py @@ -38,7 +38,7 @@ def isHost(host: str) -> bool: def isPort(port: int) -> bool: if type(port) != int: return False - return 1 <= port <= 65535 # 1 ~ 65535 + return port in range(1, 65536) # 1 ~ 65535 def md5Sum(data: str, encode: str = 'utf-8') -> str: @@ -80,9 +80,9 @@ def toInt(raw) -> int: raise RuntimeError('Unable convert to int') -def toStr(raw, acceptNone: bool = True) -> str: - if raw is None and acceptNone: # None -> '' - return '' +def toStr(raw) -> str: + if raw is None: + raise RuntimeError('None could not convert to str') if isinstance(raw, bytes): # bytes -> str return str(raw, encoding = 'utf-8') try: @@ -91,6 +91,10 @@ def toStr(raw, acceptNone: bool = True) -> str: raise RuntimeError('Unable convert to str') +def toStrTidy(raw) -> str: + return toStr(raw).strip().lower() # with trim and lower + + def toBool(raw) -> bool: if isinstance(raw, (bool, int, float)): return bool(raw) diff --git a/Filter/Plugin.py b/Filter/Plugin.py index 492b500..559676e 100644 --- a/Filter/Plugin.py +++ b/Filter/Plugin.py @@ -2,9 +2,9 @@ # -*- coding: utf-8 -*- import copy -from Basis.Functions import toStr from Basis.Filter import rulesFilter from Basis.Constant import pluginClients +from Basis.Functions import toStr, toStrTidy pluginAlias = { 'obfs-local': {'obfs', 'simple-obfs'}, @@ -24,7 +24,7 @@ pluginAlias = { pluginObject = rulesFilter({ 'type': { 'type': str, - 'format': lambda s: pluginFormat(toStr(s)), + 'format': lambda s: pluginFormat(toStrTidy(s)), 'filter': lambda s: s in pluginClients, 'errMsg': 'Unknown SIP003 plugin' }, @@ -48,7 +48,7 @@ def loadAlias() -> None: def pluginFormat(pluginName: str) -> str: - pluginName = pluginName.strip().lower().replace('_', '-') + pluginName = pluginName.replace('_', '-') for plugin, alias in pluginAlias.items(): if pluginName in alias: return plugin diff --git a/Filter/Shadowsocks.py b/Filter/Shadowsocks.py index 7b23a54..9b44301 100644 --- a/Filter/Shadowsocks.py +++ b/Filter/Shadowsocks.py @@ -4,13 +4,13 @@ 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.Functions import toInt, toStr, toStrTidy ssObject = rulesFilter({ 'server': { 'type': str, - 'format': lambda s: toStr(s).strip().lower(), + 'format': toStrTidy, 'filter': isHost, 'errMsg': 'Invalid server address' }, @@ -22,7 +22,7 @@ ssObject = rulesFilter({ }, 'method': { 'type': str, - 'format': lambda s: toStr(s).strip().lower().replace('_', '-'), + 'format': lambda s: toStrTidy(s).replace('_', '-'), 'filter': lambda s: s in ssAllMethods, 'errMsg': 'Unknown Shadowsocks method' }, diff --git a/Filter/ShadowsocksR.py b/Filter/ShadowsocksR.py index 807ed26..749a9b3 100644 --- a/Filter/ShadowsocksR.py +++ b/Filter/ShadowsocksR.py @@ -2,8 +2,8 @@ # -*- coding: utf-8 -*- from Basis.Filter import rulesFilter -from Basis.Functions import toInt, toStr from Basis.Functions import isHost, isPort +from Basis.Functions import toInt, toStr, toStrTidy from Basis.Constant import ssrMethods, ssrProtocols, ssrObfuscations @@ -20,7 +20,7 @@ def ssrObfsFormat(obfs: str) -> str: ssrObject = rulesFilter({ 'server': { 'type': str, - 'format': lambda s: toStr(s).strip().lower(), + 'format': toStrTidy, 'filter': isHost, 'errMsg': 'Invalid server address' }, @@ -32,7 +32,7 @@ ssrObject = rulesFilter({ }, 'method': { 'type': str, - 'format': lambda s: toStr(s).strip().lower().replace('_', '-'), + 'format': lambda s: toStrTidy(s).replace('_', '-'), 'filter': lambda s: s in ssrMethods, 'errMsg': 'Unknown ShadowsocksR method' }, diff --git a/Filter/V2ray.py b/Filter/V2ray.py new file mode 100644 index 0000000..6ba6d27 --- /dev/null +++ b/Filter/V2ray.py @@ -0,0 +1,239 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from Basis.Filter import rulesFilter +from Basis.Constant import quicMethods, udpObfuscations +from Basis.Functions import toInt, toStr, toStrTidy, toBool + +tlsObject = rulesFilter({ + 'sni': { + 'optional': True, + 'default': '', + 'type': str, + 'format': toStrTidy, + 'errMsg': 'Invalid SNI content' + }, + 'alpn': { + 'optional': True, + 'default': None, + 'allowNone': True, + 'type': str, + 'format': lambda s: toStrTidy(s).replace(' ', ''), # remove space + 'filter': lambda s: s in ['h2', 'http/1.1', 'h2,http/1.1'], + 'errMsg': 'Invalid alpn option' + }, + 'verify': { + 'optional': True, + 'default': True, + 'type': bool, + 'format': toBool, + 'errMsg': 'Invalid verify option' + } +}) + +obfsObject = rulesFilter({ + 'host': { + 'optional': True, + 'default': '', + 'type': str, + 'format': toStrTidy, + 'errMsg': 'Invalid obfs host' + }, + 'path': { + 'optional': True, + 'default': '/', + 'type': str, + 'format': lambda s: toStr(s).strip(), + 'errMsg': 'Invalid obfs path' + } +}) + +tcpObject = rulesFilter({ + 'type': { + 'type': str, + 'format': toStrTidy, + 'filter': lambda s: s == 'tcp', + 'errMsg': 'Invalid TCP stream type' + }, + 'obfs': { + 'optional': True, + 'default': None, + 'allowNone': True, + 'type': obfsObject, + 'errMsg': 'Invalid obfsObject' + }, + 'secure': { + 'optional': True, + 'default': None, + 'allowNone': True, + 'type': tlsObject, + 'errMsg': 'Invalid tlsObject' + } +}) + +kcpObject = rulesFilter({ + 'type': { + 'type': str, + 'format': toStrTidy, + 'filter': lambda s: s == 'kcp', + 'errMsg': 'Invalid mKCP stream type' + }, + 'seed': { + 'optional': True, + 'default': None, + 'allowNone': True, + 'type': str, + 'format': toStr, + 'errMsg': 'Invalid mKCP seed' + }, + 'obfs': { + 'optional': True, + 'default': 'none', + 'type': str, + 'format': lambda s: toStrTidy(s).replace('_', '-'), + 'filter': lambda s: s in udpObfuscations, + 'errMsg': 'Unknown mKCP obfs method' + }, + 'secure': { + 'optional': True, + 'default': None, + 'allowNone': True, + 'type': tlsObject, + 'errMsg': 'Invalid tlsObject' + } +}) + +wsObject = rulesFilter({ + 'type': { + 'type': str, + 'format': toStrTidy, + 'filter': lambda s: s == 'ws', + 'errMsg': 'Invalid WebSocket stream type' + }, + 'host': { + 'optional': True, + 'default': '', + 'type': str, + 'format': toStrTidy, + 'errMsg': 'Invalid WebSocket host' + }, + 'path': { + 'optional': True, + 'default': '/', + 'type': str, + 'format': lambda s: toStr(s).strip(), + 'errMsg': 'Invalid WebSocket path' + }, + 'ed': { + 'optional': True, + 'default': None, + 'allowNone': True, + 'type': int, + 'format': toInt, + 'filter': lambda i: i > 0, + 'errMsg': 'Illegal Max-Early-Data length' + }, + 'secure': { + 'optional': True, + 'default': None, + 'allowNone': True, + 'type': tlsObject, + 'errMsg': 'Invalid tlsObject' + } +}) + +h2Object = rulesFilter({ + 'type': { + 'type': str, + 'format': toStrTidy, + 'filter': lambda s: s == 'h2', + 'errMsg': 'Invalid HTTP/2 stream type' + }, + 'host': { + 'optional': True, + 'default': '', + 'type': str, + 'format': toStrTidy, + 'errMsg': 'Invalid HTTP/2 host' + }, + 'path': { + 'optional': True, + 'default': '/', + 'type': str, + 'format': lambda s: toStr(s).strip(), + 'errMsg': 'Invalid HTTP/2 path' + }, + 'secure': { + 'optional': True, + 'default': {}, + 'type': tlsObject, + 'errMsg': 'Invalid tlsObject' + } +}) + +quicObject = rulesFilter({ + 'type': { + 'type': str, + 'format': toStrTidy, + 'filter': lambda s: s == 'quic', + 'errMsg': 'Invalid QUIC stream type' + }, + 'method': { + 'optional': True, + 'default': 'none', + 'type': str, + 'format': lambda s: toStrTidy(s).replace('_', '-'), + 'filter': lambda s: s in quicMethods, + 'errMsg': 'Unknown QUIC method' + }, + 'passwd': { + 'optional': True, + 'default': '', + 'type': str, + 'format': toStr, + 'errMsg': 'Invalid QUIC password' + }, + 'obfs': { + 'optional': True, + 'default': 'none', + 'type': str, + 'format': lambda s: toStrTidy(s).replace('_', '-'), + 'filter': lambda s: s in udpObfuscations, + 'errMsg': 'Unknown QUIC obfs method' + }, + 'secure': { + 'optional': True, + 'default': {}, + 'type': tlsObject, + 'errMsg': 'Invalid tlsObject' + } +}) + +grpcObject = rulesFilter({ + 'type': { + 'type': str, + 'format': toStrTidy, + 'filter': lambda s: s == 'grpc', + 'errMsg': 'Invalid gRPC stream type' + }, + 'service': { + 'type': str, + 'format': lambda s: toStr(s).strip(), + 'errMsg': 'Invalid service content' + }, + 'mode': { + 'optional': True, + 'default': 'gun', + 'type': str, + 'format': toStrTidy, + 'filter': lambda s: s in ['gun', 'multi'], + 'errMsg': 'Unknown gRPC mode' + }, + 'secure': { + 'optional': True, + 'default': None, + 'allowNone': True, + 'type': tlsObject, + 'errMsg': 'Invalid tlsObject' + } +}) diff --git a/Filter/VMess.py b/Filter/VMess.py new file mode 100644 index 0000000..b91120f --- /dev/null +++ b/Filter/VMess.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from Filter import V2ray +from Basis.Filter import rulesFilter +from Basis.Constant import vmessMethods +from Basis.Functions import isHost, isPort +from Basis.Functions import toInt, toStrTidy + +vmessObject = rulesFilter({ + 'server': { + 'type': str, + 'format': toStrTidy, + 'filter': isHost, + 'errMsg': 'Invalid server address' + }, + 'port': { + 'type': int, + 'format': toInt, + 'filter': isPort, + 'errMsg': 'Invalid port number' + }, + 'method': { + 'optional': True, + 'default': 'auto', + 'type': str, + 'format': lambda s: toStrTidy(s).replace('_', '-'), + 'filter': lambda s: s in vmessMethods, + 'errMsg': 'Unknown VMess method' + }, + 'id': { + 'type': str, + 'format': toStrTidy, + 'errMsg': 'Unknown VMess ID' + }, + 'aid': { + 'optional': True, + 'default': 0, + 'type': int, + 'format': toInt, + 'filter': lambda i: i in range(0, 65536), # 0 ~ 65535 + 'errMsg': 'Invalid VMess alter ID' + }, + 'stream': { + 'optional': True, + 'default': { + 'type': 'tcp' + }, + 'multiSub': True, + 'type': { + 'tcp': V2ray.tcpObject, + 'kcp': V2ray.kcpObject, + 'ws': V2ray.wsObject, + 'h2': V2ray.h2Object, + 'quic': V2ray.quicObject, + 'grpc': V2ray.grpcObject, + }, + 'errMsg': 'Invalid VMess stream' + } +}) + +# TODO: add SNI / ws host / h2 host diff --git a/demo.py b/demo.py index 360f70b..6e8cddd 100755 --- a/demo.py +++ b/demo.py @@ -5,9 +5,11 @@ from Basis.Filter import Filter from Basis.Filter import filterObject from Filter.Shadowsocks import ssObject from Filter.ShadowsocksR import ssrObject +from Filter.VMess import vmessObject # pprint(ssObject, sort_dicts = False) # pprint(ssrObject, sort_dicts = False) +# pprint(vmessObject, sort_dicts = False) # pprint(filterObject, sort_dicts = False) ssProxy = { @@ -29,6 +31,24 @@ ssrProxy = { 'obfs': 'http_post' } +vmessProxy = { + 'server': '1.1.1.1', + 'port': b'12345', + 'id': 'c8783403-64d5-4b6d-8cf4-bd3988d01b6c', + 'aid': '64', + 'stream': { + 'type': 'GRPC', + 'service': 'no-gfw', + 'mode': ' multi ', + 'secure': { + 'sni': ' DNOMD343.top', + 'alpn': 'h2, http/1.1', + 'verify': 'False ' + } + } +} + # ret = Filter(ssProxy, ssObject) -ret = Filter(ssrProxy, ssrObject) +# ret = Filter(ssrProxy, ssrObject) +ret = Filter(vmessProxy, vmessObject) pprint(ret, sort_dicts = False) diff --git a/docs/ProxyObject/VMess.md b/docs/ProxyObject/VMess.md index 5f32bf2..fc8b526 100644 --- a/docs/ProxyObject/VMess.md +++ b/docs/ProxyObject/VMess.md @@ -72,7 +72,7 @@ ### secure -+ 类型:*None* / [*secureObject*](#secureobject) ++ 类型:*None* / [*tlsObject*](#tlsobject) + 说明:TLS加密选项 + 缺省:`None` + 限制:无 @@ -104,7 +104,7 @@ ### secure -+ 类型:*None* / [*secureObject*](#secureobject) ++ 类型:*None* / [*tlsObject*](#tlsobject) + 说明:TLS加密选项 + 缺省:`None` + 限制:无 @@ -144,7 +144,7 @@ ### secure -+ 类型:*None* / [*secureObject*](#secureobject) ++ 类型:*None* / [*tlsObject*](#tlsobject) + 说明:TLS加密选项 + 缺省:`None` + 限制:无 @@ -176,9 +176,9 @@ ### secure -+ 类型:[*secureObject*](#secureobject) ++ 类型:[*tlsObject*](#tlsobject) + 说明:TLS加密选项 -+ 缺省:`None` ++ 缺省:`tlsObject` + 限制:无 ## quicObject @@ -216,9 +216,9 @@ ### secure -+ 类型:[*secureObject*](#secureobject) ++ 类型:[*tlsObject*](#tlsobject) + 说明:TLS加密选项 -+ 缺省:`secureObject` ++ 缺省:`tlsObject` + 限制:无 ## grpcObject @@ -248,7 +248,7 @@ ### secure -+ 类型:*None* / [*secureObject*](#secureobject) ++ 类型:*None* / [*tlsObject*](#tlsobject) + 说明:TLS加密选项 + 缺省:`None` + 限制:无 @@ -276,7 +276,7 @@ + 缺省:`/` + 限制:无 -## secureObject +## tlsObject ``` {