It seems I have tracked the origin of the problem.
When a browser receives an invalid ZIP file, it is actually a GZIP-compressed ZIP file. gunzip it, and then you can unzip it too.
What causes it is of course mod_deflate, since it's the only part of the site that may cause gzip-compression. It is intended to save bandwidth.
So why is it broken? I suspect it's a problem in the browsers. They claim they can decompress stuff in transmission, but when it's a downloadable file, they forget to decompress it before saving. Some browsers.
So how to fix it? DeHackEd, help!
This is my /etc/apache2/mods-available/deflate.conf:
<Location />
# Insert filter
SetOutputFilter DEFLATE
SetInputFilter DEFLATE
# Netscape 4.x has some problems...
BrowserMatch ^Mozilla/4 gzip-only-text/html
# Netscape 4.06-4.08 have some more problems
BrowserMatch ^Mozilla/4\.0[678] no-gzip
# MSIE masquerades as Netscape, but it is fine
# BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
# NOTE: Due to a bug in mod_setenvif up to Apache 2.0.48
# the above regex won't work. You can use the following
# workaround to get the desired effect:
BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html
# Don't compress images
SetEnvIfNoCase Request_URI \
\.(?:gif|jpe?g|png)$ no-gzip dont-vary
# Make sure proxies don't deliver the wrong content
Header append Vary User-Agent env=!dont-vary
</Location>
DeflateCompressionLevel 2
For comparison, this was my /etc/apache/gzip.conf (mod_gzip, which I used in Apache 1.3, is not available in Apache 2.0, which I use now):
<IfModule mod_gzip.c>
mod_gzip_on Yes
mod_gzip_can_negotiate No
mod_gzip_static_suffix .gz
AddEncoding gzip .gz
mod_gzip_update_static No
mod_gzip_command_version '/mod_gzip_status'
mod_gzip_temp_dir /tmp
mod_gzip_keep_workfiles No
mod_gzip_minimum_file_size 500
mod_gzip_maximum_file_size 800000
mod_gzip_maximum_inmem_size 200000
mod_gzip_min_http 1000
mod_gzip_handle_methods GET POST
mod_gzip_item_exclude reqheader "User-agent: Mozilla/4.0[678]"
mod_gzip_item_include file \.htm$
mod_gzip_item_include file \.html$
mod_gzip_item_include file \.js$
mod_gzip_item_include file \.css$
mod_gzip_item_include file \.php.*
mod_gzip_item_include file \.xml$
mod_gzip_item_include file \.pl$
mod_gzip_item_include handler ^cgi-script$
mod_gzip_item_include mime ^text/.*
mod_gzip_item_include mime ^httpd/unix-directory$
mod_gzip_item_include mime ^application/x-javascript$
mod_gzip_item_exclude file pushrefresh
mod_gzip_item_exclude file spamref
mod_gzip_item_exclude mime ^image/
mod_gzip_dechunk Yes
LogFormat "%h %l %u %t \"%V %r\" %<s %b mod_gzip: %{mod_gzip_result}n In:%{mod_gzip_input_size}n -< Out:%{mod_gzip_output_size}n = %{mod_gzip_compression_ratio}n pct." common_with_mod_gzip_info2
mod_gzip_add_header_count Yes
mod_gzip_send_vary Yes
</IfModule>