@@ -2205,21 +2205,25 @@ def process_file(self, *, tarinfo, status, type, tar):
22052205INCOMPLETE = object ()
22062206
22072207
2208- def valid_msgpacked_dict (d ):
2209- """check if the data <d> looks like a msgpacked item dict
2208+ def valid_msgpacked_dict (d , offset = 0 ):
2209+ """check if the data <d> at <offset> looks like a msgpacked item dict
22102210
22112211 That is: a msgpack map whose first key is a msgpack str that looks like an item key. Items are
22122212 packed with sorted keys, so the first key is the alphabetically smallest key of the item. The key
22132213 is not checked against a list of known keys, so items written by a newer borg version (with keys
22142214 this version does not know) are found as well.
2215+
2216+ <d> is only indexed, never sliced: so <d> can be a memoryview and no child views of it are created
2217+ (on PyPy, a child view that was not released keeps the viewed object exported, i.e. not resizable,
2218+ until the garbage collector collects the view).
22152219 """
22162220 d_len = len (d )
2217- if d_len == 0 :
2221+ if d_len <= offset :
22182222 return False
2219- if d [0 ] & 0xF0 == 0x80 : # object is a fixmap (up to 15 elements)
2220- offs = 1
2221- elif d [0 ] == 0xDE : # object is a map16 (up to 2^16-1 elements)
2222- offs = 3
2223+ if d [offset ] & 0xF0 == 0x80 : # object is a fixmap (up to 15 elements)
2224+ offs = offset + 1
2225+ elif d [offset ] == 0xDE : # object is a map16 (up to 2^16-1 elements)
2226+ offs = offset + 3
22232227 else :
22242228 # object is not a map (dict)
22252229 # note: we must not have dicts with > 2^16-1 elements
@@ -2237,11 +2241,11 @@ def valid_msgpacked_dict(d):
22372241 else :
22382242 # key is not a (short) str
22392243 return False
2240- key = d [ key_offs : key_offs + key_len ]
2241- if key_len == 0 or len ( key ) < key_len :
2244+ key_end = key_offs + key_len
2245+ if key_len == 0 or d_len < key_end :
22422246 return False
22432247 # does the key look like an item key?
2244- return all (c in ITEM_KEY_CHARS for c in key )
2248+ return all (d [ i ] in ITEM_KEY_CHARS for i in range ( key_offs , key_end ) )
22452249
22462250
22472251class RobustUnpacker :
@@ -2301,6 +2305,10 @@ def _search(self):
23012305 searched_end = max (self ._search_offset , data_len - MAX_ITEM_HEADER_LEN )
23022306 pending_offsets = [o for o in self ._pending_offsets if data_len - o <= self .MAX_PENDING_ITEM_LEN ]
23032307 self ._pending_offsets = []
2308+ # _buffered_data is resized below (and by feed()), which fails while a memoryview of it exists. CPython
2309+ # frees an unreferenced view at once, PyPy only when the garbage collector runs, so every view of
2310+ # _buffered_data is released explicitly: data here, the slice in _try_item_start. valid_msgpacked_dict
2311+ # gets (data, offset) instead of a slice of data, it only indexes data.
23042312 with memoryview (self ._buffered_data ) as data :
23052313 for offset in pending_offsets :
23062314 item = self ._try_item_start (data , offset )
@@ -2309,7 +2317,7 @@ def _search(self):
23092317 elif item is not None :
23102318 return item
23112319 for offset in range (self ._search_offset , data_len ):
2312- if not valid_msgpacked_dict (data [ offset :] ):
2320+ if not valid_msgpacked_dict (data , offset ):
23132321 continue
23142322 item = self ._try_item_start (data , offset )
23152323 if item is INCOMPLETE :
0 commit comments