From 38c6a22f38a249d107691a79f2b16a62ba8c73be Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 10 May 2006 16:26:03 +0000 Subject: [PATCH] Patch #1484695: Update the tarfile module to version 0.8. This fixes a couple of issues, notably handling of long file names using the GNU LONGNAME extension. --- Doc/lib/libtarfile.tex | 6 +- Lib/tarfile.py | 380 ++++++++++++++++++++------------------- Lib/test/test_tarfile.py | 49 ++++- Lib/test/testtar.tar | Bin 112640 -> 133120 bytes Misc/NEWS | 4 + 5 files changed, 256 insertions(+), 183 deletions(-) diff --git a/Doc/lib/libtarfile.tex b/Doc/lib/libtarfile.tex index a49942bc5362..ca6e65a8ad57 100644 --- a/Doc/lib/libtarfile.tex +++ b/Doc/lib/libtarfile.tex @@ -334,8 +334,12 @@ the file's data itself. Create and return a \class{TarInfo} object from a string buffer. \end{methoddesc} -\begin{methoddesc}{tobuf}{} +\begin{methoddesc}{tobuf}{posix} Create a string buffer from a \class{TarInfo} object. + See \class{TarFile}'s \member{posix} attribute for information + on the \var{posix} argument. It defaults to \constant{False}. + + \versionadded[The \var{posix} parameter]{2.5} \end{methoddesc} A \code{TarInfo} object has the following public data attributes: diff --git a/Lib/tarfile.py b/Lib/tarfile.py index 0b3d4771719c..8987ca709e7e 100644 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -33,7 +33,7 @@ __version__ = "$Revision$" # $Source$ -version = "0.6.4" +version = "0.8.0" __author__ = "Lars Gustäbel (lars@gustaebel.de)" __date__ = "$Date$" __cvsid__ = "$Id$" @@ -137,16 +137,64 @@ def nts(s): """ return s.rstrip(NUL) -def calc_chksum(buf): - """Calculate the checksum for a member's header. It's a simple addition - of all bytes, treating the chksum field as if filled with spaces. - buf is a 512 byte long string buffer which holds the header. +def stn(s, length): + """Convert a python string to a null-terminated string buffer. """ - chk = 256 # chksum field is treated as blanks, - # so the initial value is 8 * ord(" ") - for c in buf[:148]: chk += ord(c) # sum up all bytes before chksum - for c in buf[156:]: chk += ord(c) # sum up all bytes after chksum - return chk + return struct.pack("%ds" % (length - 1), s) + NUL + +def nti(s): + """Convert a number field to a python number. + """ + # There are two possible encodings for a number field, see + # itn() below. + if s[0] != chr(0200): + n = int(s.rstrip(NUL) or "0", 8) + else: + n = 0L + for i in xrange(len(s) - 1): + n <<= 8 + n += ord(s[i + 1]) + return n + +def itn(n, digits=8, posix=False): + """Convert a python number to a number field. + """ + # POSIX 1003.1-1988 requires numbers to be encoded as a string of + # octal digits followed by a null-byte, this allows values up to + # (8**(digits-1))-1. GNU tar allows storing numbers greater than + # that if necessary. A leading 0200 byte indicates this particular + # encoding, the following digits-1 bytes are a big-endian + # representation. This allows values up to (256**(digits-1))-1. + if 0 <= n < 8 ** (digits - 1): + s = "%0*o" % (digits - 1, n) + NUL + else: + if posix: + raise ValueError, "overflow in number field" + + if n < 0: + # XXX We mimic GNU tar's behaviour with negative numbers, + # this could raise OverflowError. + n = struct.unpack("L", struct.pack("l", n))[0] + + s = "" + for i in xrange(digits - 1): + s = chr(n & 0377) + s + n >>= 8 + s = chr(0200) + s + return s + +def calc_chksums(buf): + """Calculate the checksum for a member's header by summing up all + characters except for the chksum field which is treated as if + it was filled with spaces. According to the GNU tar sources, + some tars (Sun and NeXT) calculate chksum with signed char, + which will be different if there are chars in the buffer with + the high bit set. So we calculate two checksums, unsigned and + signed. + """ + unsigned_chksum = 256 + sum(struct.unpack("148B", buf[:148]) + struct.unpack("356B", buf[156:512])) + signed_chksum = 256 + sum(struct.unpack("148b", buf[:148]) + struct.unpack("356b", buf[156:512])) + return unsigned_chksum, signed_chksum def copyfileobj(src, dst, length=None): """Copy length bytes from fileobj src to fileobj dst. @@ -655,7 +703,7 @@ class ExFileObject(object): """Get an iterator over the file object. """ if self.closed: - raise ValueError("I/O operation on closed file") + raise ValueError, "I/O operation on closed file" return self def next(self): @@ -684,24 +732,24 @@ class TarInfo(object): of the member. """ - self.name = name # member name (dirnames must end with '/') - self.mode = 0666 # file permissions - self.uid = 0 # user id - self.gid = 0 # group id - self.size = 0 # file size - self.mtime = 0 # modification time - self.chksum = 0 # header checksum - self.type = REGTYPE # member type - self.linkname = "" # link name - self.uname = "user" # user name - self.gname = "group" # group name - self.devmajor = 0 #- - self.devminor = 0 #-for use with CHRTYPE and BLKTYPE - self.prefix = "" # prefix to filename or holding information - # about sparse files - - self.offset = 0 # the tar header starts here - self.offset_data = 0 # the file's data starts here + self.name = name # member name (dirnames must end with '/') + self.mode = 0666 # file permissions + self.uid = 0 # user id + self.gid = 0 # group id + self.size = 0 # file size + self.mtime = 0 # modification time + self.chksum = 0 # header checksum + self.type = REGTYPE # member type + self.linkname = "" # link name + self.uname = "user" # user name + self.gname = "group" # group name + self.devmajor = 0 # device major number + self.devminor = 0 # device minor number + self.prefix = "" # prefix to filename or information + # about sparse files + + self.offset = 0 # the tar header starts here + self.offset_data = 0 # the file's data starts here def __repr__(self): return "<%s %r at %#x>" % (self.__class__.__name__,self.name,id(self)) @@ -710,95 +758,57 @@ class TarInfo(object): def frombuf(cls, buf): """Construct a TarInfo object from a 512 byte string buffer. """ - tarinfo = cls() - tarinfo.name = nts(buf[0:100]) - tarinfo.mode = int(buf[100:108], 8) - tarinfo.uid = int(buf[108:116],8) - tarinfo.gid = int(buf[116:124],8) - - # There are two possible codings for the size field we - # have to discriminate, see comment in tobuf() below. - if buf[124] != chr(0200): - tarinfo.size = long(buf[124:136], 8) - else: - tarinfo.size = 0L - for i in range(11): - tarinfo.size <<= 8 - tarinfo.size += ord(buf[125 + i]) + if len(buf) != BLOCKSIZE: + raise ValueError, "truncated header" + if buf.count(NUL) == BLOCKSIZE: + raise ValueError, "empty header" - tarinfo.mtime = long(buf[136:148], 8) - tarinfo.chksum = int(buf[148:156], 8) - tarinfo.type = buf[156:157] + tarinfo = cls() + tarinfo.buf = buf + tarinfo.name = nts(buf[0:100]) + tarinfo.mode = nti(buf[100:108]) + tarinfo.uid = nti(buf[108:116]) + tarinfo.gid = nti(buf[116:124]) + tarinfo.size = nti(buf[124:136]) + tarinfo.mtime = nti(buf[136:148]) + tarinfo.chksum = nti(buf[148:156]) + tarinfo.type = buf[156:157] tarinfo.linkname = nts(buf[157:257]) - tarinfo.uname = nts(buf[265:297]) - tarinfo.gname = nts(buf[297:329]) - try: - tarinfo.devmajor = int(buf[329:337], 8) - tarinfo.devminor = int(buf[337:345], 8) - except ValueError: - tarinfo.devmajor = tarinfo.devmajor = 0 + tarinfo.uname = nts(buf[265:297]) + tarinfo.gname = nts(buf[297:329]) + tarinfo.devmajor = nti(buf[329:337]) + tarinfo.devminor = nti(buf[337:345]) tarinfo.prefix = buf[345:500] - # Some old tar programs represent a directory as a regular - # file with a trailing slash. - if tarinfo.isreg() and tarinfo.name.endswith("/"): - tarinfo.type = DIRTYPE - - # The prefix field is used for filenames > 100 in - # the POSIX standard. - # name = prefix + '/' + name - if tarinfo.type != GNUTYPE_SPARSE: - tarinfo.name = normpath(os.path.join(nts(tarinfo.prefix), tarinfo.name)) - - # Directory names should have a '/' at the end. - if tarinfo.isdir(): - tarinfo.name += "/" + if tarinfo.chksum not in calc_chksums(buf): + raise ValueError, "invalid header" return tarinfo - def tobuf(self): + def tobuf(self, posix=False): """Return a tar header block as a 512 byte string. """ - # Prefer the size to be encoded as 11 octal ascii digits - # which is the most portable. If the size exceeds this - # limit (>= 8 GB), encode it as an 88-bit value which is - # a GNU tar feature. - if self.size <= MAXSIZE_MEMBER: - size = "%011o" % self.size - else: - s = self.size - size = "" - for i in range(11): - size = chr(s & 0377) + size - s >>= 8 - size = chr(0200) + size - - # The following code was contributed by Detlef Lannert. - parts = [] - for value, fieldsize in ( - (self.name, 100), - ("%07o" % (self.mode & 07777), 8), - ("%07o" % self.uid, 8), - ("%07o" % self.gid, 8), - (size, 12), - ("%011o" % self.mtime, 12), - (" ", 8), - (self.type, 1), - (self.linkname, 100), - (MAGIC, 6), - (VERSION, 2), - (self.uname, 32), - (self.gname, 32), - ("%07o" % self.devmajor, 8), - ("%07o" % self.devminor, 8), - (self.prefix, 155) - ): - l = len(value) - parts.append(value[:fieldsize] + (fieldsize - l) * NUL) - - buf = "".join(parts) - chksum = calc_chksum(buf) + parts = [ + stn(self.name, 100), + itn(self.mode & 07777, 8, posix), + itn(self.uid, 8, posix), + itn(self.gid, 8, posix), + itn(self.size, 12, posix), + itn(self.mtime, 12, posix), + " ", # checksum field + self.type, + stn(self.linkname, 100), + stn(MAGIC, 6), + stn(VERSION, 2), + stn(self.uname, 32), + stn(self.gname, 32), + itn(self.devmajor, 8, posix), + itn(self.devminor, 8, posix), + stn(self.prefix, 155) + ] + + buf = struct.pack("%ds" % BLOCKSIZE, "".join(parts)) + chksum = calc_chksums(buf)[0] buf = buf[:148] + "%06o\0" % chksum + buf[155:] - buf += (BLOCKSIZE - len(buf)) * NUL self.buf = buf return buf @@ -873,12 +883,12 @@ class TarFile(object): self.fileobj = fileobj # Init datastructures - self.closed = False - self.members = [] # list of members as TarInfo objects - self._loaded = False # flag if all members have been read - self.offset = 0L # current position in the archive file - self.inodes = {} # dictionary caching the inodes of - # archive members already added + self.closed = False + self.members = [] # list of members as TarInfo objects + self._loaded = False # flag if all members have been read + self.offset = 0L # current position in the archive file + self.inodes = {} # dictionary caching the inodes of + # archive members already added if self._mode == "r": self.firstmember = None @@ -1347,7 +1357,7 @@ class TarFile(object): tarinfo.name = tarinfo.name[:LENGTH_NAME - 1] self._dbg(2, "tarfile: Created GNU tar extension LONGNAME") - self.fileobj.write(tarinfo.tobuf()) + self.fileobj.write(tarinfo.tobuf(self.posix)) self.offset += BLOCKSIZE # If there's data to follow, append it. @@ -1660,7 +1670,6 @@ class TarFile(object): raise ExtractError, "could not change modification time" #-------------------------------------------------------------------------- - def next(self): """Return the next member of the archive as a TarInfo object, when TarFile is opened for reading. Return None if there is no more @@ -1678,70 +1687,81 @@ class TarFile(object): buf = self.fileobj.read(BLOCKSIZE) if not buf: return None + try: tarinfo = TarInfo.frombuf(buf) - except ValueError: + + # Set the TarInfo object's offset to the current position of the + # TarFile and set self.offset to the position where the data blocks + # should begin. + tarinfo.offset = self.offset + self.offset += BLOCKSIZE + + tarinfo = self.proc_member(tarinfo) + + except ValueError, e: if self.ignore_zeros: - if buf.count(NUL) == BLOCKSIZE: - adj = "empty" - else: - adj = "invalid" - self._dbg(2, "0x%X: %s block" % (self.offset, adj)) + self._dbg(2, "0x%X: %s" % (self.offset, e)) self.offset += BLOCKSIZE continue else: - # Block is empty or unreadable. if self.offset == 0: - # If the first block is invalid. That does not - # look like a tar archive we can handle. - raise ReadError,"empty, unreadable or compressed file" + raise ReadError, str(e) return None break - # We shouldn't rely on this checksum, because some tar programs - # calculate it differently and it is merely validating the - # header block. We could just as well skip this part, which would - # have a slight effect on performance... - if tarinfo.chksum != calc_chksum(buf): - self._dbg(1, "tarfile: Bad Checksum %r" % tarinfo.name) - - # Set the TarInfo object's offset to the current position of the - # TarFile and set self.offset to the position where the data blocks - # should begin. - tarinfo.offset = self.offset - self.offset += BLOCKSIZE + # Some old tar programs represent a directory as a regular + # file with a trailing slash. + if tarinfo.isreg() and tarinfo.name.endswith("/"): + tarinfo.type = DIRTYPE - # Check if the TarInfo object has a typeflag for which a callback - # method is registered in the TYPE_METH. If so, then call it. - if tarinfo.type in self.TYPE_METH: - return self.TYPE_METH[tarinfo.type](self, tarinfo) + # The prefix field is used for filenames > 100 in + # the POSIX standard. + # name = prefix + '/' + name + tarinfo.name = normpath(os.path.join(nts(tarinfo.prefix), tarinfo.name)) - tarinfo.offset_data = self.offset - if tarinfo.isreg() or tarinfo.type not in SUPPORTED_TYPES: - # Skip the following data blocks. - self.offset += self._block(tarinfo.size) + # Directory names should have a '/' at the end. + if tarinfo.isdir(): + tarinfo.name += "/" self.members.append(tarinfo) return tarinfo #-------------------------------------------------------------------------- - # Below are some methods which are called for special typeflags in the - # next() method, e.g. for unwrapping GNU longname/longlink blocks. They - # are registered in TYPE_METH below. You can register your own methods - # with this mapping. - # A registered method is called with a TarInfo object as only argument. - # - # During its execution the method MUST perform the following tasks: - # 1. set tarinfo.offset_data to the position where the data blocks begin, - # if there is data to follow. - # 2. set self.offset to the position where the next member's header will + # The following are methods that are called depending on the type of a + # member. The entry point is proc_member() which is called with a TarInfo + # object created from the header block from the current offset. The + # proc_member() method can be overridden in a subclass to add custom + # proc_*() methods. A proc_*() method MUST implement the following + # operations: + # 1. Set tarinfo.offset_data to the position where the data blocks begin, + # if there is data that follows. + # 2. Set self.offset to the position where the next member's header will # begin. - # 3. append the tarinfo object to self.members, if it is supposed to appear - # as a member of the TarFile object. - # 4. return tarinfo or another valid TarInfo object. + # 3. Return tarinfo or another valid TarInfo object. + def proc_member(self, tarinfo): + """Choose the right processing method for tarinfo depending + on its type and call it. + """ + if tarinfo.type in (GNUTYPE_LONGNAME, GNUTYPE_LONGLINK): + return self.proc_gnulong(tarinfo) + elif tarinfo.type == GNUTYPE_SPARSE: + return self.proc_sparse(tarinfo) + else: + return self.proc_builtin(tarinfo) + + def proc_builtin(self, tarinfo): + """Process a builtin type member or an unknown member + which will be treated as a regular file. + """ + tarinfo.offset_data = self.offset + if tarinfo.isreg() or tarinfo.type not in SUPPORTED_TYPES: + # Skip the following data blocks. + self.offset += self._block(tarinfo.size) + return tarinfo def proc_gnulong(self, tarinfo): - """Evaluate the blocks that hold a GNU longname + """Process the blocks that hold a GNU longname or longlink member. """ buf = "" @@ -1752,9 +1772,15 @@ class TarFile(object): self.offset += BLOCKSIZE count -= BLOCKSIZE - # Fetch the next header - next = self.next() + # Fetch the next header and process it. + b = self.fileobj.read(BLOCKSIZE) + t = TarInfo.frombuf(b) + t.offset = self.offset + self.offset += BLOCKSIZE + next = self.proc_member(t) + # Patch the TarInfo object from the next header with + # the longname information. next.offset = tarinfo.offset if tarinfo.type == GNUTYPE_LONGNAME: next.name = nts(buf) @@ -1764,9 +1790,9 @@ class TarFile(object): return next def proc_sparse(self, tarinfo): - """Analyze a GNU sparse header plus extra headers. + """Process a GNU sparse header plus extra headers. """ - buf = tarinfo.tobuf() + buf = tarinfo.buf sp = _ringbuffer() pos = 386 lastpos = 0L @@ -1775,8 +1801,8 @@ class TarFile(object): # first header. for i in xrange(4): try: - offset = int(buf[pos:pos + 12], 8) - numbytes = int(buf[pos + 12:pos + 24], 8) + offset = nti(buf[pos:pos + 12]) + numbytes = nti(buf[pos + 12:pos + 24]) except ValueError: break if offset > lastpos: @@ -1787,7 +1813,7 @@ class TarFile(object): pos += 24 isextended = ord(buf[482]) - origsize = int(buf[483:495], 8) + origsize = nti(buf[483:495]) # If the isextended flag is given, # there are extra headers to process. @@ -1797,8 +1823,8 @@ class TarFile(object): pos = 0 for i in xrange(21): try: - offset = int(buf[pos:pos + 12], 8) - numbytes = int(buf[pos + 12:pos + 24], 8) + offset = nti(buf[pos:pos + 12]) + numbytes = nti(buf[pos + 12:pos + 24]) except ValueError: break if offset > lastpos: @@ -1818,17 +1844,11 @@ class TarFile(object): self.offset += self._block(tarinfo.size) tarinfo.size = origsize - self.members.append(tarinfo) - return tarinfo + # Clear the prefix field so that it is not used + # as a pathname in next(). + tarinfo.prefix = "" - # The type mapping for the next() method. The keys are single character - # strings, the typeflag. The values are methods which are called when - # next() encounters such a typeflag. - TYPE_METH = { - GNUTYPE_LONGNAME: proc_gnulong, - GNUTYPE_LONGLINK: proc_gnulong, - GNUTYPE_SPARSE: proc_sparse - } + return tarinfo #-------------------------------------------------------------------------- # Little helper methods: diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index 98349c4ebfd5..03fb55f93533 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -2,6 +2,7 @@ import sys import os import shutil import tempfile +import StringIO import unittest import tarfile @@ -25,7 +26,7 @@ def path(path): testtar = path("testtar.tar") tempdir = os.path.join(tempfile.gettempdir(), "testtar" + os.extsep + "dir") tempname = test_support.TESTFN -membercount = 10 +membercount = 12 def tarname(comp=""): if not comp: @@ -254,7 +255,7 @@ class WriteTest(BaseTest): if not tarinfo.isreg(): continue f = self.src.extractfile(tarinfo) - if self.dst.posix and len(tarinfo.name) > tarfile.LENGTH_NAME: + if self.dst.posix and len(tarinfo.name) > tarfile.LENGTH_NAME and "/" not in tarinfo.name: self.assertRaises(ValueError, self.dst.addfile, tarinfo, f) else: @@ -385,6 +386,49 @@ class WriteGNULongTest(unittest.TestCase): self._test(("longnam/" * 127) + "longname_", ("longlnk/" * 127) + "longlink_") +class ReadGNULongTest(unittest.TestCase): + + def setUp(self): + self.tar = tarfile.open(tarname()) + + def tearDown(self): + self.tar.close() + + def test_1471427(self): + """Test reading of longname (bug #1471427). + """ + name = "test/" * 20 + "0-REGTYPE" + try: + tarinfo = self.tar.getmember(name) + except KeyError: + tarinfo = None + self.assert_(tarinfo is not None, "longname not found") + self.assert_(tarinfo.type != tarfile.DIRTYPE, "read longname as dirtype") + + def test_read_name(self): + name = ("0-LONGNAME-" * 10)[:101] + try: + tarinfo = self.tar.getmember(name) + except KeyError: + tarinfo = None + self.assert_(tarinfo is not None, "longname not found") + + def test_read_link(self): + link = ("1-LONGLINK-" * 10)[:101] + name = ("0-LONGNAME-" * 10)[:101] + try: + tarinfo = self.tar.getmember(link) + except KeyError: + tarinfo = None + self.assert_(tarinfo is not None, "longlink not found") + self.assert_(tarinfo.linkname == name, "linkname wrong") + + def test_truncated_longname(self): + fobj = StringIO.StringIO(file(tarname()).read(1024)) + tar = tarfile.open(name="foo.tar", fileobj=fobj) + self.assert_(len(tar.getmembers()) == 0, "") + + class ExtractHardlinkTest(BaseTest): def test_hardlink(self): @@ -512,6 +556,7 @@ def test_main(): WriteSize0Test, WriteStreamTest, WriteGNULongTest, + ReadGNULongTest, ] if hasattr(os, "link"): diff --git a/Lib/test/testtar.tar b/Lib/test/testtar.tar index 8fd0c50c92a54790f66e231202f97a196e17f2f0..1f4493f3516ca1003553c591f94027068d8a6761 100644 GIT binary patch delta 12470 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 delta 2918 zc-p;IZ%k8H6z@gFTA7o@%()P`>gJya?R&59^+5&Nf)(fhov6qkg=hPK2c?C+VgZFB ziW-MgIGNjA=B6yD@ej^7T{f5a4-?~z*}g3MgwZdHU*;AQ7Z$4ZEw_@7J(2Su#kb|j(evecw zi5@FnE(Ap@W+;j=QRybijA_owrCC|V;Ajrk`U6&+Ed&Cs*kQyaLO>QmIAfD)u($ae zMWfr_pq#fG(4?Go#mZI3T{LUN$$;~*Dm2EcoU7ng&Rn2!5@5fekI!rM2K~PNNy`z+ zhsn++!)C#USNmnTxdB?7X_~2`IAf3P%hVyM&C+zBi zg#}iGp{ksn@%MrO7h`DeX0^T_7Q8tl5-`hPT-+ddy-<<27;dlEC#z@e^WQL=HY1(Fs4dKx?R{fB~m)XTm zD-%5RLd1T#tBcrNlAY*xqzt03OHskzD%=U#b;}S|ns)Putp8q@MReUB8_`?pGvTMm zudnJ=Ysm&xAJ~uy3wpTN5Gs4r#X~*WMBnKdbxBEkSHQ=6JE7lYQ;#)oUncRp=v8&n z{tN164OTd}X5nO#{3Wn7l%p(JdkkqqUDQ?rOH0DAyM38rTyKh6t18{1oeq~?&`#UC za^QAZo7Qx{yAYBqMjfKO;u8G0!2$1Yq_p1U`_khd210_@qs;d#K+r8-Rkl}OL6EyW z8{+F|tuI&aM9U%hU3=nclIOH^jB`bw7!ZQ~0Q|i64bX2)fXCi!`0bMn_%1N?FLV55 zG&c99LP{t+KJAr9q4Q|&Gchy)9<*u?tt@K0jmW88)gkEEs^(Ost$mALnbt1HX}1lB zTeQ>5H4eb*CWEb0iOydbg4;5*rn0i}Ne?Yuc}OMvkcRNfl(-$~DE7OtOE}gPQz-Yl zZ)m446=lO8J2|Z>sUmGOoz$Ahs;iAKq_<}eVmvp3FL2G3|&oM zAcpW$5$c=WwO+|*Zx9>gRI$ZfEBIUc>Myu>po z$X-FN1?hAK{BU}1f=8;6+F&+4QLZ-n#GuJtBc*PVe5TEk7*fq-ZW_UP zI{5rPk0b|8jbft=RbSkJmFIBZtF@CzFYc|MGlyZ=n6^tZJgdV6YTKRKEC+=E#=VJ- zzME5erRk9doMi~Fa}rryT3N8ND7Ui6QRuAvU&Zibq&ZG$ifcgsZAjF!yn|+G@6c@K z*nfBkouBAgrAq8Wh$C*8?n{Y!2VD_4b$6bB4kQc1^KkIWpeCC`)%^3uLypJmuvtBw zg06{(0Pr05JOC_f)?qI8Wq17rLScDIhb^&4Hzv4c%V;Quwa_GI#TuyISC){AQg(ei eylGrGnjI@?JWEAW;7i7Yj?O%C3lCJGPyYnL+_xhD diff --git a/Misc/NEWS b/Misc/NEWS index ab84dbfb053e..401f5d96f599 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -96,6 +96,10 @@ Extension Modules Library ------- +- Patch #1484695: Update the tarfile module to version 0.8. This fixes + a couple of issues, notably handling of long file names using the + GNU LONGNAME extension. + - Patch #1478292. ``doctest.register_optionflag(name)`` shouldn't create a new flag when ``name`` is already the name of an option flag. -- 2.47.3