@@ -22,9 +22,8 @@ class VisorTarInfo(tarfile.TarInfo):
2222 fixUpPgs : int | None
2323
2424 @classmethod
25- def frombuf (cls , buf : bytes , encoding : str , errors : str ) -> VisorTarInfo :
26- obj = super ().frombuf (buf , encoding , errors )
27-
25+ def _init_visor_attrs (cls , obj : VisorTarInfo , buf : bytes ) -> None :
26+ """Initialize visor-specific attributes from the raw header buffer."""
2827 obj .is_visor = buf [257 :264 ] == b"visor "
2928 if obj .is_visor :
3029 obj .offset_data = struct .unpack ("<I" , buf [496 :500 ])[0 ]
@@ -35,6 +34,19 @@ def frombuf(cls, buf: bytes, encoding: str, errors: str) -> VisorTarInfo:
3534 obj .textPgs = None
3635 obj .fixUpPgs = None
3736
37+ @classmethod
38+ def frombuf (cls , buf : bytes , encoding : str , errors : str ) -> VisorTarInfo :
39+ obj = super ().frombuf (buf , encoding , errors )
40+ cls ._init_visor_attrs (obj , buf )
41+ return obj
42+
43+ @classmethod
44+ def _frombuf (cls , buf : bytes , encoding : str , errors : str , ** kwargs ) -> VisorTarInfo :
45+ # Python 3.13.13+ refactored tarfile to call _frombuf directly instead of
46+ # frombuf in fromtarfile and _proc_gnulong/_proc_pax. We must override _frombuf
47+ # to ensure visor-specific attributes are initialized for all code paths.
48+ obj = super ()._frombuf (buf , encoding , errors , ** kwargs )
49+ cls ._init_visor_attrs (obj , buf )
3850 return obj
3951
4052 def _proc_member (self , tarfile : tarfile .TarFile ) -> VisorTarInfo | tarfile .TarInfo :
@@ -76,6 +88,8 @@ def visoropen(cls, name: str, mode: str = "r", fileobj: BinaryIO | None = None,
7688 try :
7789 t = cls .taropen (name , mode , fileobj , ** kwargs )
7890 except Exception :
91+ if fileobj is not None :
92+ fileobj .seek (0 )
7993 try :
8094 fileobj = GzipFile (name , mode + "b" , fileobj = fileobj )
8195 except OSError as e :
@@ -103,10 +117,11 @@ def visoropen(cls, name: str, mode: str = "r", fileobj: BinaryIO | None = None,
103117 compressed = True
104118
105119 # If we get here, we have a valid visor tar file
106- if fileobj is not None and compressed :
107- # Just read the entire file into memory, it's probably small
120+ if fileobj is not None :
108121 fileobj .seek (0 )
109- fileobj = BytesIO (fileobj .read ())
122+ if compressed :
123+ # Read the decompressed data into a BytesIO to make random reads faster
124+ fileobj = BytesIO (fileobj .read ())
110125
111126 t = cls .taropen (name , mode , fileobj , ** kwargs )
112127
0 commit comments