f6592d2e42
Update all .py source files by $ pyupgrade --py3-only $(git ls-files | grep ".py$") to modernize the code according to Python 3 syntaxes. Also add the pyupgrade hook to pre-commit to avoid merging additional Python 2 syntaxes. Change-Id: I03025adae5df96058ad3364c9ac17e899a600f7b
138 lines
4.0 KiB
Python
138 lines
4.0 KiB
Python
# Copyright 2010-2011 OpenStack Foundation
|
|
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import logging
|
|
import pcre
|
|
import re
|
|
|
|
|
|
LOG = logging.getLogger()
|
|
|
|
|
|
class Rule:
|
|
"Base class for rules."
|
|
|
|
def __init__(self, linenum, *params):
|
|
self.linenum = linenum
|
|
self._params = params
|
|
if len(params) == 4:
|
|
# redirect code pattern target
|
|
self.code = params[1]
|
|
self.pattern = params[2]
|
|
self.target = params[3]
|
|
elif len(params) == 3:
|
|
if params[1] == '410':
|
|
# The page has been deleted and is not coming back.
|
|
self.code = params[1]
|
|
self.pattern = params[2]
|
|
self.target = None
|
|
else:
|
|
# redirect pattern target
|
|
# (code is implied)
|
|
self.code = '301'
|
|
self.pattern = params[1]
|
|
self.target = params[2]
|
|
else:
|
|
raise ValueError('Could not understand rule {}'.format(params))
|
|
|
|
def __str__(self):
|
|
return '[{}] {}'.format(
|
|
self.linenum,
|
|
' '.join(p for p in self._params if p),
|
|
)
|
|
|
|
def match(self, path):
|
|
raise NotImplementedError('Base class does not implement match()')
|
|
|
|
|
|
class Redirect(Rule):
|
|
"A Redirect rule."
|
|
|
|
def match(self, path):
|
|
if path == self.pattern:
|
|
return (self.code, self.target)
|
|
return None
|
|
|
|
|
|
class RedirectMatch(Rule):
|
|
"A RedirectMatch rule with a regular expression."
|
|
|
|
_group_subst = re.compile(r'(?<!\\)\$([0-9])')
|
|
|
|
def __init__(self, linenum, *params):
|
|
super().__init__(linenum, *params)
|
|
self.regex = pcre.compile(self.pattern)
|
|
if self.target:
|
|
self.target_repl = self._get_target_repl()
|
|
else:
|
|
self.target_repl = None
|
|
|
|
def _get_target_repl(self):
|
|
escaped = pcre.escape_template(self.target)
|
|
return self._group_subst.sub(r'{\1}', escaped).replace(r'\$', '$')
|
|
|
|
def match(self, path):
|
|
m = self.regex.search(path)
|
|
if m:
|
|
if self.target_repl:
|
|
return (self.code, self.regex.sub(self.target_repl, path))
|
|
else:
|
|
# A rule that doesn't have a response target, like 410.
|
|
return (self.code, self.target_repl)
|
|
return None
|
|
|
|
|
|
class RuleSet:
|
|
"An ordered collection of rules."
|
|
|
|
_factories = {
|
|
'redirect': Redirect,
|
|
'redirectmatch': RedirectMatch,
|
|
}
|
|
|
|
def __init__(self):
|
|
self._rules = []
|
|
self._by_num = {}
|
|
|
|
def add(self, linenum, *params):
|
|
rule_type = params[0].lower()
|
|
rule = self._factories[rule_type](linenum, *params)
|
|
self._rules.append(rule)
|
|
self._by_num[linenum] = rule
|
|
|
|
def __getitem__(self, index):
|
|
return self._by_num[index]
|
|
|
|
def __iter__(self):
|
|
return iter(self._rules)
|
|
|
|
@property
|
|
def all_ids(self):
|
|
return list(self._by_num.keys())
|
|
|
|
def match(self, path):
|
|
for rule in self:
|
|
try:
|
|
m = rule.match(path)
|
|
except Exception as e:
|
|
LOG.warning('Failed to evaluate {} against {}: {}'.format(
|
|
rule, path, e))
|
|
else:
|
|
if m is not None:
|
|
LOG.debug(
|
|
'Matched "{}" for path "{}" producing {}'.format(
|
|
rule, path, m))
|
|
return (rule.linenum,) + m
|