[z3-checkins] r56075 - in z3/deliverance/sandbox/ianb/deliverance/trunk/deliverance: . tests
ianb at codespeak.net
ianb at codespeak.net
Wed Jun 25 20:28:38 CEST 2008
Author: ianb
Date: Wed Jun 25 20:28:35 2008
New Revision: 56075
Added:
z3/deliverance/sandbox/ianb/deliverance/trunk/deliverance/classmatch.py (contents, props changed)
z3/deliverance/sandbox/ianb/deliverance/trunk/deliverance/tests/test_classmatch.txt (contents, props changed)
Log:
add matchers for request/response matching
Added: z3/deliverance/sandbox/ianb/deliverance/trunk/deliverance/classmatch.py
==============================================================================
--- (empty file)
+++ z3/deliverance/sandbox/ianb/deliverance/trunk/deliverance/classmatch.py Wed Jun 25 20:28:35 2008
@@ -0,0 +1,194 @@
+import fnmatch
+import re
+
+__all__ = ['compile_matcher', 'compile_header_matcher', 'MatchSyntaxError']
+
+_prefix_re = re.compile(r'^([a-z_-]+):', re.I)
+
+def compile_matcher(s, default=None):
+ match = _prefix_re.search(s)
+ if not match:
+ if default is None:
+ ## FIXME: show possible names?
+ raise MatchSyntaxError(
+ "You must provide a match type (like type:) in the pattern %r"
+ % s)
+ type = default
+ pattern = s
+ else:
+ type = match.group(1).lower()
+ pattern = s[match.end():]
+ if type not in _matches:
+ ## FIXME: show possible names?
+ raise MatchSyntaxError(
+ "The type %r is not valid"
+ % (type + ':'))
+ return _matches[type](pattern)
+
+def compile_header_matcher(s, default='exact'):
+ if ':' not in s:
+ raise MatchSyntaxError(
+ "A header match must be like 'Header: pattern'; you have no header in %r"
+ % s)
+ header, pattern = s.split(':', 1)
+ header = header.strip()
+ pattern = pattern.lstrip()
+ pattern = compile_matcher(pattern, default)
+ if '*' in header:
+ return HeaderWildcardMatcher(header, pattern)
+ else:
+ return HeaderMatcher(header, pattern)
+
+class MatchSyntaxError(Exception):
+ """
+ Raised if you have an invalid expression in a matcher
+ """
+
+_matches = {}
+
+def _add_matcher(cls):
+ _matches[cls.name] = cls
+
+class Matcher(object):
+
+ name = None
+
+ def __init__(self, pattern):
+ self.pattern = pattern
+
+ def __call__(self, s):
+ raise NotImplementedError
+
+ def __unicode__(self):
+ return '%s:%s' % (self.name, self.pattern)
+
+ def __str__(self):
+ return str(unicode(self))
+
+ def __repr__(self):
+ return '<%s %s>' % (self.__class__.__name__, str(self))
+
+class WildcardMatcher(Matcher):
+
+ name = 'wildcard'
+
+ def __init__(self, pattern):
+ super(WildcardMatcher, self).__init__(pattern)
+ self.compiled = re.compile(fnmatch.translate(pattern))
+
+ def __call__(self, s):
+ return bool(self.compiled.match(s))
+
+_add_matcher(WildcardMatcher)
+
+class WildcardInsensitiveMatcher(Matcher):
+
+ name = 'wildcard-insensitive'
+
+ def __init__(self, pattern):
+ super(WildcardInsensitiveMatcher, self).__init__(pattern)
+ self.compiled = re.compile(fnmatch.translate(pattern), re.I)
+
+ def __call__(self, s):
+ return bool(self.compiled.match(s))
+
+_add_matcher(WildcardInsensitiveMatcher)
+
+class RegexMatcher(Matcher):
+
+ name = 'regex'
+
+ def __init__(self, pattern):
+ super(RegexMatcher, self).__init__(pattern)
+ try:
+ self.compiled = re.compile(pattern)
+ except re.error, e:
+ raise MatchSyntaxError(
+ "Invalid regular expression %r: %s"
+ % (pattern, e))
+
+ def __call__(self, s):
+ return bool(self.compiled.match(s))
+
+_add_matcher(RegexMatcher)
+
+class PathMatcher(Matcher):
+
+ name = 'path'
+
+ def __init__(self, pattern):
+ if not pattern.endswith('/'):
+ pattern += '/'
+ super(PathMatcher, self).__init__(pattern)
+
+ def __call__(self, s):
+ return (s == self.pattern[:-1]
+ or s.startswith(self.pattern))
+
+_add_matcher(PathMatcher)
+
+class ExactMatcher(Matcher):
+
+ name = 'exact'
+
+ def __call__(self, s):
+ return s == self.pattern
+
+_add_matcher(ExactMatcher)
+
+class ExactInsensitiveMatcher(Matcher):
+
+ name = 'exact-insensitive'
+
+ def __call__(self, s):
+ return s.lower() == self.pattern.lower()
+
+_add_matcher(ExactInsensitiveMatcher)
+
+class ContainsMatcher(Matcher):
+
+ name = 'contains'
+
+ def __call__(self, s):
+ return self.pattern in s
+
+_add_matcher(ContainsMatcher)
+
+class ContainsInsensitiveMatcher(Matcher):
+
+ name = 'contains-insensitive'
+
+ def __call__(self, s):
+ return self.pattern.lower() in s.lower()
+
+_add_matcher(ContainsInsensitiveMatcher)
+
+class HeaderMatcher(object):
+
+ def __init__(self, header, pattern):
+ self.header = header
+ self.pattern = pattern
+
+ def __call__(self, headers):
+ return self.pattern(headers.get(self.header, ''))
+
+ def __unicode__(self):
+ return u'%s: %s' % (self.header, self.pattern)
+
+class HeaderWildcardMatcher(object):
+
+ def __init__(self, header, pattern):
+ self.header = header
+ self.header_re = re.compile(fnmatch.translate(header), re.I)
+ self.pattern = pattern
+
+ def __call__(self, headers):
+ matches = self.header_re.match
+ for key in headers:
+ if matches(key):
+ if self.pattern(headers[key]):
+ return True
+ return False
+
+ def __unicode__(self):
+ return u'%s: %s' % (self.header, self.pattern)
Added: z3/deliverance/sandbox/ianb/deliverance/trunk/deliverance/tests/test_classmatch.txt
==============================================================================
--- (empty file)
+++ z3/deliverance/sandbox/ianb/deliverance/trunk/deliverance/tests/test_classmatch.txt Wed Jun 25 20:28:35 2008
@@ -0,0 +1,50 @@
+This tests different kinds of matchers.
+
+ >>> from deliverance.classmatch import compile_matcher, compile_header_matcher
+ >>> def match(pattern, *values):
+ ... for value in values:
+ ... result = compile_matcher(pattern)(value)
+ ... print '%s: %s' % (value, result)
+ >>> match('exact:foo', 'foo', ' foo')
+ foo: True
+ foo: False
+ >>> match('exact-insensitive:foo', 'foo', 'fOo', ' FOO')
+ foo: True
+ fOo: True
+ FOO: False
+ >>> match('wildcard:foo*', 'foobar', 'barfoo', 'FOOBAR')
+ foobar: True
+ barfoo: False
+ FOOBAR: False
+ >>> match('wildcard-insensitive:*foo*', 'foobar', 'BARFOOBAR', 'fobar')
+ foobar: True
+ BARFOOBAR: True
+ fobar: False
+ >>> match('regex:^\\w+$', 'asdf', '1234', ' ', '')
+ asdf: True
+ 1234: True
+ : False
+ : False
+ >>> match('path:/foo', '/foo', '/foo/', '/foo/something', '/foobar')
+ /foo: True
+ /foo/: True
+ /foo/something: True
+ /foobar: False
+ >>> match('contains:foo', 'somefoo thing')
+ somefoo thing: True
+
+And then some header matching:
+
+ >>> def mheader(pattern, headers):
+ ... return compile_header_matcher(pattern)(headers)
+ >>> mheader('Something: foo', {'Something': 'foo'})
+ True
+ >>> mheader('Something: foo', {'Something': 'foobar'})
+ False
+ >>> mheader('Something: contains:foo', {'Something': 'foobar'})
+ True
+ >>> mheader('X-*: contains:evil', {'X-Other': 'nothing', 'X-Foo-Bar': 'some evil!'})
+ True
+ >>> mheader('X-*: contains:evil', {'X-Foo-Bar': 'okay'})
+ False
+
More information about the z3-checkins
mailing list