@@ -2730,6 +2730,148 @@ def test_pax_extended_header(self):
27302730 finally :
27312731 tar .close ()
27322732
2733+ def _roundtrip_pax_member (self , tarinfo ):
2734+ # Write the member in PAX format and return it as it is read back.
2735+ with tarfile .open (tmpname , "w" , format = tarfile .PAX_FORMAT ) as tar :
2736+ tar .addfile (tarinfo )
2737+ with tarfile .open (tmpname ) as tar :
2738+ return tar .getmembers ()[0 ]
2739+
2740+ def _read_pax_member (self , pax_headers , type = tarfile .REGTYPE ):
2741+ # Return a member that was read from an archive with the given
2742+ # pax extended header.
2743+ t = tarfile .TarInfo ("ustar" )
2744+ t .type = type
2745+ with open (tmpname , "wb" ) as fobj :
2746+ fobj .write (t ._create_pax_generic_header (
2747+ pax_headers , tarfile .XHDTYPE , "utf-8" ))
2748+ fobj .write (t .tobuf (tarfile .USTAR_FORMAT ))
2749+ fobj .write (bytes (tarfile .RECORDSIZE ))
2750+ with tarfile .open (tmpname ) as tar :
2751+ return tar .getmembers ()[0 ]
2752+
2753+ def test_pax_extended_header_changed_attributes (self ):
2754+ # The fields from a pax header that was read from an archive
2755+ # must not undo later changes to the TarInfo.
2756+ pax_headers = {"path" : "../foo" , "linkpath" : "../bar" ,
2757+ "mtime" : "1000.5" , "uid" : "123" , "gid" : "124" ,
2758+ "uname" : "spam" , "gname" : "eggs" , "comment" : "ham" }
2759+ changes = {"name" : "foo" , "linkname" : "bar" , "mtime" : 2000 ,
2760+ "uid" : 1 , "gid" : 2 , "uname" : "tarfile" , "gname" : "tarfile" }
2761+
2762+ def check (t ):
2763+ t = self ._roundtrip_pax_member (t )
2764+ for attr , value in changes .items ():
2765+ self .assertEqual (getattr (t , attr ), value )
2766+ self .assertEqual (t .pax_headers , {"comment" : "ham" })
2767+
2768+ with self .subTest ("replace" ):
2769+ t = self ._read_pax_member (pax_headers , tarfile .SYMTYPE )
2770+ self .assertEqual (t .pax_headers , pax_headers )
2771+ check (t .replace (** changes ))
2772+ with self .subTest ("replace shallow" ):
2773+ t = self ._read_pax_member (pax_headers , tarfile .SYMTYPE )
2774+ check (t .replace (** changes , deep = False ))
2775+ self .assertEqual (t .pax_headers , pax_headers )
2776+ with self .subTest ("setattr" ):
2777+ t = self ._read_pax_member (pax_headers , tarfile .SYMTYPE )
2778+ for attr , value in changes .items ():
2779+ setattr (t , attr , value )
2780+ check (t )
2781+
2782+ for attr , value in changes .items ():
2783+ with self .subTest (attr = attr ):
2784+ t = self ._read_pax_member (pax_headers , tarfile .SYMTYPE )
2785+ t2 = self ._roundtrip_pax_member (t .replace (** {attr : value }))
2786+ self .assertEqual (getattr (t2 , attr ), value )
2787+ for other in changes .keys () - {attr }:
2788+ self .assertEqual (getattr (t2 , other ), getattr (t , other ))
2789+
2790+ def test_pax_extended_header_renamed_copy (self ):
2791+ # Members with a name that needs a pax header can be renamed
2792+ # when they are copied to another archive.
2793+ with tarfile .open (tmpname , "w" , format = tarfile .PAX_FORMAT ) as tar :
2794+ tar .addfile (tarfile .TarInfo ("\xe4 \xf6 \xfc " ))
2795+ tar .addfile (tarfile .TarInfo ("foo" ))
2796+ with tarfile .open (tmpname ) as tar :
2797+ members = tar .getmembers ()
2798+ self .assertEqual (members [0 ].pax_headers , {"path" : "\xe4 \xf6 \xfc " })
2799+ with tarfile .open (tmpname , "w" , format = tarfile .PAX_FORMAT ) as tar :
2800+ for t in members :
2801+ t .name = "bar/" + t .name
2802+ tar .addfile (t )
2803+ with tarfile .open (tmpname ) as tar :
2804+ self .assertEqual (tar .getnames (), ["bar/\xe4 \xf6 \xfc " , "bar/foo" ])
2805+
2806+ def test_pax_extended_header_without_applied_fields (self ):
2807+ # A TarInfo object that was pickled by an older version
2808+ # has no record of the applied fields.
2809+ t = self ._read_pax_member ({"path" : "foo" })
2810+ del t ._pax_applied
2811+ t .name = "bar"
2812+ self .assertEqual (self ._roundtrip_pax_member (t ).name , "foo" )
2813+
2814+ def test_pax_extended_header_changed_size (self ):
2815+ t = self ._read_pax_member ({"size" : "0" })
2816+ t .size = 3
2817+ with tarfile .open (tmpname , "w" , format = tarfile .PAX_FORMAT ) as tar :
2818+ tar .addfile (t , io .BytesIO (b"foo" ))
2819+ tar .addfile (tarfile .TarInfo ("bar" ))
2820+ with tarfile .open (tmpname ) as tar :
2821+ self .assertEqual (tar .getnames (), ["ustar" , "bar" ])
2822+ self .assertEqual (tar .extractfile ("ustar" ).read (), b"foo" )
2823+
2824+ def test_pax_extended_header_changed_sparse_name (self ):
2825+ t = self ._read_pax_member ({"path" : "foo" , "GNU.sparse.name" : "bar" })
2826+ self .assertEqual (t .name , "bar" )
2827+ self .assertEqual (self ._roundtrip_pax_member (t ).name , "bar" )
2828+ t = self ._roundtrip_pax_member (t .replace (name = "baz" ))
2829+ self .assertEqual (t .name , "baz" )
2830+ self .assertEqual (t .pax_headers , {})
2831+
2832+ t = self ._read_pax_member ({"GNU.sparse.name" : "bar" })
2833+ t .name = "baz"
2834+ self .assertEqual (self ._roundtrip_pax_member (t ).name , "baz" )
2835+
2836+ def test_pax_extended_header_unchanged_attributes (self ):
2837+ # The fields from a pax header are preserved if the
2838+ # corresponding attributes are not changed.
2839+ pax_headers = {"path" : "foo" , "mtime" : "1000.123456789" ,
2840+ "uid" : "123" , "comment" : "ham" }
2841+ t = self ._read_pax_member (pax_headers )
2842+ t = self ._roundtrip_pax_member (t .replace (uname = "tarfile" ))
2843+ self .assertEqual (t .pax_headers , pax_headers )
2844+ self .assertEqual (t .name , "foo" )
2845+ self .assertEqual (t .uid , 123 )
2846+ self .assertEqual (t .uname , "tarfile" )
2847+
2848+ def test_pax_extended_header_changed_pax_headers (self ):
2849+ # The fields that are set after the pax header was read
2850+ # still have priority over the TarInfo.
2851+ t = self ._read_pax_member ({"path" : "foo" , "uid" : "123" })
2852+ t .name = "bar"
2853+ t .uid = 124
2854+ t .pax_headers ["path" ] = "baz"
2855+ t = self ._roundtrip_pax_member (t )
2856+ self .assertEqual (t .pax_headers , {"path" : "baz" })
2857+ self .assertEqual (t .name , "baz" )
2858+ self .assertEqual (t .uid , 124 )
2859+
2860+ def test_pax_extended_header_gnu_longname (self ):
2861+ # A GNU longname header that precedes the pax header overrides
2862+ # its path field.
2863+ t = tarfile .TarInfo ("ustar" )
2864+ t .pax_headers = {"path" : "foo" }
2865+ with open (tmpname , "wb" ) as fobj :
2866+ fobj .write (t ._create_gnu_long_header (
2867+ "bar" , tarfile .GNUTYPE_LONGNAME , "utf-8" , "strict" ))
2868+ fobj .write (t .tobuf (tarfile .PAX_FORMAT ))
2869+ fobj .write (bytes (tarfile .RECORDSIZE ))
2870+ with tarfile .open (tmpname ) as tar :
2871+ t = tar .getmembers ()[0 ]
2872+ self .assertEqual (t .name , "bar" )
2873+ self .assertEqual (self ._roundtrip_pax_member (t ).name , "bar" )
2874+
27332875 def test_create_pax_header (self ):
27342876 # The ustar header should contain values that can be
27352877 # represented reasonably, even if a better (e.g. higher
0 commit comments