mirror of https://github.com/python/cpython
Patch #1583880: fix tarfile's problems with long names and posix/
GNU modes. (backport from rev. 52524)
This commit is contained in:
parent
0d3de7612c
commit
2527f7fee0
169
Lib/tarfile.py
169
Lib/tarfile.py
|
@ -49,6 +49,7 @@ import stat
|
||||||
import errno
|
import errno
|
||||||
import time
|
import time
|
||||||
import struct
|
import struct
|
||||||
|
import copy
|
||||||
|
|
||||||
if sys.platform == 'mac':
|
if sys.platform == 'mac':
|
||||||
# This module needs work for MacOS9, especially in the area of pathname
|
# This module needs work for MacOS9, especially in the area of pathname
|
||||||
|
@ -793,7 +794,6 @@ class TarInfo(object):
|
||||||
"""Construct a TarInfo object. name is the optional name
|
"""Construct a TarInfo object. name is the optional name
|
||||||
of the member.
|
of the member.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.name = name # member name (dirnames must end with '/')
|
self.name = name # member name (dirnames must end with '/')
|
||||||
self.mode = 0666 # file permissions
|
self.mode = 0666 # file permissions
|
||||||
self.uid = 0 # user id
|
self.uid = 0 # user id
|
||||||
|
@ -807,8 +807,6 @@ class TarInfo(object):
|
||||||
self.gname = "group" # group name
|
self.gname = "group" # group name
|
||||||
self.devmajor = 0 # device major number
|
self.devmajor = 0 # device major number
|
||||||
self.devminor = 0 # device minor 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 = 0 # the tar header starts here
|
||||||
self.offset_data = 0 # the file's data starts here
|
self.offset_data = 0 # the file's data starts here
|
||||||
|
@ -840,24 +838,70 @@ class TarInfo(object):
|
||||||
tarinfo.gname = buf[297:329].rstrip(NUL)
|
tarinfo.gname = buf[297:329].rstrip(NUL)
|
||||||
tarinfo.devmajor = nti(buf[329:337])
|
tarinfo.devmajor = nti(buf[329:337])
|
||||||
tarinfo.devminor = nti(buf[337:345])
|
tarinfo.devminor = nti(buf[337:345])
|
||||||
tarinfo.prefix = buf[345:500]
|
prefix = buf[345:500].rstrip(NUL)
|
||||||
|
|
||||||
|
if prefix and not tarinfo.issparse():
|
||||||
|
tarinfo.name = prefix + "/" + tarinfo.name
|
||||||
|
|
||||||
if tarinfo.chksum not in calc_chksums(buf):
|
if tarinfo.chksum not in calc_chksums(buf):
|
||||||
raise ValueError("invalid header")
|
raise ValueError("invalid header")
|
||||||
return tarinfo
|
return tarinfo
|
||||||
|
|
||||||
def tobuf(self, posix=False):
|
def tobuf(self, posix=False):
|
||||||
"""Return a tar header block as a 512 byte string.
|
"""Return a tar header as a string of 512 byte blocks.
|
||||||
"""
|
"""
|
||||||
|
buf = ""
|
||||||
|
type = self.type
|
||||||
|
prefix = ""
|
||||||
|
|
||||||
|
if self.name.endswith("/"):
|
||||||
|
type = DIRTYPE
|
||||||
|
|
||||||
|
name = normpath(self.name)
|
||||||
|
|
||||||
|
if type == DIRTYPE:
|
||||||
|
# directories should end with '/'
|
||||||
|
name += "/"
|
||||||
|
|
||||||
|
linkname = self.linkname
|
||||||
|
if linkname:
|
||||||
|
# if linkname is empty we end up with a '.'
|
||||||
|
linkname = normpath(linkname)
|
||||||
|
|
||||||
|
if posix:
|
||||||
|
if self.size > MAXSIZE_MEMBER:
|
||||||
|
raise ValueError("file is too large (>= 8 GB)")
|
||||||
|
|
||||||
|
if len(self.linkname) > LENGTH_LINK:
|
||||||
|
raise ValueError("linkname is too long (>%d)" % (LENGTH_LINK))
|
||||||
|
|
||||||
|
if len(name) > LENGTH_NAME:
|
||||||
|
prefix = name[:LENGTH_PREFIX + 1]
|
||||||
|
while prefix and prefix[-1] != "/":
|
||||||
|
prefix = prefix[:-1]
|
||||||
|
|
||||||
|
name = name[len(prefix):]
|
||||||
|
prefix = prefix[:-1]
|
||||||
|
|
||||||
|
if not prefix or len(name) > LENGTH_NAME:
|
||||||
|
raise ValueError("name is too long")
|
||||||
|
|
||||||
|
else:
|
||||||
|
if len(self.linkname) > LENGTH_LINK:
|
||||||
|
buf += self._create_gnulong(self.linkname, GNUTYPE_LONGLINK)
|
||||||
|
|
||||||
|
if len(name) > LENGTH_NAME:
|
||||||
|
buf += self._create_gnulong(name, GNUTYPE_LONGNAME)
|
||||||
|
|
||||||
parts = [
|
parts = [
|
||||||
stn(self.name, 100),
|
stn(name, 100),
|
||||||
itn(self.mode & 07777, 8, posix),
|
itn(self.mode & 07777, 8, posix),
|
||||||
itn(self.uid, 8, posix),
|
itn(self.uid, 8, posix),
|
||||||
itn(self.gid, 8, posix),
|
itn(self.gid, 8, posix),
|
||||||
itn(self.size, 12, posix),
|
itn(self.size, 12, posix),
|
||||||
itn(self.mtime, 12, posix),
|
itn(self.mtime, 12, posix),
|
||||||
" ", # checksum field
|
" ", # checksum field
|
||||||
self.type,
|
type,
|
||||||
stn(self.linkname, 100),
|
stn(self.linkname, 100),
|
||||||
stn(MAGIC, 6),
|
stn(MAGIC, 6),
|
||||||
stn(VERSION, 2),
|
stn(VERSION, 2),
|
||||||
|
@ -865,15 +909,38 @@ class TarInfo(object):
|
||||||
stn(self.gname, 32),
|
stn(self.gname, 32),
|
||||||
itn(self.devmajor, 8, posix),
|
itn(self.devmajor, 8, posix),
|
||||||
itn(self.devminor, 8, posix),
|
itn(self.devminor, 8, posix),
|
||||||
stn(self.prefix, 155)
|
stn(prefix, 155)
|
||||||
]
|
]
|
||||||
|
|
||||||
buf = struct.pack("%ds" % BLOCKSIZE, "".join(parts))
|
buf += struct.pack("%ds" % BLOCKSIZE, "".join(parts))
|
||||||
chksum = calc_chksums(buf)[0]
|
chksum = calc_chksums(buf)[0]
|
||||||
buf = buf[:148] + "%06o\0" % chksum + buf[155:]
|
buf = buf[:-364] + "%06o\0" % chksum + buf[-357:]
|
||||||
self.buf = buf
|
self.buf = buf
|
||||||
return buf
|
return buf
|
||||||
|
|
||||||
|
def _create_gnulong(self, name, type):
|
||||||
|
"""Create a GNU longname/longlink header from name.
|
||||||
|
It consists of an extended tar header, with the length
|
||||||
|
of the longname as size, followed by data blocks,
|
||||||
|
which contain the longname as a null terminated string.
|
||||||
|
"""
|
||||||
|
name += NUL
|
||||||
|
|
||||||
|
tarinfo = self.__class__()
|
||||||
|
tarinfo.name = "././@LongLink"
|
||||||
|
tarinfo.type = type
|
||||||
|
tarinfo.mode = 0
|
||||||
|
tarinfo.size = len(name)
|
||||||
|
|
||||||
|
# create extended header
|
||||||
|
buf = tarinfo.tobuf()
|
||||||
|
# create name blocks
|
||||||
|
buf += name
|
||||||
|
blocks, remainder = divmod(len(name), BLOCKSIZE)
|
||||||
|
if remainder > 0:
|
||||||
|
buf += (BLOCKSIZE - remainder) * NUL
|
||||||
|
return buf
|
||||||
|
|
||||||
def isreg(self):
|
def isreg(self):
|
||||||
return self.type in REGULAR_TYPES
|
return self.type in REGULAR_TYPES
|
||||||
def isfile(self):
|
def isfile(self):
|
||||||
|
@ -1377,50 +1444,11 @@ class TarFile(object):
|
||||||
"""
|
"""
|
||||||
self._check("aw")
|
self._check("aw")
|
||||||
|
|
||||||
tarinfo.name = normpath(tarinfo.name)
|
tarinfo = copy.copy(tarinfo)
|
||||||
if tarinfo.isdir():
|
|
||||||
# directories should end with '/'
|
|
||||||
tarinfo.name += "/"
|
|
||||||
|
|
||||||
if tarinfo.linkname:
|
buf = tarinfo.tobuf(self.posix)
|
||||||
tarinfo.linkname = normpath(tarinfo.linkname)
|
self.fileobj.write(buf)
|
||||||
|
self.offset += len(buf)
|
||||||
if tarinfo.size > MAXSIZE_MEMBER:
|
|
||||||
if self.posix:
|
|
||||||
raise ValueError("file is too large (>= 8 GB)")
|
|
||||||
else:
|
|
||||||
self._dbg(2, "tarfile: Created GNU tar largefile header")
|
|
||||||
|
|
||||||
|
|
||||||
if len(tarinfo.linkname) > LENGTH_LINK:
|
|
||||||
if self.posix:
|
|
||||||
raise ValueError("linkname is too long (>%d)" % (LENGTH_LINK))
|
|
||||||
else:
|
|
||||||
self._create_gnulong(tarinfo.linkname, GNUTYPE_LONGLINK)
|
|
||||||
tarinfo.linkname = tarinfo.linkname[:LENGTH_LINK -1]
|
|
||||||
self._dbg(2, "tarfile: Created GNU tar extension LONGLINK")
|
|
||||||
|
|
||||||
if len(tarinfo.name) > LENGTH_NAME:
|
|
||||||
if self.posix:
|
|
||||||
prefix = tarinfo.name[:LENGTH_PREFIX + 1]
|
|
||||||
while prefix and prefix[-1] != "/":
|
|
||||||
prefix = prefix[:-1]
|
|
||||||
|
|
||||||
name = tarinfo.name[len(prefix):]
|
|
||||||
prefix = prefix[:-1]
|
|
||||||
|
|
||||||
if not prefix or len(name) > LENGTH_NAME:
|
|
||||||
raise ValueError("name is too long (>%d)" % (LENGTH_NAME))
|
|
||||||
|
|
||||||
tarinfo.name = name
|
|
||||||
tarinfo.prefix = prefix
|
|
||||||
else:
|
|
||||||
self._create_gnulong(tarinfo.name, GNUTYPE_LONGNAME)
|
|
||||||
tarinfo.name = tarinfo.name[:LENGTH_NAME - 1]
|
|
||||||
self._dbg(2, "tarfile: Created GNU tar extension LONGNAME")
|
|
||||||
|
|
||||||
self.fileobj.write(tarinfo.tobuf(self.posix))
|
|
||||||
self.offset += BLOCKSIZE
|
|
||||||
|
|
||||||
# If there's data to follow, append it.
|
# If there's data to follow, append it.
|
||||||
if fileobj is not None:
|
if fileobj is not None:
|
||||||
|
@ -1779,12 +1807,6 @@ class TarFile(object):
|
||||||
if tarinfo.isreg() and tarinfo.name.endswith("/"):
|
if tarinfo.isreg() and tarinfo.name.endswith("/"):
|
||||||
tarinfo.type = DIRTYPE
|
tarinfo.type = DIRTYPE
|
||||||
|
|
||||||
# The prefix field is used for filenames > 100 in
|
|
||||||
# the POSIX standard.
|
|
||||||
# name = prefix + '/' + name
|
|
||||||
tarinfo.name = normpath(os.path.join(tarinfo.prefix.rstrip(NUL),
|
|
||||||
tarinfo.name))
|
|
||||||
|
|
||||||
# Directory names should have a '/' at the end.
|
# Directory names should have a '/' at the end.
|
||||||
if tarinfo.isdir():
|
if tarinfo.isdir():
|
||||||
tarinfo.name += "/"
|
tarinfo.name += "/"
|
||||||
|
@ -1909,10 +1931,6 @@ class TarFile(object):
|
||||||
self.offset += self._block(tarinfo.size)
|
self.offset += self._block(tarinfo.size)
|
||||||
tarinfo.size = origsize
|
tarinfo.size = origsize
|
||||||
|
|
||||||
# Clear the prefix field so that it is not used
|
|
||||||
# as a pathname in next().
|
|
||||||
tarinfo.prefix = ""
|
|
||||||
|
|
||||||
return tarinfo
|
return tarinfo
|
||||||
|
|
||||||
#--------------------------------------------------------------------------
|
#--------------------------------------------------------------------------
|
||||||
|
@ -1970,31 +1988,6 @@ class TarFile(object):
|
||||||
else:
|
else:
|
||||||
return TarIter(self)
|
return TarIter(self)
|
||||||
|
|
||||||
def _create_gnulong(self, name, type):
|
|
||||||
"""Write a GNU longname/longlink member to the TarFile.
|
|
||||||
It consists of an extended tar header, with the length
|
|
||||||
of the longname as size, followed by data blocks,
|
|
||||||
which contain the longname as a null terminated string.
|
|
||||||
"""
|
|
||||||
name += NUL
|
|
||||||
|
|
||||||
tarinfo = TarInfo()
|
|
||||||
tarinfo.name = "././@LongLink"
|
|
||||||
tarinfo.type = type
|
|
||||||
tarinfo.mode = 0
|
|
||||||
tarinfo.size = len(name)
|
|
||||||
|
|
||||||
# write extended header
|
|
||||||
self.fileobj.write(tarinfo.tobuf())
|
|
||||||
self.offset += BLOCKSIZE
|
|
||||||
# write name blocks
|
|
||||||
self.fileobj.write(name)
|
|
||||||
blocks, remainder = divmod(tarinfo.size, BLOCKSIZE)
|
|
||||||
if remainder > 0:
|
|
||||||
self.fileobj.write(NUL * (BLOCKSIZE - remainder))
|
|
||||||
blocks += 1
|
|
||||||
self.offset += blocks * BLOCKSIZE
|
|
||||||
|
|
||||||
def _dbg(self, level, msg):
|
def _dbg(self, level, msg):
|
||||||
"""Write debugging output to sys.stderr.
|
"""Write debugging output to sys.stderr.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -90,6 +90,9 @@ Extension Modules
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Patch #1583880: fix tarfile's problems with long names and posix/
|
||||||
|
GNU modes.
|
||||||
|
|
||||||
- Fix codecs.EncodedFile which did not use file_encoding in 2.5.0, and
|
- Fix codecs.EncodedFile which did not use file_encoding in 2.5.0, and
|
||||||
fix all codecs file wrappers to work correctly with the "with"
|
fix all codecs file wrappers to work correctly with the "with"
|
||||||
statement (bug #1586513).
|
statement (bug #1586513).
|
||||||
|
|
Loading…
Reference in New Issue