Last active
November 22, 2022 05:33
-
-
Save obfusk/9367ebbb887737cad35280df344c19cd to your computer and use it in GitHub Desktop.
-> https://github.com/obfusk/reproducible-apk-tools | fix META-INF/services/ newlines
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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