Skip to content

Instantly share code, notes, and snippets.

@obfusk
Last active November 22, 2022 05:33
Show Gist options
  • Select an option

  • Save obfusk/9367ebbb887737cad35280df344c19cd to your computer and use it in GitHub Desktop.

Select an option

Save obfusk/9367ebbb887737cad35280df344c19cd to your computer and use it in GitHub Desktop.
-> https://github.com/obfusk/reproducible-apk-tools | fix META-INF/services/ newlines
#!/usr/bin/python3
# encoding: utf-8
# SPDX-FileCopyrightText: 2022 FC Stegerman <[email protected]>
# SPDX-License-Identifier: GPL-3.0-or-later
import sys
import zipfile
import zlib
from typing import Any, Dict
ATTRS = ("compress_type", "create_system", "create_version", "date_time",
"external_attr", "extract_version", "flag_bits")
LEVELS = (9, 6, 4, 1)
# FIXME: is there a better alternative?
class ReproducibleZipInfo(zipfile.ZipInfo):
"""Reproducible ZipInfo hack."""
_override: Dict[str, Any] = {}
def __init__(self, zinfo, **override): # pylint: disable=W0231
if override:
self._override = {**self._override, **override}
for k in self.__slots__:
if hasattr(zinfo, k):
setattr(self, k, getattr(zinfo, k))
def __getattribute__(self, name):
if name != "_override":
try:
return self._override[name]
except KeyError:
pass
return object.__getattribute__(self, name)
def fix_services_newlines(file_in, file_out, *, prefix="META-INF/services/",
replace=("\n", "\r\n"), verbose=False):
with zipfile.ZipFile(file_in) as zf_in:
with zipfile.ZipFile(file_out, "w") as zf_out:
for info in zf_in.infolist():
attrs = {attr: getattr(info, attr) for attr in ATTRS}
zinfo = ReproducibleZipInfo(info, **attrs)
if info.filename.startswith(prefix):
print(f"fixing {info.filename!r}...")
data = zf_in.read(info)
if info.compress_type == 8:
for lvl in LEVELS:
comp = zlib.compressobj(lvl, 8, -15)
if len(comp.compress(data) + comp.flush()) == info.compress_size:
zinfo._compresslevel = lvl
break
else:
raise RuntimeError(f"Unable to determine compresslevel for {info.filename!r}")
elif info.compress_type != 0:
raise RuntimeError(f"Unsupported compress_type {info.compress_type}")
zf_out.writestr(zinfo, data.decode().replace(*replace))
else:
if verbose:
print(f"copying {info.filename!r}...")
if info.compress_type == 8:
with zf_in.open(info) as fh_in:
comps = {lvl: zlib.compressobj(lvl, 8, -15) for lvl in LEVELS}
clens = {lvl: 0 for lvl in LEVELS}
while True:
data = fh_in.read(4096)
if not data:
break
for lvl in LEVELS:
clens[lvl] += len(comps[lvl].compress(data))
for lvl in LEVELS:
if clens[lvl] + len(comps[lvl].flush()) == info.compress_size:
zinfo._compresslevel = lvl
break
else:
raise RuntimeError(f"Unable to determine compresslevel for {info.filename!r}")
elif info.compress_type != 0:
raise RuntimeError(f"Unsupported compress_type {info.compress_type}")
with zf_in.open(info) as fh_in:
with zf_out.open(zinfo, "w") as fh_out:
while True:
data = fh_in.read(4096)
if not data:
break
fh_out.write(data)
if __name__ == "__main__":
args = sys.argv[1:]
verbose = "--verbose" in args or "-v" in args
args = [a for a in args if a not in ("--verbose", "-v")]
fix_services_newlines(*args, verbose=verbose)
# vim: set tw=80 sw=4 sts=4 et fdm=marker :
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment