Skip to content

Commit a3c0c22

Browse files
fagermistotebe
authored andcommitted
feat(ldif) Add support for add, delete and rename operations in LDIFParser
Fixes: #567
1 parent 7014a81 commit a3c0c22

2 files changed

Lines changed: 268 additions & 23 deletions

File tree

Lib/ldif.py

Lines changed: 119 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
0:'add',1:'delete',2:'replace',3:'increment'
4646
}
4747

48-
CHANGE_TYPES = ['add','delete','modify','modrdn']
48+
CHANGE_TYPES = ['add','delete','modify','modrdn', 'moddn', 'rename']
4949
valid_changetype_dict = {}
5050
for c in CHANGE_TYPES:
5151
valid_changetype_dict[c]=None
@@ -459,6 +459,17 @@ def parse(self):
459459
"""
460460
return self.parse_entry_records() # parse()
461461

462+
def handle_add(self, dn, entry, controls=None):
463+
"""
464+
Process a single LDIF record representing a single add operation.
465+
This method should be implemented by applications using LDIFParser.
466+
467+
Args:
468+
dn (str): DN of the new object to be created
469+
entry (dict): Data of the new object to be created
470+
"""
471+
pass
472+
462473
def handle_modify(self,dn,modops,controls=None):
463474
"""
464475
Process a single LDIF record representing a single modify operation.
@@ -467,6 +478,31 @@ def handle_modify(self,dn,modops,controls=None):
467478
controls = [] or None
468479
pass
469480

481+
def handle_modrdn(self, dn, newrdn, deleteoldrdn=False, newsuperior=None,
482+
controls=None):
483+
"""
484+
Process a single LDIF record representing a single modrdn/rename operation.
485+
This method should be implemented by applications using LDIFParser.
486+
487+
Args:
488+
dn (str): DN of the existing object to be renamed/moved
489+
newrdn (str): RDN of the new object
490+
deleteoldrdn (bool): Whether the old RDN value(s) should be removed
491+
from the entry
492+
newsuperior (str): DN of the new parent
493+
"""
494+
pass
495+
496+
def handle_delete(self, dn, controls=None):
497+
"""
498+
Process a single LDIF record representing a single delete operation.
499+
This method should be implemented by applications using LDIFParser.
500+
501+
Args:
502+
dn (str): DN of the existing object to be deleted
503+
"""
504+
pass
505+
470506
def parse_change_records(self):
471507
# Local symbol for better performance
472508
next_key_and_value = self._next_key_and_value
@@ -509,7 +545,7 @@ def parse_change_records(self):
509545
# Consume changetype line of record
510546
if k=='changetype':
511547
# v is still bytes, spec says it should be valid utf-8; decode it.
512-
v = v.decode('utf-8')
548+
v = v.decode('utf-8').lower()
513549
if not v in valid_changetype_dict:
514550
raise ValueError('Invalid changetype: %s' % repr(v))
515551
changetype = v
@@ -551,12 +587,60 @@ def parse_change_records(self):
551587
except EOFError:
552588
k,v = None,None
553589

554-
if modops:
555-
# append entry to result list
556-
self.handle_modify(dn,modops,controls)
590+
self.handle_modify(dn, modops, controls or None)
557591

592+
elif changetype == 'add':
593+
entry = {}
594+
while k!=None:
595+
if not k.lower() in self._ignored_attr_types:
596+
entry.setdefault(k, []).append(v)
597+
try:
598+
k,v = next_key_and_value()
599+
except EOFError:
600+
k,v = None,None
601+
602+
self.handle_add(dn,entry, controls or None)
603+
604+
elif changetype == 'delete':
605+
if k is not None:
606+
raise ValueError(f'Line {self.line_counter}: Unexpected '
607+
f'attribute {k} in LDIF delete')
608+
self.handle_delete(dn, controls or None)
609+
elif changetype in ('moddn', 'modrdn', 'rename'):
610+
if k.lower() != 'newrdn':
611+
raise ValueError(f'Line {self.line_counter}: expected "newrdn"'
612+
f'got {k}')
613+
newrdn = v.decode('utf-8')
614+
k,v = next_key_and_value()
615+
deleteoldrdn = False
616+
if k is not None:
617+
if k.lower() == 'deleteoldrdn':
618+
deleteoldrdn = (v == b'1')
619+
try:
620+
k,v = next_key_and_value()
621+
except EOFError:
622+
k,v = None,None
623+
elif k.lower() == 'newsuperior':
624+
pass
625+
else:
626+
raise ValueError(f'Line {self.line_counter}: expected '
627+
f'"deleteoldrdn" got {k}')
628+
newsuperior = None
629+
if k is not None:
630+
if k.lower() != 'newsuperior':
631+
raise ValueError(f'Line {self.line_counter}: expected '
632+
f'"newsuperior" got {k}')
633+
newsuperior = v.decode('utf-8')
634+
try:
635+
k,v = next_key_and_value()
636+
except EOFError:
637+
k,v = None,None
638+
if k is not None:
639+
raise ValueError(f'Line {self.line_counter}: {changetype} entry '
640+
f'unexpected pseudoattribute {k}')
641+
self.handle_modrdn(dn, newrdn, deleteoldrdn, newsuperior,
642+
controls or None)
558643
else:
559-
560644
# Consume the unhandled change record
561645
while k!=None:
562646
k,v = next_key_and_value()
@@ -592,20 +676,48 @@ def __init__(
592676
#: List storing parsed records.
593677
self.all_records = []
594678
self.all_modify_changes = []
679+
self.all_changes = []
595680

596681
def handle(self,dn,entry):
597682
"""
598683
Append a single record to the list of all records (:attr:`.all_records`).
599684
"""
600685
self.all_records.append((dn,entry))
601686

687+
def handle_add(self, dn, entry, controls=None):
688+
"""
689+
Process a single LDIF record representing a single add operation.
690+
This method should be implemented by applications using LDIFParser.
691+
"""
692+
self.all_changes.append(('add', {'dn': dn, 'entry': entry,
693+
'controls': controls}))
694+
602695
def handle_modify(self,dn,modops,controls=None):
603696
"""
604697
Process a single LDIF record representing a single modify operation.
605698
This method should be implemented by applications using LDIFParser.
606699
"""
607-
controls = [] or None
608700
self.all_modify_changes.append((dn,modops,controls))
701+
self.all_changes.append(('modify', {'dn': dn, 'modops': modops,
702+
'controls': controls}))
703+
704+
def handle_modrdn(self, dn, newrdn, deleteoldrdn=False, newsuperior=None,
705+
controls=None):
706+
"""
707+
Process a single LDIF record representing a single modrdn/rename operation.
708+
This method should be implemented by applications using LDIFParser.
709+
"""
710+
self.all_changes.append(('modrdn', {'dn': dn, 'newrdn': newrdn,
711+
'deleteoldrdn': deleteoldrdn,
712+
'newsuperior': newsuperior,
713+
'controls': controls}))
714+
715+
def handle_delete(self, dn, controls=None):
716+
"""
717+
Process a single LDIF record representing a single delete operation.
718+
This method should be implemented by applications using LDIFParser.
719+
"""
720+
self.all_changes.append(('delete', {'dn': dn, 'controls': controls}))
609721

610722

611723
class LDIFCopy(LDIFParser):

0 commit comments

Comments
 (0)