Use PCRE instead of Python's re for RedirectMatch tests

Previously, RedirectMatch rules were evaluated using Python's re regexp
module. However, Apache httpd uses the PCRE library for evaluating
regular expressions,[1] and there are subtle differences between the
Python and PCRE implementations. Using the PCRE library itself (via the
pcre-python binding) provides more representative results, and hence
more confidence to the user that any rules that pass whereto's tests
will work in the same way in a real Apache configuration.

[1] https://httpd.apache.org/docs/trunk/glossary.html#regex

Change-Id: Ibef3376d9da0688d0c97f5837dacc5b7cc52431c
This commit is contained in:
Zane Bitter 2017-11-08 15:47:06 -05:00
parent 006ffc075c
commit 797cb67a37
6 changed files with 35 additions and 2 deletions

3
bindep.txt Normal file
View File

@ -0,0 +1,3 @@
libpcre3-dev [platform:dpkg]
pcre-devel [platform:rpm]
dev-libs/libpcre [platform:gentoo]

View File

@ -0,0 +1,4 @@
---
features:
- whereto now uses the PCRE library - the same regex library used by
mod_alias - for compiling regular expressions.

View File

@ -3,3 +3,4 @@
# process, which may cause wedges in the gate later. # process, which may cause wedges in the gate later.
pbr>=2.0 # Apache-2.0 pbr>=2.0 # Apache-2.0
python-pcre

View File

@ -25,6 +25,10 @@ commands = flake8 {posargs}
basepython = python3 basepython = python3
commands = {posargs} commands = {posargs}
[testenv:bindep]
deps = bindep
commands = bindep test
[testenv:cover] [testenv:cover]
commands = python setup.py test --coverage --testr-args='{posargs}' commands = python setup.py test --coverage --testr-args='{posargs}'

View File

@ -16,6 +16,7 @@
# under the License. # under the License.
import logging import logging
import pcre
import re import re
@ -74,14 +75,15 @@ class RedirectMatch(Rule):
def __init__(self, linenum, *params): def __init__(self, linenum, *params):
super(RedirectMatch, self).__init__(linenum, *params) super(RedirectMatch, self).__init__(linenum, *params)
self.regex = re.compile(self.pattern) self.regex = pcre.compile(self.pattern)
if self.target: if self.target:
self.target_repl = self._get_target_repl() self.target_repl = self._get_target_repl()
else: else:
self.target_repl = None self.target_repl = None
def _get_target_repl(self): def _get_target_repl(self):
return self._group_subst.sub(r'\\1', self.target).replace(r'\$', '$') escaped = pcre.escape_template(self.target)
return self._group_subst.sub(r'{\1}', escaped).replace(r'\$', '$')
def match(self, path): def match(self, path):
m = self.regex.search(path) m = self.regex.search(path)

View File

@ -96,6 +96,15 @@ class TestRedirectMatch(base.TestCase):
rule.match('/user/'), rule.match('/user/'),
) )
def test_match_pcre_syntax(self):
rule = rules.RedirectMatch(
1,
'redirectmatch', '301', '^/((?i)pike)/user/.*$', '/pike/user/',
)
self.assertIsNone(
rule.match('/Pike/USER/')
)
def test_match_with_group(self): def test_match_with_group(self):
rule = rules.RedirectMatch( rule = rules.RedirectMatch(
1, 1,
@ -106,6 +115,16 @@ class TestRedirectMatch(base.TestCase):
rule.match('/user/foo'), rule.match('/user/foo'),
) )
def test_match_with_group_braces(self):
rule = rules.RedirectMatch(
1,
'redirectmatch', '301', '^/user/(.*)$', '/pike/user/{1}/$1',
)
self.assertEqual(
('301', '/pike/user/{1}/foo'),
rule.match('/user/foo'),
)
def test_match_with_no_group_dollar(self): def test_match_with_no_group_dollar(self):
rule = rules.RedirectMatch( rule = rules.RedirectMatch(
1, 1,