|
@@ -2,7 +2,6 @@ import stat
|
|
|
from collections import namedtuple
|
|
|
|
|
|
from .constants import ITEM_KEYS, ARCHIVE_KEYS
|
|
|
-from .helpers import safe_encode, safe_decode
|
|
|
from .helpers import StableDict
|
|
|
from .helpers import format_file_size
|
|
|
from .helpers.msgpack import timestamp_to_int, int_to_timestamp
|
|
@@ -16,6 +15,51 @@ cdef extern from "_item.c":
|
|
|
API_VERSION = '1.2_01'
|
|
|
|
|
|
|
|
|
+def fix_key(data, key):
|
|
|
+ """if k is a bytes-typed key, migrate key/value to a str-typed key in dict data"""
|
|
|
+ if isinstance(key, bytes):
|
|
|
+ value = data.pop(key)
|
|
|
+ key = key.decode()
|
|
|
+ data[key] = value
|
|
|
+ assert isinstance(key, str)
|
|
|
+ return key
|
|
|
+
|
|
|
+
|
|
|
+def fix_str_value(data, key, errors='surrogateescape'):
|
|
|
+ """makes sure that data[key] is a str (decode if it is bytes)"""
|
|
|
+ assert isinstance(key, str) # fix_key must be called first
|
|
|
+ value = data[key]
|
|
|
+ if isinstance(value, bytes):
|
|
|
+ value = value.decode('utf-8', errors=errors)
|
|
|
+ data[key] = value
|
|
|
+ assert isinstance(value, str)
|
|
|
+ return value
|
|
|
+
|
|
|
+
|
|
|
+def fix_list_of_str(t):
|
|
|
+ """make sure we have a list of str"""
|
|
|
+ assert isinstance(t, (tuple, list))
|
|
|
+ l = [e.decode() if isinstance(e, bytes) else e for e in t]
|
|
|
+ assert all(isinstance(e, str) for e in l), repr(l)
|
|
|
+ return l
|
|
|
+
|
|
|
+
|
|
|
+def fix_tuple_of_str(t):
|
|
|
+ """make sure we have a tuple of str"""
|
|
|
+ assert isinstance(t, (tuple, list))
|
|
|
+ t = tuple(e.decode() if isinstance(e, bytes) else e for e in t)
|
|
|
+ assert all(isinstance(e, str) for e in t), repr(t)
|
|
|
+ return t
|
|
|
+
|
|
|
+
|
|
|
+def fix_tuple_of_str_and_int(t):
|
|
|
+ """make sure we have a tuple of str"""
|
|
|
+ assert isinstance(t, (tuple, list))
|
|
|
+ t = tuple(e.decode() if isinstance(e, bytes) else e for e in t)
|
|
|
+ assert all(isinstance(e, (str, int)) for e in t), repr(t)
|
|
|
+ return t
|
|
|
+
|
|
|
+
|
|
|
class PropDict:
|
|
|
"""
|
|
|
Manage a dictionary via properties.
|
|
@@ -155,10 +199,10 @@ class Item(PropDict):
|
|
|
|
|
|
# properties statically defined, so that IDEs can know their names:
|
|
|
|
|
|
- path = PropDict._make_property('path', str, 'surrogate-escaped str', encode=safe_encode, decode=safe_decode)
|
|
|
- source = PropDict._make_property('source', str, 'surrogate-escaped str', encode=safe_encode, decode=safe_decode)
|
|
|
- user = PropDict._make_property('user', (str, type(None)), 'surrogate-escaped str or None', encode=safe_encode, decode=safe_decode)
|
|
|
- group = PropDict._make_property('group', (str, type(None)), 'surrogate-escaped str or None', encode=safe_encode, decode=safe_decode)
|
|
|
+ path = PropDict._make_property('path', str, 'surrogate-escaped str')
|
|
|
+ source = PropDict._make_property('source', str, 'surrogate-escaped str')
|
|
|
+ user = PropDict._make_property('user', (str, type(None)), 'surrogate-escaped str or None')
|
|
|
+ group = PropDict._make_property('group', (str, type(None)), 'surrogate-escaped str or None')
|
|
|
|
|
|
acl_access = PropDict._make_property('acl_access', bytes)
|
|
|
acl_default = PropDict._make_property('acl_default', bytes)
|
|
@@ -290,6 +334,14 @@ class Item(PropDict):
|
|
|
except AttributeError:
|
|
|
return False
|
|
|
|
|
|
+ def update_internal(self, d):
|
|
|
+ # legacy support for migration (data from old msgpacks comes in as bytes always, but sometimes we want str)
|
|
|
+ for k, v in list(d.items()):
|
|
|
+ k = fix_key(d, k)
|
|
|
+ if k in ('path', 'source', 'user', 'group'):
|
|
|
+ v = fix_str_value(d, k)
|
|
|
+ self._dict[k] = v
|
|
|
+
|
|
|
|
|
|
class EncryptedKey(PropDict):
|
|
|
"""
|
|
@@ -309,7 +361,7 @@ class EncryptedKey(PropDict):
|
|
|
__slots__ = ("_dict", ) # avoid setting attributes not supported by properties
|
|
|
|
|
|
version = PropDict._make_property('version', int)
|
|
|
- algorithm = PropDict._make_property('algorithm', str, encode=str.encode, decode=bytes.decode)
|
|
|
+ algorithm = PropDict._make_property('algorithm', str)
|
|
|
iterations = PropDict._make_property('iterations', int)
|
|
|
salt = PropDict._make_property('salt', bytes)
|
|
|
hash = PropDict._make_property('hash', bytes)
|
|
@@ -317,7 +369,17 @@ class EncryptedKey(PropDict):
|
|
|
argon2_time_cost = PropDict._make_property('argon2_time_cost', int)
|
|
|
argon2_memory_cost = PropDict._make_property('argon2_memory_cost', int)
|
|
|
argon2_parallelism = PropDict._make_property('argon2_parallelism', int)
|
|
|
- argon2_type = PropDict._make_property('argon2_type', str, encode=str.encode, decode=bytes.decode)
|
|
|
+ argon2_type = PropDict._make_property('argon2_type', str)
|
|
|
+
|
|
|
+ def update_internal(self, d):
|
|
|
+ # legacy support for migration (data from old msgpacks comes in as bytes always, but sometimes we want str)
|
|
|
+ for k, v in list(d.items()):
|
|
|
+ k = fix_key(d, k)
|
|
|
+ if k == 'version':
|
|
|
+ assert isinstance(v, int)
|
|
|
+ if k in ('algorithm', 'argon2_type'):
|
|
|
+ v = fix_str_value(d, k)
|
|
|
+ self._dict[k] = v
|
|
|
|
|
|
|
|
|
class Key(PropDict):
|
|
@@ -344,17 +406,13 @@ class Key(PropDict):
|
|
|
chunk_seed = PropDict._make_property('chunk_seed', int)
|
|
|
tam_required = PropDict._make_property('tam_required', bool)
|
|
|
|
|
|
-
|
|
|
-def tuple_encode(t):
|
|
|
- """encode a tuple that might contain str items"""
|
|
|
- # we have str, but want to give bytes to msgpack.pack
|
|
|
- return tuple(safe_encode(e) if isinstance(e, str) else e for e in t)
|
|
|
-
|
|
|
-
|
|
|
-def tuple_decode(t):
|
|
|
- """decode a tuple that might contain bytes items"""
|
|
|
- # we get bytes objects from msgpack.unpack, but want str
|
|
|
- return tuple(safe_decode(e) if isinstance(e, bytes) else e for e in t)
|
|
|
+ def update_internal(self, d):
|
|
|
+ # legacy support for migration (data from old msgpacks comes in as bytes always, but sometimes we want str)
|
|
|
+ for k, v in list(d.items()):
|
|
|
+ k = fix_key(d, k)
|
|
|
+ if k == 'version':
|
|
|
+ assert isinstance(v, int)
|
|
|
+ self._dict[k] = v
|
|
|
|
|
|
|
|
|
class ArchiveItem(PropDict):
|
|
@@ -374,15 +432,15 @@ class ArchiveItem(PropDict):
|
|
|
__slots__ = ("_dict", ) # avoid setting attributes not supported by properties
|
|
|
|
|
|
version = PropDict._make_property('version', int)
|
|
|
- name = PropDict._make_property('name', str, 'surrogate-escaped str', encode=safe_encode, decode=safe_decode)
|
|
|
+ name = PropDict._make_property('name', str, 'surrogate-escaped str')
|
|
|
items = PropDict._make_property('items', list)
|
|
|
cmdline = PropDict._make_property('cmdline', list) # list of s-e-str
|
|
|
- hostname = PropDict._make_property('hostname', str, 'surrogate-escaped str', encode=safe_encode, decode=safe_decode)
|
|
|
- username = PropDict._make_property('username', str, 'surrogate-escaped str', encode=safe_encode, decode=safe_decode)
|
|
|
- time = PropDict._make_property('time', str, 'surrogate-escaped str', encode=safe_encode, decode=safe_decode)
|
|
|
- time_end = PropDict._make_property('time_end', str, 'surrogate-escaped str', encode=safe_encode, decode=safe_decode)
|
|
|
- comment = PropDict._make_property('comment', str, 'surrogate-escaped str', encode=safe_encode, decode=safe_decode)
|
|
|
- chunker_params = PropDict._make_property('chunker_params', tuple, 'chunker-params tuple', encode=tuple_encode, decode=tuple_decode)
|
|
|
+ hostname = PropDict._make_property('hostname', str, 'surrogate-escaped str')
|
|
|
+ username = PropDict._make_property('username', str, 'surrogate-escaped str')
|
|
|
+ time = PropDict._make_property('time', str)
|
|
|
+ time_end = PropDict._make_property('time_end', str)
|
|
|
+ comment = PropDict._make_property('comment', str, 'surrogate-escaped str')
|
|
|
+ chunker_params = PropDict._make_property('chunker_params', tuple)
|
|
|
recreate_cmdline = PropDict._make_property('recreate_cmdline', list) # list of s-e-str
|
|
|
# recreate_source_id, recreate_args, recreate_partial_chunks were used in 1.1.0b1 .. b2
|
|
|
recreate_source_id = PropDict._make_property('recreate_source_id', bytes)
|
|
@@ -395,6 +453,22 @@ class ArchiveItem(PropDict):
|
|
|
csize_parts = PropDict._make_property('csize_parts', int)
|
|
|
nfiles_parts = PropDict._make_property('nfiles_parts', int)
|
|
|
|
|
|
+ def update_internal(self, d):
|
|
|
+ # legacy support for migration (data from old msgpacks comes in as bytes always, but sometimes we want str)
|
|
|
+ for k, v in list(d.items()):
|
|
|
+ k = fix_key(d, k)
|
|
|
+ if k == 'version':
|
|
|
+ assert isinstance(v, int)
|
|
|
+ if k in ('name', 'hostname', 'username', 'comment'):
|
|
|
+ v = fix_str_value(d, k)
|
|
|
+ if k in ('time', 'time_end'):
|
|
|
+ v = fix_str_value(d, k, 'replace')
|
|
|
+ if k == 'chunker_params':
|
|
|
+ v = fix_tuple_of_str_and_int(v)
|
|
|
+ if k in ('cmdline', 'recreate_cmdline'):
|
|
|
+ v = fix_list_of_str(v)
|
|
|
+ self._dict[k] = v
|
|
|
+
|
|
|
|
|
|
class ManifestItem(PropDict):
|
|
|
"""
|
|
@@ -413,10 +487,52 @@ class ManifestItem(PropDict):
|
|
|
__slots__ = ("_dict", ) # avoid setting attributes not supported by properties
|
|
|
|
|
|
version = PropDict._make_property('version', int)
|
|
|
- archives = PropDict._make_property('archives', dict) # name -> dict
|
|
|
- timestamp = PropDict._make_property('timestamp', str, 'surrogate-escaped str', encode=safe_encode, decode=safe_decode)
|
|
|
+ archives = PropDict._make_property('archives', dict, 'dict of str -> dict') # name -> dict
|
|
|
+ timestamp = PropDict._make_property('timestamp', str)
|
|
|
config = PropDict._make_property('config', dict)
|
|
|
- item_keys = PropDict._make_property('item_keys', tuple)
|
|
|
+ item_keys = PropDict._make_property('item_keys', tuple, 'tuple of str')
|
|
|
+
|
|
|
+ def update_internal(self, d):
|
|
|
+ # legacy support for migration (data from old msgpacks comes in as bytes always, but sometimes we want str)
|
|
|
+ for k, v in list(d.items()):
|
|
|
+ k = fix_key(d, k)
|
|
|
+ if k == 'version':
|
|
|
+ assert isinstance(v, int)
|
|
|
+ if k == 'archives':
|
|
|
+ ad = v
|
|
|
+ assert isinstance(ad, dict)
|
|
|
+ for ak, av in list(ad.items()):
|
|
|
+ ak = fix_key(ad, ak)
|
|
|
+ assert isinstance(av, dict)
|
|
|
+ for ik, iv in list(av.items()):
|
|
|
+ ik = fix_key(av, ik)
|
|
|
+ assert set(av) == {'id', 'time'}
|
|
|
+ assert isinstance(av['id'], bytes)
|
|
|
+ fix_str_value(av, 'time')
|
|
|
+ if k == 'timestamp':
|
|
|
+ v = fix_str_value(d, k, 'replace')
|
|
|
+ if k == 'config':
|
|
|
+ cd = v
|
|
|
+ assert isinstance(cd, dict)
|
|
|
+ for ck, cv in list(cd.items()):
|
|
|
+ ck = fix_key(cd, ck)
|
|
|
+ if ck == 'tam_required':
|
|
|
+ assert isinstance(cv, bool)
|
|
|
+ if ck == 'feature_flags':
|
|
|
+ assert isinstance(cv, dict)
|
|
|
+ ops = {'read', 'check', 'write', 'delete'}
|
|
|
+ for op, specs in list(cv.items()):
|
|
|
+ op = fix_key(cv, op)
|
|
|
+ assert op in ops
|
|
|
+ for speck, specv in list(specs.items()):
|
|
|
+ speck = fix_key(specs, speck)
|
|
|
+ if speck == 'mandatory':
|
|
|
+ specs[speck] = fix_tuple_of_str(specv)
|
|
|
+ assert set(cv).issubset(ops)
|
|
|
+ if k == 'item_keys':
|
|
|
+ v = fix_tuple_of_str(v)
|
|
|
+ self._dict[k] = v
|
|
|
+
|
|
|
|
|
|
class ItemDiff:
|
|
|
"""
|