Python2 和 Python3 之间的 zipfile 标头语言编码位设置不同

问题描述 投票:0回答:3

我希望这段代码在使用 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:语言编码。 BIT11 似乎已设置
if filename.encode("ascii")
抛出。

我尝试在创建 ZipInfo 对象后通过设置标志来强制启用此位,但它会重置回

0x00
中的
_open_to_write()

不知道这里有没有人有好的解决办法。理想情况下,我希望两个输出都设置标志,因为这反映了 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 python-2.7 python-3.7 python-zipfile
3个回答
1
投票

编辑: 下面的代码适用于 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
)。


0
投票

我暂时使用这样的东西:

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() 似乎将来不会改变。


0
投票

就我而言,如果这样做,您将获得正确的编码:

import zipfile

with zipfile.ZipFile("./your_file.zip", "r") as zf:
    file_list = [file.encode("cp437").decode("cp850") for file in zf.namelist()]
file_list
© www.soinside.com 2019 - 2024. All rights reserved.