Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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

146

147

148

149

150

151

152

153

154

155

156

157

# -*- coding: utf-8 -*- 

############################################################################## 

# 

# Copyright (c) 2012 Jens Vagelpohl and Contributors. All Rights Reserved. 

# 

# This software is subject to the provisions of the Zope Public License, 

# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 

# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 

# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 

# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 

# FOR A PARTICULAR PURPOSE. 

# 

############################################################################## 

 

import re 

 

import six 

 

from dataflake.fakeldap.op import Op 

from dataflake.fakeldap.queryfilter import Filter 

from dataflake.fakeldap.utils import utf8_string 

 

# From http://www.ietf.org/rfc/rfc2254.txt, Section 4 

# with references to http://www.ietf.org/rfc/rfc2251.txt 

# 

# filter = "(" filtercomp ")" 

# filtercomp = and / or / not / item 

# and = "&" filterlist 

# or = "|" filterlist 

# not = "!" filter 

# filterlist = 1*filter 

# item = simple / present / substring / extensible 

# simple = attr filtertype value 

# filtertype = equal / approx / greater / less 

# equal = "=" 

# approx = "~=" 

# greater = ">=" 

# less = "<=" 

# extensible = attr [":dn"] [":" matchingrule] ":=" value 

# / [":dn"] ":" matchingrule ":=" value 

# present = attr "=*" 

# substring = attr "=" [initial] any [final] 

# initial = value 

# any = "*" *(value "*") 

# final = value 

# attr = AttributeDescription from Section 4.1.5 of RFC 2251 

# matchingrule = MatchingRuleId from Section 4.1.9 of RFC 2251 

# value = AttributeValue from Section 4.1.6 of RFC 2251 

 

 

_FLTR = br'\(\w*?=[\*\w\s=,\\]*?\)' 

_OP = b'[&\|\!]{1}' 

 

FLTR = (br'\((?P<attr>\w*?)(?P<comp>=)' + 

br'(?P<value>[\*\w\s=,\\\'@\-\+_\.^\W\d_]*?)\)' + 

b'(?P<fltr>.*)') 

FLTR_RE = re.compile(FLTR.decode(), re.UNICODE) 

 

FULL = b'\((?P<op>(%s))(?P<fltr>.*)\)' % _OP 

FULL_RE = re.compile(FULL) 

 

OP = b'\((?P<op>(%s))(?P<fltr>(%s)*)\)' % (_OP, _FLTR) 

OP_RE = re.compile(OP) 

 

 

class Parser(object): 

 

@utf8_string('query') 

def parse_query(self, query, recurse=False): 

""" Parse a query string into a series of Ops and Filters 

""" 

parts = [] 

for expr in (OP_RE, FULL_RE): 

# Match outermost operations 

m = expr.match(query) 

if m: 

d = m.groupdict() 

op = Op(d['op']) 

sub = self.parse_query(d['fltr']) 

if recurse: 

parts.append((op, sub)) 

else: 

parts.append(op) 

parts.append(sub) 

rest = query[m.end():] 

if rest: 

parts.extend(self.parse_query(rest)) 

return tuple(parts) 

 

# Under Python 3, our regular expression is type <str>, which 

# does not handle <bytes>. Doing a temporary decode. 

decode_needed = six.PY3 and isinstance(query, bytes) 

if decode_needed: 

query = query.decode('UTF-8') 

 

# Match internal filter. 

d = FLTR_RE.match(query).groupdict() 

 

# Revert the previous temporary decode 

if decode_needed: 

for k, v in d.items(): 

d[k] = v.encode('UTF-8') 

 

parts.append(Filter(d['attr'], d['comp'], d['value'])) 

if d['fltr']: 

parts.extend(self.parse_query(d['fltr'], recurse=True)) 

return tuple(parts) 

 

def flatten_query(self, query, klass=Filter): 

""" Flatten a sequence of Ops/Filters leaving only ``klass`` instances 

""" 

q = [f for f in query if isinstance(f, klass)] 

for item in query: 

if isinstance(item, tuple): 

q.extend(self.flatten_query(item, klass)) 

return tuple(q) 

 

def explode_query(self, query): 

""" Separate a parsed query into operations 

""" 

res = [] 

 

def dig(sub, res): 

level = [] 

for item in sub: 

if isinstance(item, tuple): 

got = dig(item, res) 

if got and level and isinstance(level[0], Op): 

level.append(got) 

res.append(tuple(level)) 

level = [] 

else: 

level.append(item) 

return tuple(level) 

 

level = dig(query, res) 

if not res: 

# A simple filter with no operands 

return ((Op(b'&'), level),) 

if level: 

# Very likely a single operand around a group of filters. 

assert len(level) == 1, (len(level), level) 

res.append((level[0], ())) 

return tuple(res) 

 

def cmp_query(self, query, other, strict=False): 

""" Compare parsed queries and return common query elements 

""" 

q1 = self.flatten_query(query) 

q2 = self.flatten_query(other) 

 

if strict: 

return q1 == q2 

 

for fltr in q2: 

if fltr in q1: 

return fltr