Daniel Sahlberg wrote on Fri, Feb 26, 2021 at 13:49:25 +0100: > +++ contrib/client-side/store-plaintext-password.py (working copy) > @@ -0,0 +1,127 @@ > +def readHashFile(filename): > + """Read a hashfile as written by svn_hash_write2() to a list of tuples > (key, value)""" > + hash = {} > + with open(authfileName, 'r', encoding='utf-8') as file: > + while True: > + # Expecting K [length] or END > + line = file.readline() > + if not line: > + raise Exception('Parse failed, expected K [length] or END, > got EOF') > + line = line.strip() > + if line.strip() == 'END': > + if file.readline(): > + raise Exception('Parse failed, unexpected data after > END') > + return hash > + elif line[:1] != 'K': > + raise Exception('Parse failed, expected K [length]') > + > + # Read keyname > + key = file.readline() > + if not line: > + raise Exception('Parse failed, expected keyname') > + key = key.strip() > + if len(key.encode('utf-8')) != int(line[2:]): > + raise Exception('Parse failed, invalid key length {} > expected {}'.format(len(key.encode('utf-8')), line[2:])) > + > + # Read V [length] > + line = file.readline() > + if not line: > + raise Exception('Parse failed, expected V [length], got EOF') > + line = line.strip() > + if line[:1] != 'V': > + raise Exception('Parse failed, expected V [length]') > + > + # Read value > + value = file.readline() > + if not value: > + raise Exception('Parse failed, expected value') > + value = value.strip() > + if len(value.encode('utf-8')) != int(line[2:]): > + raise Exception('Parse failed, invalid value length {} > expected {}'.format(len(value.encode('utf-8')), line[2:])) > + > + # Store > + hash[key] = value
Couldn't resist. Attached is my take of this function. Preview: [[[ % ./svn_hash_read.py ~/.subversion/auth/svn.simple/d3c8a345b14f6a1b42251aef8027ab57 {b'svn:realmstring': b'<https://svn.apache.org:443> ASF Committers', b'username': b'danielsh'} ]]] The script should support everything the serialized hash format allows, including binary data in both keys and values (even though we generally use «const char *» keys). I'm sure there are a few nits to pick, though. The code is runnable as-is, despite the ellipses. Cheers, Daniel [[[ #!/usr/bin/env python3 TERMINATOR = b"END\n" def _read_one_datum(fd, letter): """\ Read a 'K <length>\\n<key>\\n' or 'V <length>\\n<value>\\n' block from an svn_hash_write2()-format FD. LETTER identifies the first letter, as a bytes object. """ assert letter in {b'K', b'V'} # Read the letter and the space if fd.read(1) != letter or fd.read(1) != b' ': raise ... # Read the length and the newline line = fd.readline() if line[-1:] != b'\n': raise ... expected_length = int(line[:-1]) # Read the datum and its newline datum = fd.read(expected_length) if len(datum) != expected_length: raise ... if fd.read(1) != b'\n': raise ... return datum def svn_hash_read(fd): """\ Read an svn_hash_write2()-formatted dict from FD, terminated by "END". Return a dict mapping bytes to bytes. """ assert 'b' in fd.mode assert TERMINATOR[0] not in {b'K', b'V'} ret = {} while True: if fd.peek(1)[0] == TERMINATOR[0]: if fd.readline() != TERMINATOR: raise ... if fd.peek(1): raise ... return ret key = _read_one_datum(fd, b'K') value = _read_one_datum(fd, b'V') ret[key] = value def main(): import sys parsed = svn_hash_read(open(sys.argv[1], 'rb')) import pprint pprint.pprint(parsed) if __name__ == '__main__': main() ]]]