1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
|
# Copyright (c) 2015-2021 by Ron Frederick <ronf@timeheart.net> and others.
#
# This program and the accompanying materials are made available under
# the terms of the Eclipse Public License v2.0 which accompanies this
# distribution and is available at:
#
# http://www.eclipse.org/legal/epl-2.0/
#
# This program may also be made available under the following secondary
# licenses when the conditions for such availability set forth in the
# Eclipse Public License v2.0 are satisfied:
#
# GNU General Public License, Version 2.0, or any later versions of
# that license
#
# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
#
# Contributors:
# Ron Frederick - initial implementation, API, and documentation
"""Pattern matching for principal and host names"""
from fnmatch import fnmatch
from typing import Union
from .misc import IPAddress, ip_network
_HostPattern = Union['WildcardHostPattern', 'CIDRHostPattern']
_AnyPattern = Union['WildcardPattern', _HostPattern]
class _BaseWildcardPattern:
"""A base class for matching '*' and '?' wildcards"""
def __init__(self, pattern: str):
# We need to escape square brackets in host patterns if we
# want to use Python's fnmatch.
self._pattern = ''.join('[[]' if ch == '[' else
'[]]' if ch == ']' else
ch for ch in pattern)
def _matches(self, value: str) -> bool:
"""Return whether a wild card pattern matches a value"""
return fnmatch(value, self._pattern)
class WildcardPattern(_BaseWildcardPattern):
"""A pattern matcher for '*' and '?' wildcards"""
def matches(self, value: str) -> bool:
"""Return whether a wild card pattern matches a value"""
return super()._matches(value)
class WildcardHostPattern(_BaseWildcardPattern):
"""Match a host name or address against a wildcard pattern"""
def matches(self, host: str, addr: str, _ip: IPAddress) -> bool:
"""Return whether a host or address matches a wild card host pattern"""
return (bool(host) and super()._matches(host)) or \
(bool(addr) and super()._matches(addr))
class CIDRHostPattern:
"""Match IPv4/v6 address against CIDR-style subnet pattern"""
def __init__(self, pattern: str):
self._network = ip_network(pattern)
def matches(self, _host: str, _addr: str, ip: IPAddress) -> bool:
"""Return whether an IP address matches a CIDR address pattern"""
return bool(ip) and ip in self._network
class _PatternList:
"""Match against a list of comma-separated positive and negative patterns
This class is a base class for building a pattern matcher that
takes a set of comma-separated positive and negative patterns,
returning `True` if one or more positive patterns match and
no negative ones do.
The pattern matching is done by objects returned by the
build_pattern method. The arguments passed in when a match
is performed will vary depending on what class build_pattern
returns.
"""
def __init__(self, patterns: str):
self._pos_patterns = []
self._neg_patterns = []
for pattern in patterns.split(','):
if pattern.startswith('!'):
negate = True
pattern = pattern[1:]
else:
negate = False
matcher = self.build_pattern(pattern)
if negate:
self._neg_patterns.append(matcher)
else:
self._pos_patterns.append(matcher)
def build_pattern(self, pattern: str) -> _AnyPattern:
"""Abstract method to build a pattern object"""
raise NotImplementedError
def matches(self, *args) -> bool:
"""Match a set of values against positive & negative pattern lists"""
pos_match = any(p.matches(*args) for p in self._pos_patterns)
neg_match = any(p.matches(*args) for p in self._neg_patterns)
return pos_match and not neg_match
class WildcardPatternList(_PatternList):
"""Match names against wildcard patterns"""
def build_pattern(self, pattern: str) -> WildcardPattern:
"""Build a wild card pattern"""
return WildcardPattern(pattern)
class HostPatternList(_PatternList):
"""Match host names & addresses against wildcard and CIDR patterns"""
def build_pattern(self, pattern: str) -> _HostPattern:
"""Build a CIDR address or wild card host pattern"""
try:
return CIDRHostPattern(pattern)
except ValueError:
return WildcardHostPattern(pattern)
|