[英]zipfile header language encoding bit set differently between Python2 and Python3
我想在使用Python 2或Python 3运行时,此代码的工作方式相同
from zipfile import ZipFile, ZipInfo
with ZipFile("out.zip", 'w') as zf:
content = "content"
info = ZipInfo()
info.filename = "file.txt"
info.flag_bits = 0x800
info.file_size = len(content)
zf.writestr(info, content)
但是,根据Python 2 out.zip开始:
50 4b 03 04 14 00 00 08
在Python3下,它开始:
50 4b 03 04 14 00 00 00
不同的部分是flag_bits
,对于Python 2设置为0x800
,对于Python 3设置为0x00
。这是BIT11:语言编码。 if filename.encode("ascii")
抛出,BIT11似乎被设置。
我试图通过在创建ZipInfo对象后设置标志来强制启用此位,但它会在_open_to_write()
重置为0x00
。
我想知道这里有没有人有一个好的解决方案。 理想情况下,我希望两个输出都设置标志,因为这反映了jar实用程序的功能。
编辑:更新以添加info.flag_bits = 0x800
行只是为了说明我想要实现的目标。 我在Windows上重现了这个:ActivePython 3.6.0.3600,与ActivePython 2.7.14.2717,Windows 10相比。在Linux上:Python 3.6.6与Python 2.7.11如果重要的话,我正在运行这个我的例子,没有hashbang,直接调用解释器:
pythonX test.py
编辑:这里的代码适用于我的Python 2.7但不适用于3.6(有点神秘,它似乎在今晚更早的工作):
$ cat zipf.py
from __future__ import print_function
from zipfile import ZipFile, ZipInfo
with ZipFile("out.zip", 'w') as zf:
content = "content"
info = ZipInfo()
info.filename = "file.txt"
info.flag_bits = 0x800
# don't set info.file_size here: zf.writestr() does that
zf.writestr(info, content)
with open('out.zip', 'rb') as stream:
byteseq = stream.read(8)
for i in byteseq:
if isinstance(i, str): i = ord(i)
print('{:02x}'.format(i), end=' ')
print()
运行方式:
$ python2.7 zipf.py
50 4b 03 04 14 00 00 08
但:
$ python3.6 zipf.py
50 4b 03 04 14 00 00 00
通过确保在创建info
条目之前打开文件,当然可以使其工作。 但是,你必须避免使用writestr
,这只适用于Python 3.6(并且看起来相当滥用):
from __future__ import print_function
from zipfile import ZipFile, ZipInfo
with ZipFile("out.zip", 'w') as zf:
info = ZipInfo()
info.filename = "file.txt"
content = "content"
if not isinstance(content, bytes):
content = content.encode('utf8')
info.file_size = len(content)
with zf.open(info, 'w') as stream:
info.flag_bits = 0x800
stream.write(content)
with open('out.zip', 'rb') as stream:
byteseq = stream.read(8)
for i in byteseq:
if isinstance(i, str): i = ord(i)
print('{:02x}'.format(i), end=' ')
print()
这可能是3.6重置所有info.flag_bits
(通过内部open
它)的错误,虽然对我来说并不是很清楚。
我无法重现这一点,但如果文件名是Unicode并且编码为ASCII失败,则设置标志位中的位11是正确的:
def _encodeFilenameFlags(self):
if isinstance(self.filename, unicode):
try:
return self.filename.encode('ascii'), self.flag_bits
except UnicodeEncodeError:
return self.filename.encode('utf-8'), self.flag_bits | 0x800
else:
return self.filename, self.flag_bits
(Python 2.7 zipfile.py源代码)或:
def _encodeFilenameFlags(self):
try:
return self.filename.encode('ascii'), self.flag_bits
except UnicodeEncodeError:
return self.filename.encode('utf-8'), self.flag_bits | 0x800
(Python 3.6 zipfile.py源代码)。
要获得该位设置,您需要一个不能直接用ASCII编码的文件名,例如:
info.filename = u"sch\N{latin small letter o with diaeresis}n" # "file.txt"
(这种表示法适用于Python 2.7和3.6)。
我试图通过在创建ZipInfo对象后设置标志来强制启用此位,但它会在_open_to_write()中重置为0x00。
如果我添加:
info.filename = "file.txt"
info.flag_bits |= 0x0800
(在将文件名设置为u"schön"
)并在Python 2.7或3.6下运行,我在标题中设置了位(当然zip目录中的文件名更改回file.txt
)。
我暂时使用这样的东西:
from zipfile import ZipFile, ZipInfo
import struct
orig_function = ZipInfo.FileHeader
def new_function(self, zip64=None):
header = orig_function(self, zip64)
fmt = "B"*len(header)
blist = list(struct.unpack(fmt, header))
blist[7] |= 0x8
return struct.pack(fmt, *blist)
setattr(ZipInfo, "FileHeader", new_function)
with ZipFile("out.zip", 'w') as zf:
content = "content"
info = ZipInfo()
info.filename = "file.txt"
info.file_size = len(content)
zf.writestr(info, content)
希望它不会太快破坏,FileHeader()似乎是将来不会改变的东西。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.