Skip to content

Commit

Permalink
feat(analyzers): implement logical operators with analyzers
Browse files Browse the repository at this point in the history
You can now use multiple analyzers, connected with logical operators (& - and, | - or) so that you have more control over when exactly a paste should match.
  • Loading branch information
d-Rickyy-b committed Feb 2, 2020
1 parent 28f707a commit aed2dbf
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 0 deletions.
40 changes: 40 additions & 0 deletions pastepwn/analyzers/basicanalyzer.py
Expand Up @@ -34,7 +34,47 @@ def match(self, paste):
"""
raise NotImplementedError("Your analyzer must implement the match method!")

def __and__(self, other):
return MergedAnalyzer(self, and_analyzer=other)

def __or__(self, other):
return MergedAnalyzer(self, or_analyzer=other)

def __repr__(self):
if self.identifier is None:
self.identifier = self.__class__.__name__
return self.identifier


class MergedAnalyzer(BasicAnalyzer):
"""Basic analyzer class"""
name = "MergedAnalyzer"

def __init__(self, base_analyzer, and_analyzer=None, or_analyzer=None):
self._base_analyzer = base_analyzer
self._and_analyzer = and_analyzer
self._or_analyzer = or_analyzer

if self._and_analyzer:
actions = base_analyzer.actions + self._and_analyzer.actions
identifier = "({} && {})".format(base_analyzer.identifier, self._and_analyzer)
elif self._or_analyzer:
actions = base_analyzer.actions + self._or_analyzer.actions
identifier = "({} || {})".format(base_analyzer.identifier, self._or_analyzer)
else:
actions = []
identifier = "Broken analyzer"
self.logger.error("Neither and_analyzer nor or_analyzer are set!")

super().__init__(actions, identifier=identifier)

def match(self, paste):
"""
Checks if a certain paste is matched by the conditions set for this analyzer
:param paste: A :class:`pastepwn.core.paste` object which should be matched
:return: :obj:`bool` if the paste has been matched
"""
if self._and_analyzer:
return bool(self._base_analyzer.match(paste)) and bool(self._and_analyzer.match(paste))
elif self._or_analyzer:
return bool(self._base_analyzer.match(paste)) or bool(self._or_analyzer.match(paste))
15 changes: 15 additions & 0 deletions pastepwn/analyzers/tests/basicanalyzer_test.py
Expand Up @@ -45,6 +45,21 @@ def test_multi_initialization(self):
analyzer = BasicAnalyzer(actions)
self.assertEqual([self.mock_action, mock_action2], analyzer.actions)

def test_logical_operators(self):
analyzer = self.analyzer & self.analyzer
self.assertTrue(isinstance(analyzer, BasicAnalyzer))

mock_action1 = mock.Mock()
mock_action2 = mock.Mock()
analyzer1 = BasicAnalyzer(mock_action1)
analyzer2 = BasicAnalyzer(mock_action2)

and_analyzer = analyzer1 & analyzer2
self.assertEqual(2, len(and_analyzer.actions))
self.assertIn(mock_action1, and_analyzer.actions)
self.assertIn(mock_action2, and_analyzer.actions)
self.assertEqual("(BasicAnalyzer && BasicAnalyzer)", and_analyzer.identifier)


if __name__ == '__main__':
unittest.main()
94 changes: 94 additions & 0 deletions pastepwn/analyzers/tests/mergedanalyzer_test.py
@@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
import unittest
from unittest import mock

from pastepwn.analyzers.basicanalyzer import BasicAnalyzer


class TestMergedAnalyzer(unittest.TestCase):
class NewAnalyzer(BasicAnalyzer):
"""Test Analyzer for testing mergedAnalyzer"""

def __init__(self, return_value):
super().__init__(actions=None)
self.return_value = return_value

def match(self, paste):
"""Match func"""
return self.return_value

def setUp(self):
"""Setup test case"""
self.true_analyzer = self.NewAnalyzer(True)
self.false_analyzer = self.NewAnalyzer(False)
self.paste_mock = mock.Mock()
self.paste_mock.body = "This is a mock paste"

def test_and(self):
"""Check if logical and between analyzers works fine"""
and_analyzer = self.true_analyzer & self.false_analyzer

# One analyzer returns False, the other True, this should evaluate to False
self.assertFalse(and_analyzer.match(self.paste_mock))

and_analyzer2 = self.true_analyzer & self.true_analyzer
# Since both analyzers return true, this should now return True as well
self.assertTrue(and_analyzer2.match(self.paste_mock))

def test_or(self):
"""Check if logical or between analyzers works fine"""
# One analyzer returns False, the other True, this should evaluate to True
or_analyzer = self.true_analyzer | self.false_analyzer
self.assertTrue(or_analyzer.match(self.paste_mock))

# Since both return true, this should return True as well
or_analyzer2 = self.true_analyzer | self.false_analyzer
self.assertTrue(or_analyzer2.match(self.paste_mock))

or_analyzer3 = self.false_analyzer | self.true_analyzer
self.assertTrue(or_analyzer3.match(self.paste_mock))

def test_both(self):
"""Check if logical and/or both work fine in combination with each other"""
# One analyzer returns False, the other True, this should evaluate to False
and_analyzer = self.true_analyzer & self.false_analyzer
self.assertFalse(and_analyzer.match(self.paste_mock))

# Since both return true, this should return True as well
or_analyzer = self.true_analyzer | self.false_analyzer
self.assertTrue(or_analyzer.match(self.paste_mock))

def test_long_chain(self):
"""Check if logical and/or both work fine in long combinations with each other"""
# Long chain of true_analyzers must evaluate to True
and_analyzer = self.true_analyzer & self.true_analyzer & self.true_analyzer & self.true_analyzer & \
self.true_analyzer & self.true_analyzer & self.true_analyzer & self.true_analyzer
self.assertTrue(and_analyzer.match(self.paste_mock))

# A single false_analyzer must make the term evaluate to false
and_analyzer2 = self.true_analyzer & self.true_analyzer & self.true_analyzer & self.false_analyzer & \
self.true_analyzer & self.true_analyzer & self.true_analyzer & self.true_analyzer
self.assertFalse(and_analyzer2.match(self.paste_mock))

# Since one returns true, this should return True as well
or_analyzer = self.false_analyzer | self.false_analyzer | self.false_analyzer | self.false_analyzer | \
self.false_analyzer | self.true_analyzer | self.true_analyzer | self.true_analyzer
self.assertTrue(or_analyzer.match(self.paste_mock))

def test_list_and(self):
"""Check if other return values than booleans are handled correctly with logical and"""
and_analyzer = self.NewAnalyzer(["Test", 123]) & self.NewAnalyzer([])
res = and_analyzer.match(self.paste_mock)
self.assertIsInstance(res, bool)
self.assertFalse(res)

def test_list_or(self):
"""Check if other return values than booleans are handled correctly with logical or"""
and_analyzer = self.NewAnalyzer(["Test", 123]) | self.NewAnalyzer([])
res = and_analyzer.match(self.paste_mock)
self.assertIsInstance(res, bool)
self.assertTrue(res)


if __name__ == '__main__':
unittest.main()

0 comments on commit aed2dbf

Please sign in to comment.