class CommandNotify(Command): HANDLER_ID = 'NOTIFY' EXTENSION_NAME = 'enotify' TAGGED_ARGS = { 'from': Tag('FROM', (StringList(1), )), 'importance': Tag('IMPORTANCE', (StringList(1), )), 'options': Tag('OPTIONS', (StringList(), )), 'message': Tag('MESSAGE', (StringList(1), )), } POSITIONAL_ARGS = [StringList(length=1)] def evaluate(self, message: Message, state: EvaluationState) -> None: notify_from = notify_importance = self.notify_message = None notify_options = [] # type: ignore if 'from' in self.tagged_args: notify_from = self.tagged_args['from'][1][0] # type: ignore if 'importance' in self.tagged_args: notify_importance = self.tagged_args['importance'][1][ 0] # type: ignore if 'options' in self.tagged_args: notify_options = self.tagged_args['options'][1] # type: ignore notify_message = None if 'message' in self.tagged_args: notify_message = self.tagged_args['message'][1][0] # type: ignore notify_method = self.positional_args[0][0] # type: ignore state.check_required_extension('enotify', 'NOTIFY') notify_from = expand_variables(notify_from, state) # type: ignore notify_importance = expand_variables(notify_importance, state) # type: ignore notify_options = list( map(lambda s: expand_variables(s, state), notify_options)) notify_message = expand_variables(notify_message, state) # type: ignore notify_method = expand_variables(notify_method, state) m = re.match('^([A-Za-z][A-Za-z0-9.+-]*):', notify_method) if not m: raise RuleSyntaxError( "Notification method must be an URI, e.g. 'mailto:[email protected]'" ) if notify_importance and notify_importance not in ["1", "2", "3"]: raise RuleSyntaxError( "Illegal notify importance '%s' encountered" % notify_importance) notify_method_cls = ExtensionRegistry.get_notification_method( m.group(1).lower()) if not notify_method_cls: raise RuleSyntaxError("Unsupported notification method '%s'" % m.group(1)) (res, msg) = notify_method_cls.test_valid(notify_method) if not res: raise RuleSyntaxError(msg) state.actions.append( 'notify', (notify_method, notify_from, notify_importance, notify_options, notify_message) # type: ignore )
class TestAddress(Test): HANDLER_ID: Text = 'ADDRESS' TAGGED_ARGS = { 'comparator': Comparator(), 'match_type': MatchType(), 'address_part': Tag(('LOCALPART', 'DOMAIN', 'ALL')), } POSITIONAL_ARGS = [ StringList(), StringList(), ] def __init__(self, arguments: Optional[List[Union['TagGrammar', SupportsInt, List[Union[ Text, 'String']]]]] = None, tests: Optional[List['Test']] = None) -> None: super().__init__(arguments, tests) self.headers, self.keylist = self.positional_args self.match_type = self.comparator = self.address_part = None if 'comparator' in self.tagged_args: self.comparator = self.tagged_args['comparator'][1][ 0] # type: ignore if 'match_type' in self.tagged_args: self.match_type = self.tagged_args['match_type'][0] if 'address_part' in self.tagged_args: self.address_part = self.tagged_args['address_part'][0] def evaluate(self, message: Message, state: EvaluationState) -> bool: if not isinstance(self.keylist, list): raise ValueError('TestAddress keylist not iterable') if not isinstance(self.headers, list): raise ValueError('TestAddress headers not iterable') header_values: List[Text] = [] for header in self.headers: header = expand_variables(header, state) # TODO: section 5.1: we should restrict the allowed headers to # those headers that contain an "address-list". this includes at # least: from, to, cc, bcc, sender, resent-from, resent-to. header_values.extend(message.get_all(header, [])) addresses: List[Text] = [] for msg_address in email.utils.getaddresses(header_values): if msg_address[1] != '': addresses.append( sifter.grammar.string.address_part( msg_address[1], cast(Text, self.address_part))) for address in addresses: for key in self.keylist: key = expand_variables(key, state) if sifter.grammar.string.compare(address, key, state, self.comparator, cast(Text, self.match_type)): return True return False
class CommandSet(Command): HANDLER_ID = 'SET' EXTENSION_NAME = 'variables' TAGGED_ARGS = { 'lower': Tag('LOWER'), 'upper': Tag('UPPER'), 'lowerfirst': Tag('LOWERFIRST'), 'upperfirst': Tag('UPPERFIRST'), 'quotewildcard': Tag('QUOTEWILDCARD'), 'quoteregex': Tag('QUOTEREGEX'), 'encodeurl': Tag('ENCODEURL'), 'length': Tag('LENGTH'), } POSITIONAL_ARGS = [ StringList(length=1), StringList(length=1), ] def evaluate(self, message: Message, state: EvaluationState) -> None: state.check_required_extension('variables', 'VARIABLES') variable_modifier = self.tagged_args variable_name = self.positional_args[0][0] # type: ignore if (not re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', variable_name)): raise RuleSyntaxError("Illegal variable name '%s' encountered" % variable_name) variable_value: Text = self.positional_args[1][0] # type: ignore variable_value = expand_variables(variable_value, state) if 'lower' in variable_modifier: variable_value = variable_value.lower() if 'upper' in variable_modifier: variable_value = variable_value.upper() if 'lowerfirst' in variable_modifier: variable_value = variable_value[:1].lower() + variable_value[1:] if 'upperfirst' in variable_modifier: variable_value = variable_value[:1].upper() + variable_value[1:] if 'quotewildcard' in variable_modifier: variable_value = variable_value.replace('*', '\\*') variable_value = variable_value.replace('?', '\\?') variable_value = variable_value.replace('\\', '\\\\') if 'quoteregex' in variable_modifier: variable_value = re.escape(variable_value) if 'encodeurl' in variable_modifier: variable_value = quote(variable_value, safe='-._~') if 'length' in variable_modifier: variable_value = "" + str(len(variable_value)) state.named_variables[variable_name] = variable_value
class TestSize(Test): HANDLER_ID = 'SIZE' TAGGED_ARGS = { 'size': Tag(('OVER', 'UNDER'), (Number(), )), } COMPARISON_FNS: Dict[Text, Callable[[Any, Any], bool]] = { 'OVER': operator.gt, 'UNDER': operator.lt, } def evaluate(self, message: Message, state: EvaluationState) -> bool: comparison_fn = self.COMPARISON_FNS[self.tagged_args['size'] [0]] # type: ignore comparison_size = self.tagged_args['size'][1] # FIXME: size is defined as number of octets, whereas this gives us # number of characters message_size = len(message.as_string()) return comparison_fn(message_size, comparison_size)
def test_not_allowed_single_tag() -> None: # test the case for a non-list single tag name. test when the tag is a # substring of the allowed tag. mock_validator = TagValidator('ISFOO') assert mock_validator.validate([GrammarTag('IS')], 0) == 0
def test_not_allowed_tag() -> None: mock_validator = TagValidator([ 'MOCK', 'FOO', ]) assert mock_validator.validate([GrammarTag('IS')], 0) == 0
def test_allowed_single_tag() -> None: # test the case for a non-list single tag name mock_validator = TagValidator('IS') assert mock_validator.validate([GrammarTag('IS')], 0) == 1