From 15a7ac72d591f4595f7957f56afef51a233c6441 Mon Sep 17 00:00:00 2001 From: dnomd343 Date: Sat, 6 Aug 2022 12:04:51 +0800 Subject: [PATCH] feat: add Filter support --- Basis/Filter.py | 109 ++++++++++++++++++++++++++++-------------- Filter/Shadowsocks.py | 12 +++-- demo.py | 7 +++ 3 files changed, 87 insertions(+), 41 deletions(-) create mode 100755 demo.py diff --git a/Basis/Filter.py b/Basis/Filter.py index 7384495..76bdf7d 100644 --- a/Basis/Filter.py +++ b/Basis/Filter.py @@ -1,85 +1,120 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import copy + filterObject = { 'optional': { + 'type': bool, 'optional': True, # `optional` is not force require 'default': False, # disable `optional` option in default 'allowNone': False, # `optional` couldn't be None - 'type': bool, - 'format': lambda x: x, # return same value - 'filter': lambda b: True, # always return True - 'errMsg': 'Invalid `optional` key' }, 'default': { + 'type': any, # skip type check 'optional': True, # `default` is not force require 'default': None, 'allowNone': True, # `default` can be None - 'type': any, # skip type check - 'format': lambda x: x, # return same value - 'filter': lambda b: True, # always return True - 'errMsg': 'Invalid `default` key' }, 'allowNone': { + 'type': bool, 'optional': True, # `allowNone` is not force require 'default': False, # disable `allowNone` option in default 'allowNone': False, # `allowNone` couldn't be None - 'type': bool, - 'format': lambda x: x, # return same value - 'filter': lambda b: True, # always return True - 'errMsg': 'Invalid `allowNone` key' }, 'type': { + 'type': [any, type, list, dict], 'optional': False, # `type` is force require 'allowNone': False, # `type` couldn't be None - 'type': [any, type, list, dict], - 'format': lambda x: x, # return same value - 'filter': lambda b: True, # always return True - 'errMsg': 'Invalid `type` key' }, 'multiSub': { + 'type': bool, 'optional': True, # `multiSub` is not force require 'default': False, # disable `multiSub` option in default 'allowNone': False, # `multiSub` couldn't be None - 'type': bool, - 'format': lambda x: x, # return same value - 'filter': lambda b: True, # always return True - 'errMsg': 'Invalid `multiSub` key' }, 'indexKey': { + 'type': str, 'optional': True, # `indexKey` is not force require 'default': 'type', 'allowNone': False, # `indexKey` couldn't be None - 'type': str, - 'format': lambda x: x, # return same value - 'filter': lambda b: True, # always return True - 'errMsg': 'Invalid `indexKey` key' }, 'format': { + 'type': any, 'optional': True, # `format` is not force require 'default': lambda x: x, # don't change anything 'allowNone': False, # `format` couldn't be None - 'type': any, - 'format': lambda x: x, # return same value - 'filter': lambda b: True, # always return True - 'errMsg': 'Invalid `format` key' }, 'filter': { + 'type': any, 'optional': True, # `filter` is not force require 'default': lambda x: True, # always pass filter 'allowNone': False, # `filter` couldn't be None - 'type': any, - 'format': lambda x: x, # return same value - 'filter': lambda b: True, # always return True - 'errMsg': 'Invalid `filter` key' }, 'errMsg': { + 'type': str, 'optional': True, # `errMsg` is not force require 'default': 'filter error', 'allowNone': False, # `errMsg` couldn't be None - 'type': str, - 'format': lambda x: x, # return same value - 'filter': lambda b: True, # always return True - 'errMsg': 'Invalid `errMsg` key' - }, + } } + +for field in filterObject: + filterObject[field]['errMsg'] = 'Invalid `%s` key' % field + filterObject[field]['format'] = filterObject['format']['default'] # return same value + filterObject[field]['filter'] = filterObject['filter']['default'] # always return True + + +def Filter(raw: dict, rules: dict) -> dict: + if type(raw) != dict: + raise RuntimeError('Invalid input for filter') + data = {} + raw = copy.deepcopy(raw) + rules = copy.deepcopy(rules) + for key, rule in rules.items(): + # 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) + data[key] = rule['default'] # set default value + 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 + # 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] / ... + if type(rule['type']) == list: # [str, int, bool, ...] + if data[key] == any and any in rule['type']: # special case -> skip type filter + pass + 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 not rule['multiSub']: # single subObject + data[key] = Filter(data[key], rule['type']) + else: # multi subObject + # TODO: multi sub filter + pass + continue + elif rule['type'] != any: # type == any -> skip type filter + raise RuntimeError('Unknown `type` in rules') + if not rule['filter'](data[key]): # run filter + raise RuntimeError(rule['errMsg']) + return data + + +def rulesFilter(rules: dict) -> dict: + result = {} + for key, rule in rules.items(): # filter by basic rules + result[key] = Filter(rule, filterObject) + return result + + +filterObject = rulesFilter(filterObject) # format itself diff --git a/Filter/Shadowsocks.py b/Filter/Shadowsocks.py index 9702174..0f038ec 100644 --- a/Filter/Shadowsocks.py +++ b/Filter/Shadowsocks.py @@ -4,9 +4,10 @@ from Filter.Plugin import pluginFormat 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 = { +pluginObject = rulesFilter({ 'type': { 'type': str, 'format': lambda s: pluginFormat(toStr(s).strip().lower()), @@ -20,9 +21,9 @@ pluginObject = { 'format': toStr, 'errMsg': 'Invalid SIP003 param' } -} +}) -ssObject = { +ssObject = rulesFilter({ 'server': { 'type': str, 'format': toStr, @@ -53,4 +54,7 @@ ssObject = { 'type': pluginObject, 'errMsg': 'Invalid pluginObject' } -} +}) + +from pprint import pprint +pprint(ssObject, sort_dicts = False) diff --git a/demo.py b/demo.py new file mode 100755 index 0000000..7be03fd --- /dev/null +++ b/demo.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python + +import Filter.Shadowsocks + +# from pprint import pprint +# from Basis.Filter import filterObject +# pprint(filterObject, sort_dicts = False)