Skip to content

Instantly share code, notes, and snippets.

@markscottwright
Last active January 10, 2024 20:51
Show Gist options
  • Save markscottwright/329d1638b1c3b10a54ffd413f6a89b93 to your computer and use it in GitHub Desktop.
Save markscottwright/329d1638b1c3b10a54ffd413f6a89b93 to your computer and use it in GitHub Desktop.
How to parse a DN in python
import string
from io import StringIO
__all__ = ['parse_dn', 'dn_to_string']
class _Peekable:
def __init__(self, wrapped):
self.wrapped = wrapped
self.last_char = None
def next_char(self):
if self.last_char is not None:
t = self.last_char
self.last_char = None
return t
else:
return self.wrapped.read(1)
def push(self, c):
self.last_char = c
def _read_attribute(dn_reader):
"""read up to and consume ="""
attribute = ''
while (c := dn_reader.next_char()) != '=':
if c == '':
raise Exception("DN parsing error")
attribute += c
return attribute
def _read_string(dn_reader):
s = ""
spaces = ''
# skip leading whitespace
while (c := dn_reader.next_char()) != '':
if c == ',' or c == '+':
dn_reader.push(c)
break
elif c == ' ':
spaces += c
else:
# only add spaces we've seen if we're in the middle of a string
if s != '':
s += spaces
spaces = ''
if c == '\\':
next_c = dn_reader.next_char()
if next_c in r'"+,;\<>=':
s += next_c
elif next_c in "0123456789abcdefABCDEF":
hexchar2 = dn_reader.next_char()
s += chr(int(next_c + hexchar2, 16))
else:
s += c
return s
def _read_rdn(dn_reader, normalize_attributes):
out = [_read_name_and_attribute(dn_reader, normalize_attributes)]
while (c := dn_reader.next_char()) == '+':
out.append(_read_name_and_attribute(dn_reader, normalize_attributes))
dn_reader.push(c)
return out
def _read_name_and_attribute(dn_reader, normalize_attributes):
if normalize_attributes:
return _read_attribute(dn_reader).upper() + "=" + _read_string(dn_reader)
else:
return _read_attribute(dn_reader) + "=" + _read_string(dn_reader)
def _read_dn(dn_reader, normalize_attributes):
out = [_read_rdn(dn_reader, normalize_attributes)]
while (c := dn_reader.next_char()) == ',':
out.append(_read_rdn(dn_reader, normalize_attributes))
dn_reader.push(c)
return out
def parse_dn(s: str, normalize_attributes=True) -> list[list[str]]:
r"""
Given a DN string, return a list of rdns, where an rdn is a list of "attribute=value"
>>> parse_dn(r'CN=Mark Wright,OU=Spectre+UID=1234,C=US')
[['CN=Mark Wright'], ['OU=Spectre', 'UID=1234'], ['C=US']]
>>> parse_dn(r'CN= Mark Wright\20 ')
[['CN=Mark Wright ']]
"""
return _read_dn(_Peekable(StringIO(s)), normalize_attributes)
def _name_and_attribute_to_string(n):
value_position = n.index('=') + 1
last_non_space_position = len(n)-1
while n[last_non_space_position] == ' ':
last_non_space_position -= 1
out = n[0:value_position]
char_seen = False
for p in range(value_position, last_non_space_position+1):
if n[p] == ' ' and not char_seen:
out += r"\20";
else:
char_seen = True
if n[p] in r'"+,;\<>=':
out += "\\" + n[p]
elif n[p] in string.printable:
out += n[p]
else:
out += "\\02x" % ord(n[p])
out += "\\20" * (len(n) - last_non_space_position - 1)
return out
def _rdn_to_string(rdn):
return "+".join(_name_and_attribute_to_string(n) for n in rdn)
def dn_to_string(dn: list[list[str]]) -> str:
out = ''
return ",".join(_rdn_to_string(rdn) for rdn in dn)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment