import struct
def string_is_safe(s, filename=False, identifier=False):
for c in s:
if ord(c) < 0x20 or ord(c) > 0x7F:
return False
if filename and not ((ord(c) >= 0x41 and ord(c) <= 0x5A)
or (ord(c) >= 0x61 and ord(c) <= 0x7A)
or (ord(c) >= 0x30 and ord(c) <= 0x39)
or (ord(c) in [0x2E])):
return False
if identifier and not ((ord(c) >= 0x41 and ord(c) <= 0x5A)
or (ord(c) >= 0x61 and ord(c) <= 0x7A)):
return False
if filename and s[0] == '.':
return False
return True
_u8_format = '!B'
def u8_pack(v):
return struct.pack(_u8_format, v)
def u8_unpack(data):
return struct.unpack(_u8_format, data)[0]
u8_size = struct.calcsize(_u8_format)
_u32_format = '!I'
def u32_pack(v):
return struct.pack(_u32_format, v)
def u32_unpack(data):
return struct.unpack(_u32_format, data)[0]
u32_size = struct.calcsize(_u32_format)
_u64_format = '!Q'
def u64_pack(v):
return struct.pack(_u64_format, v)
def u64_unpack(data):
return struct.unpack(_u64_format, data)[0]
u64_size = struct.calcsize(_u64_format)
class InvalidFieldsError(Exception):
pass
def read_fields(read_fn):
buf = read_fn(u8_size)
num_fields = u8_unpack(buf)
if num_fields > 255:
raise InvalidFieldsError('Too many fields')
fields = {}
for _ in range(num_fields):
buf = read_fn(u8_size)
size = u8_unpack(buf)
if size == 0 or size > 255:
raise InvalidFieldsError('Invalid field key length')
key = read_fn(size).decode('utf-8')
if not string_is_safe(key):
raise InvalidFieldsError('Unprintable key value')
buf = read_fn(u8_size)
size = u8_unpack(buf)
if size > 255:
raise InvalidFieldsError('Invalid field value length')
value = read_fn(size)
fields[key] = value
return fields
def format_fields(fields):
if len(fields) > 255:
raise ValueError('Too many fields')
data = u8_pack(len(fields))
for (key, value) in fields.items():
if len(key) > 255:
raise ValueError('Key name {0!s} too long'.format(key))
data += u8_pack(len(key))
data += key.encode('utf-8')
if isinstance(value, bool):
if value:
value = u8_pack(1)
else:
value = u8_pack(0)
elif isinstance(value, int):
value = u32_pack(value)
elif isinstance(value, str):
value = value.encode('utf-8')
elif isinstance(value, bytes):
pass
else:
raise ValueError('Unknown value type of {0!s}'.format(repr(value)))
if len(value) > 255:
raise ValueError('Value {0!s} too long'.format(repr(value)))
data += u8_pack(len(value))
data += value
return data