Skip to content

ZipFile.open() should not ignore compression, new file should default to today rather than 1980 #121402

Open

Description

@c0xc

Bug report

Bug description:

When streaming, writing to a zip file, it internally creates a ZipInfo object without explicitly setting date_time, causing a timestamp of 1980 to be used. I believe in almost all cases, using the current timestamp makes more sense, I rarely zip files from 1980. To work around that issue, I create the ZipInfo object myself and pass it to the open() method, which causes another bug where the specified compression is ignored and the file is simply stored.

After some minor investigation, I've found issue #113971 which at least hinted towards what I now use as workaround but otherwise didn't fix this:

Today people pass ZipInfo instances into APIs that accept them such as ZipFile.writestr() in order to control how individual items are put into a zip archive as such ...
zip_info.compress_type

So you'd create a new ZipFile specifying a compression but after open(), you're just storing uncompressed:

zip_info = zipfile.ZipInfo("file")
# zip_info.compress_type, ._compresslevel are wrong
zip_file.open(zip_info, mode="w", force_zip64=True)

In other words, I expect that:
a) when using open("file"...), it should use the current time as mtime by default
b) when creating a ZipInfo object to specify another date or another setting for the new file, it should inherit the previously configured compression
Both assumptions are wrong, which is what I'm suggesting to fix.

Here's a reproducer and I'll add a PR as suggested fix...
https://gist.github.com/c0xc/b54c005b296cdf6378ce65dd4aff3fe7

CPython versions tested on:

3.11, CPython main branch

Operating systems tested on:

Linux

Linked PRs

Activity

  1. added
    type-bugAn unexpected behavior, bug, or error
    on Jul 5, 2024
  2. added a commit that references this issue on Jul 5, 2024
  3. serhiy-storchaka commented on Jul 10, 2024

    @serhiy-storchaka
    Member

    Why do you use ZipInfo instead of a string if you do not set its attributes?

  4. c0xc commented on Jul 10, 2024

    @c0xc
    Author

    Why do you use ZipInfo instead of a string if you do not set its attributes?

    That would cause the (streamed) file to be archived with a timestamp of 1980, which is the first bug.
    See my example: https://gist.github.com/c0xc/b54c005b296cdf6378ce65dd4aff3fe7#file-ztest-py-L12

  5. serhiy-storchaka commented on Jul 10, 2024

    @serhiy-storchaka
    Member

    zip uses the current timestamp when add a file from a streamed input. So I think that it is reasonable to do the same in zipfile.

    As for using the archive default compression by default, this is more complex question.

  6. c0xc commented on Jul 10, 2024

    @c0xc
    Author

    zip uses the current timestamp when add a file from a streamed input. So I think that it is reasonable to do the same in zipfile.

    Exactly and that's what my suggested change does, see:
    https://github.com/python/cpython/pull/121405/files#diff-7629293618f2b3cf8ae7daf98526226fa12f047d71645a05497e6687aae10c76R413

    As for using the archive default compression by default, this is more complex question.

    Please note that the second bug I'm trying to fix is not merely using a default compression (I don't want to change the default behavior) but what happens is that the programmer explicitly specifies the compression (line 8) which is then ignored when adding files (lines 20 and 26).

  7. obfusk commented on Aug 28, 2024

    @obfusk
    Contributor

    I believe in almost all cases, using the current timestamp makes more sense, I rarely zip files from 1980.

    Unfortunately, embedded timestamps are the most common problem for Reproducible Builds and changing this default might have unintended consequences.

  8. added
    stdlibStandard Library Python modules in the Lib/ directory
    on Aug 29, 2024
  9. thatch commented on May 20, 2025

    @thatch
    Contributor

    I'm a fan of the logic in writestr, would using that when a str filename is passed would satisfy this request I think.

    if isinstance(zinfo_or_arcname, ZipInfo):
    zinfo = zinfo_or_arcname
    else:
    zinfo = ZipInfo(zinfo_or_arcname)._for_archive(self)

    1. Defaults to current date unless SOURCE_DATE_EPOCH is set
    2. Inherits compression setting from archive
  10. danny0838 commented on Jun 23, 2025

    @danny0838
    Contributor

    @thatch

    I'm a fan of the logic in writestr, would using that when a str filename is passed would satisfy this request I think.

    +1 to this approach. This will make the patch as simple as:

    --- a/Lib/zipfile/__init__.py
    +++ b/Lib/zipfile/__init__.py
    @@ -1710,9 +1710,7 @@ def open(self, name, mode="r", pwd=None, *, force_zip64=False):
                 # 'name' is already an info object
                 zinfo = name
             elif mode == 'w':
    -            zinfo = ZipInfo(name)
    -            zinfo.compress_type = self.compression
    -            zinfo.compress_level = self.compresslevel
    +            zinfo = ZipInfo(name)._for_archive(self)
             else:
                 # Get info object for name
                 zinfo = self.getinfo(name)

    However the problem is that it breaks backward compatibility and I don't know if it worth. If it's not to be implemented due to backward compatibility, the behavior differences when passing a name should at least be documented.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or error

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions