Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-acefile for openSUSE:Factory checked in at 2026-01-22 17:59:03 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-acefile (Old) and /work/SRC/openSUSE:Factory/.python-acefile.new.1928 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-acefile" Thu Jan 22 17:59:03 2026 rev:7 rq:1328692 version:0.6.14 Changes: -------- --- /work/SRC/openSUSE:Factory/python-acefile/python-acefile.changes 2025-06-17 18:21:39.420896388 +0200 +++ /work/SRC/openSUSE:Factory/.python-acefile.new.1928/python-acefile.changes 2026-01-22 17:59:12.939157357 +0100 @@ -1,0 +2,10 @@ +Thu Jan 22 13:51:11 UTC 2026 - Dirk Müller <[email protected]> + +- update to 0.6.14: + * Experimental support for free-threaded Python. + * Fix memory leak in acebitstream module. + * Add pytest smoke tests and configure GitHub Actions CI for + testing. + * Remove deprecated setup.py features. + +------------------------------------------------------------------- Old: ---- acefile-0.6.13.tar.gz New: ---- acefile-0.6.14.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-acefile.spec ++++++ --- /var/tmp/diff_new_pack.g1Ow0g/_old 2026-01-22 17:59:13.555183055 +0100 +++ /var/tmp/diff_new_pack.g1Ow0g/_new 2026-01-22 17:59:13.559183222 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-acefile # -# Copyright (c) 2025 SUSE LLC +# Copyright (c) 2026 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -19,7 +19,7 @@ %define skip_python2 1 %bcond_without libalternatives Name: python-acefile -Version: 0.6.13 +Version: 0.6.14 Release: 0 Summary: ACE 1.0 and 2.0 archive reader/extractor in pure Python License: BSD-2-Clause ++++++ acefile-0.6.13.tar.gz -> acefile-0.6.14.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acefile-0.6.13/.github/workflows/test.yml new/acefile-0.6.14/.github/workflows/test.yml --- old/acefile-0.6.13/.github/workflows/test.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/acefile-0.6.14/.github/workflows/test.yml 2026-01-21 00:19:33.000000000 +0100 @@ -0,0 +1,108 @@ +name: Test + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.13t", "3.14", "3.14t"] + + steps: + + - uses: actions/checkout@v6 + with: + path: acefile + + - uses: actions/checkout@v6 + with: + repository: droe/acefile-testdata + path: testdata + + - name: "Set up Python ${{ matrix.python-version }}" + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: "Install dependencies" + shell: bash + run: | + python -m pip install --upgrade pip + if [ -f acefile/requirements.txt ]; then pip install -r acefile/requirements.txt; fi + if [ -f acefile/requirements-dev.txt ]; then pip install -r acefile/requirements-dev.txt; fi + + - name: "Display Python version" + shell: bash + run: | + python -c "import sys; print(sys.version)" + + - name: "Build acebitstream C module" + shell: bash + run: | + cd acefile && python setup.py build_ext --inplace + + + # doctest variants + + - name: "Run doctest, with acebitstream, free-threaded" + if: ${{ endsWith(matrix.python-version, 't') }} + shell: bash + run: | + cd acefile && ACEFILE_FORCE_ACEBITSTREAM=1 python -X gil=0 acefile.py --doctest -v + + - name: "Run doctest, with acebitstream, with GIL" + if: ${{ endsWith(matrix.python-version, 't') }} + shell: bash + run: | + cd acefile && ACEFILE_FORCE_ACEBITSTREAM=1 python -X gil=1 acefile.py --doctest -v + + - name: "Run doctest, with acebitstream" + if: ${{ ! endsWith(matrix.python-version, 't') }} + shell: bash + run: | + cd acefile && ACEFILE_FORCE_ACEBITSTREAM=1 python acefile.py --doctest -v + + - name: "Run doctest, without acebitstream, free-threaded" + if: ${{ endsWith(matrix.python-version, 't') }} + shell: bash + run: | + cd acefile && ACEFILE_FORCE_ACEBITSTREAM=0 python -X gil=0 acefile.py --doctest -v + + - name: "Run doctest, without acebitstream, with GIL" + if: ${{ endsWith(matrix.python-version, 't') }} + shell: bash + run: | + cd acefile && ACEFILE_FORCE_ACEBITSTREAM=0 python -X gil=1 acefile.py --doctest -v + + - name: "Run doctest, without acebitstream" + if: ${{ ! endsWith(matrix.python-version, 't') }} + shell: bash + run: | + cd acefile && ACEFILE_FORCE_ACEBITSTREAM=0 python acefile.py --doctest -v + + + # pytest variants without GIL variants for now + + - name: "Run pytest, with acebitstream" + shell: bash + run: | + cd acefile && ACEFILE_FORCE_ACEBITSTREAM=1 pytest -v + + - name: "Run pytest, without acebitstream" + shell: bash + run: | + cd acefile && ACEFILE_FORCE_ACEBITSTREAM=0 pytest -v + + + # build documentation + + - name: "Build apidocs" + shell: bash + run: | + cd acefile/apidoc && sphinx-build -W . _build diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acefile-0.6.13/GNUmakefile new/acefile-0.6.14/GNUmakefile --- old/acefile-0.6.13/GNUmakefile 2019-02-26 21:22:06.000000000 +0100 +++ new/acefile-0.6.14/GNUmakefile 2026-01-19 12:59:18.000000000 +0100 @@ -30,9 +30,10 @@ doctest: $(PYTHON) acefile.py --doctest + $(PYTHON) tests/test_smoke.py --doctest -test: - $(PYTHON) setup.py test +test: doctest + pytest -v build: $(PYTHON) setup.py build_ext --inplace @@ -47,5 +48,5 @@ todo: egrep -r 'XXX|TODO|FIXME' *.py -.PHONY: all apidoc dist sign upload doctest test build clean todo +.PHONY: all apidoc dist sign upload doctest build clean todo diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acefile-0.6.13/LICENSE.md new/acefile-0.6.14/LICENSE.md --- old/acefile-0.6.13/LICENSE.md 2024-11-09 01:16:39.000000000 +0100 +++ new/acefile-0.6.14/LICENSE.md 2026-01-18 02:00:10.000000000 +0100 @@ -2,7 +2,7 @@ ## Copyright -Copyright (c) 2017-2024, Daniel Roethlisberger and contributors. +Copyright (c) 2017-2026, Daniel Roethlisberger and contributors. All rights reserved. Licensed under the 2-clause BSD license contained herein. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acefile-0.6.13/NEWS.md new/acefile-0.6.14/NEWS.md --- old/acefile-0.6.13/NEWS.md 2024-11-09 01:15:52.000000000 +0100 +++ new/acefile-0.6.14/NEWS.md 2026-01-21 00:30:30.000000000 +0100 @@ -1,3 +1,11 @@ +### acefile 0.6.14 2026-01-21 + +- Experimental support for free-threaded Python. +- Fix memory leak in acebitstream module. +- Add pytest smoke tests and configure GitHub Actions CI for testing. +- Remove deprecated setup.py features. + + ### acefile 0.6.13 2024-11-09 - Rephrase setup failure notices for clarity. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acefile-0.6.13/PKG-INFO new/acefile-0.6.14/PKG-INFO --- old/acefile-0.6.13/PKG-INFO 2024-11-09 01:26:55.703423700 +0100 +++ new/acefile-0.6.14/PKG-INFO 2026-01-21 00:33:21.405675200 +0100 @@ -1,15 +1,14 @@ -Metadata-Version: 2.1 +Metadata-Version: 2.4 Name: acefile -Version: 0.6.13 +Version: 0.6.14 Summary: Read/test/extract ACE 1.0 and 2.0 archives in pure python. Home-page: https://www.roe.ch/acefile Author: Daniel Roethlisberger Author-email: [email protected] -License: BSD +License: BSD-2-Clause Keywords: ace,unace,compression,decompression,archive Platform: all Classifier: Development Status :: 5 - Production/Stable -Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 @@ -22,8 +21,19 @@ Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 Classifier: Topic :: System :: Archiving :: Compression License-File: LICENSE.md +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: home-page +Dynamic: keywords +Dynamic: license +Dynamic: license-file +Dynamic: platform +Dynamic: summary This single-file, pure python 3, no-dependencies implementation is intended to be used as a library, but also provides a stand-alone unace utility. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acefile-0.6.13/README.md new/acefile-0.6.14/README.md --- old/acefile-0.6.13/README.md 2024-11-09 01:18:11.000000000 +0100 +++ new/acefile-0.6.14/README.md 2026-01-21 00:04:28.000000000 +0100 @@ -1,5 +1,5 @@ # acefile - read/test/extract ACE 1.0 and 2.0 archives in pure python -Copyright (C) 2017-2024, [Daniel Roethlisberger](//daniel.roe.ch/). +Copyright (C) 2017-2026, [Daniel Roethlisberger](//daniel.roe.ch/). https://www.roe.ch/acefile @@ -116,6 +116,24 @@ acefile-unace -h +## Testing + +This project uses docstrings for unit testing: + + ./acefile.py --doctest + +This project uses pytest for integration testing, recursively looking for +corpora of ACE archives in `../acefile-testdata/` and +`../acefile-testdata-private/`. The former is intended for a checkout of +[droe/acefile-testdata](https://github.com/droe/acefile-testdata). The latter +is intended for a developer-local test corpus that might be impractically large +or contain data that cannot be redistributed. Both are optional, the +integration tests are currently not useful without any test archives though. + + git clone https://github.com/droe/acefile-testdata.git ../acefile-testdata + pytest -v + + ## Credits Marcel Lemke for designing the ACE archive format and ACE compression and diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acefile-0.6.13/acefile.egg-info/PKG-INFO new/acefile-0.6.14/acefile.egg-info/PKG-INFO --- old/acefile-0.6.13/acefile.egg-info/PKG-INFO 2024-11-09 01:26:55.000000000 +0100 +++ new/acefile-0.6.14/acefile.egg-info/PKG-INFO 2026-01-21 00:33:21.000000000 +0100 @@ -1,15 +1,14 @@ -Metadata-Version: 2.1 +Metadata-Version: 2.4 Name: acefile -Version: 0.6.13 +Version: 0.6.14 Summary: Read/test/extract ACE 1.0 and 2.0 archives in pure python. Home-page: https://www.roe.ch/acefile Author: Daniel Roethlisberger Author-email: [email protected] -License: BSD +License: BSD-2-Clause Keywords: ace,unace,compression,decompression,archive Platform: all Classifier: Development Status :: 5 - Production/Stable -Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 @@ -22,8 +21,19 @@ Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 Classifier: Topic :: System :: Archiving :: Compression License-File: LICENSE.md +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: home-page +Dynamic: keywords +Dynamic: license +Dynamic: license-file +Dynamic: platform +Dynamic: summary This single-file, pure python 3, no-dependencies implementation is intended to be used as a library, but also provides a stand-alone unace utility. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acefile-0.6.13/acefile.egg-info/SOURCES.txt new/acefile-0.6.14/acefile.egg-info/SOURCES.txt --- old/acefile-0.6.13/acefile.egg-info/SOURCES.txt 2024-11-09 01:26:55.000000000 +0100 +++ new/acefile-0.6.14/acefile.egg-info/SOURCES.txt 2026-01-21 00:33:21.000000000 +0100 @@ -5,8 +5,10 @@ NEWS.md README.md acefile.py +requirements-dev.txt requirements.txt setup.py +.github/workflows/test.yml acefile.egg-info/PKG-INFO acefile.egg-info/SOURCES.txt acefile.egg-info/dependency_links.txt @@ -15,6 +17,9 @@ apidoc/GNUmakefile apidoc/conf.py apidoc/index.rst +apidoc/_static/.gitkeep c/acebitstream.c c/acebitstream.h -c/acebitstream_mod.c \ No newline at end of file +c/acebitstream_mod.c +tests/conftest.py +tests/test_smoke.py \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acefile-0.6.13/acefile.py new/acefile-0.6.14/acefile.py --- old/acefile-0.6.13/acefile.py 2024-11-09 01:20:20.000000000 +0100 +++ new/acefile-0.6.14/acefile.py 2026-01-21 00:29:30.000000000 +0100 @@ -1,8 +1,7 @@ #!/usr/bin/env python3 -# vim: set list et ts=8 sts=4 sw=4 ft=python: # acefile - read/test/extract ACE 1.0 and 2.0 archives in pure python -# Copyright (C) 2017-2024, Daniel Roethlisberger <[email protected]> +# Copyright (C) 2017-2026, Daniel Roethlisberger <[email protected]> # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -55,12 +54,12 @@ - https://infosec.exchange/@droe """ -__version__ = '0.6.13' +__version__ = '0.6.14' __author__ = 'Daniel Roethlisberger' __email__ = '[email protected]' -__copyright__ = 'Copyright 2017-2024, Daniel Roethlisberger' +__copyright__ = 'Copyright 2017-2026, Daniel Roethlisberger' __credits__ = ['Marcel Lemke'] -__license__ = 'BSD' +__license__ = 'BSD-2-Clause' __url__ = 'https://www.roe.ch/acefile' @@ -80,10 +79,18 @@ import sys import zlib -try: + + +_ACEFILE_FORCE_ACEBITSTREAM = os.environ.get('ACEFILE_FORCE_ACEBITSTREAM', None) +if _ACEFILE_FORCE_ACEBITSTREAM == '1': import acebitstream -except: +elif _ACEFILE_FORCE_ACEBITSTREAM == '0': acebitstream = None +else: + try: + import acebitstream + except: + acebitstream = None @@ -2812,29 +2819,29 @@ want to ignore the filenames in the archive headers and instead generate your own filename for each archive member. - >>> AceMember._sanitize_filename(b'a.exe\\0b.txt') + >>> AceMember._sanitize_filename(b'a.exe\\0b.txt').replace('\\\\', '/') 'a.exe' - >>> AceMember._sanitize_filename(b'\\\\etc\\\\foo/bar\\\\baz.txt') + >>> AceMember._sanitize_filename(b'\\\\etc\\\\foo/bar\\\\baz.txt').replace('\\\\', '/') 'etc/foo/bar/baz.txt' - >>> AceMember._sanitize_filename(b'a/b/../b/.//.././c/.//../d/file.txt') + >>> AceMember._sanitize_filename(b'a/b/../b/.//.././c/.//../d/file.txt').replace('\\\\', '/') 'a/d/file.txt' - >>> AceMember._sanitize_filename(b'/etc/passwd') + >>> AceMember._sanitize_filename(b'/etc/passwd').replace('\\\\', '/') 'etc/passwd' - >>> AceMember._sanitize_filename(b'.././.././.././.././../etc/passwd') + >>> AceMember._sanitize_filename(b'.././.././.././.././../etc/passwd').replace('\\\\', '/') 'etc/passwd' - >>> AceMember._sanitize_filename(b'C:\\\\Windows\\\\foo.exe') + >>> AceMember._sanitize_filename(b'C:\\\\Windows\\\\foo.exe').replace('\\\\', '/') 'C/Windows/foo.exe' - >>> AceMember._sanitize_filename(b'\\\\\\\\server\\\\share\\\\file') + >>> AceMember._sanitize_filename(b'\\\\\\\\server\\\\share\\\\file').replace('\\\\', '/') 'server/share/file' - >>> AceMember._sanitize_filename(b'\\\\\\\\.\\\\CdRom0') + >>> AceMember._sanitize_filename(b'\\\\\\\\.\\\\CdRom0').replace('\\\\', '/') 'CdRom0' - >>> AceMember._sanitize_filename(b'\\\\\\\\?\\\\raw\\\\path') + >>> AceMember._sanitize_filename(b'\\\\\\\\?\\\\raw\\\\path').replace('\\\\', '/') 'raw/path' - >>> AceMember._sanitize_filename(b'hello\x05world') + >>> AceMember._sanitize_filename(b'hello\x05world').replace('\\\\', '/') 'helloworld' - >>> AceMember._sanitize_filename(b'.././.././.././.././../etc/../') + >>> AceMember._sanitize_filename(b'.././.././.././.././../etc/../').replace('\\\\', '/') '' - >>> AceMember._sanitize_filename(b'c:\\\\c:\\\\CVE-2018-20250\\\\p.lnk') + >>> AceMember._sanitize_filename(b'c:\\\\c:\\\\CVE-2018-20250\\\\p.lnk').replace('\\\\', '/') 'c/c/CVE-2018-20250/p.lnk' """ filename = filename.decode('utf-8', errors='replace') @@ -2847,15 +2854,17 @@ filename = filename.replace('/', os.sep) if os.sep != '\\': filename = filename.replace('\\', os.sep) - # eliminate characters illegal on some platforms, including some - # characters that are relevant for path traversal attacks (i.e. colon) + # Eliminate characters illegal on some platforms, including some + # characters that are relevant for path traversal attacks (i.e. colon). filename = filename.translate(AceMember.TRANSLATION_TAB) - # first eliminate all /./, foo/../ and similar sequences, then remove - # all remaining .. labels in order to avoid path traversal attacks but - # still allow a safe subset of dot syntax in the filename + # First eliminate all /./, foo/../ and similar sequences, then remove + # all remaining . and .. labels in order to avoid path traversal + # attacks but still allow a safe subset of dot syntax in the filename. + # The exact behaviour of normpath differs between POSIX and Windows, so + # the regex also serves as a second line of defense. filename = os.path.normpath(filename) escsep = re.escape(os.sep) - pattern = r'(^|%s)(?:\.\.(?:%s|$))+' % (escsep, escsep) + pattern = r'(^|%s)(?:\.\.?(?:%s|$))+' % (escsep, escsep) filename = re.sub(pattern, r'\1', filename) # remove leading path separators to ensure a relative path filename = filename.lstrip(os.sep) @@ -4353,6 +4362,8 @@ import doctest return doctest.DocTestSuite(optionflags=doctest.IGNORE_EXCEPTION_DETAIL) + + def test(): import doctest fails, tests = doctest.testmod(optionflags=doctest.IGNORE_EXCEPTION_DETAIL) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acefile-0.6.13/apidoc/GNUmakefile new/acefile-0.6.14/apidoc/GNUmakefile --- old/acefile-0.6.13/apidoc/GNUmakefile 2017-07-11 22:09:12.000000000 +0200 +++ new/acefile-0.6.14/apidoc/GNUmakefile 2026-01-21 00:17:03.000000000 +0100 @@ -1,15 +1,11 @@ SPHINXOPTS = SPHINXBUILD = sphinx-build -SPHINXPROJ = acefile SOURCEDIR = . BUILDDIR = _build -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - all: html -%: GNUmakefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) +html: + @$(SPHINXBUILD) "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) -.PHONY: help all GNUmakefile +.PHONY: all html diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acefile-0.6.13/apidoc/conf.py new/acefile-0.6.14/apidoc/conf.py --- old/acefile-0.6.13/apidoc/conf.py 2017-07-16 21:03:49.000000000 +0200 +++ new/acefile-0.6.14/apidoc/conf.py 2026-01-18 16:12:15.000000000 +0100 @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# vim: set list et ts=8 sts=4 sw=4 ft=python: import os import sys diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acefile-0.6.13/apidoc/index.rst new/acefile-0.6.14/apidoc/index.rst --- old/acefile-0.6.13/apidoc/index.rst 2018-01-02 23:47:09.000000000 +0100 +++ new/acefile-0.6.14/apidoc/index.rst 2026-01-20 01:18:22.000000000 +0100 @@ -22,6 +22,9 @@ See :class:`acefile.AceArchive` and :class:`acefile.AceMember` for the complete descriptions of the methods supported by these two classes. +On free-threaded Python, :mod:`acefile` is only thread-safe as long as only a +single thread operates on any given :class:`acefile.AceArchive` at a time. + Functions ~~~~~~~~~ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acefile-0.6.13/c/acebitstream.c new/acefile-0.6.14/c/acebitstream.c --- old/acefile-0.6.13/c/acebitstream.c 2024-11-09 01:18:42.000000000 +0100 +++ new/acefile-0.6.14/c/acebitstream.c 2026-01-20 00:42:37.000000000 +0100 @@ -1,6 +1,6 @@ /* * acefile - read/test/extract ACE 1.0 and 2.0 archives in pure python - * Copyright (C) 2017-2024, Daniel Roethlisberger <[email protected]> + * Copyright (C) 2017-2026, Daniel Roethlisberger <[email protected]> * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -62,8 +62,9 @@ assert(ctx->bitcount <= 32); if (ctx->bufptr == ctx->bufend) { acebitstream_refill_buf(ctx); - if (ctx->bufptr == ctx->bufend) + if (ctx->bufptr == ctx->bufend) { return; + } } ctx->bits |= ((uint64_t)*ctx->bufptr) << (32 - ctx->bitcount); ctx->bitcount += 32; @@ -76,8 +77,9 @@ acebitstream_ctx_t *ctx; ctx = malloc(sizeof(acebitstream_ctx_t)); - if (!ctx) + if (!ctx) { return NULL; + } memset(ctx, 0, sizeof(acebitstream_ctx_t)); ctx->read = read; @@ -115,8 +117,9 @@ assert(n > 0 && n < 32); if (ctx->bitcount < n) { acebitstream_refill_bits(ctx); - if (ctx->bitcount < n) + if (ctx->bitcount < n) { return ACEBITSTREAM_EOF; + } } ctx->bits <<= n; ctx->bitcount -= n; @@ -130,8 +133,9 @@ value = acebitstream_peek_bits(ctx, n); rv = acebitstream_skip_bits(ctx, n); - if (rv == ACEBITSTREAM_EOF) + if (rv == ACEBITSTREAM_EOF) { return rv; + } return value; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acefile-0.6.13/c/acebitstream.h new/acefile-0.6.14/c/acebitstream.h --- old/acefile-0.6.13/c/acebitstream.h 2024-11-09 01:18:50.000000000 +0100 +++ new/acefile-0.6.14/c/acebitstream.h 2026-01-18 02:01:40.000000000 +0100 @@ -1,6 +1,6 @@ /* * acefile - read/test/extract ACE 1.0 and 2.0 archives in pure python - * Copyright (C) 2017-2024, Daniel Roethlisberger <[email protected]> + * Copyright (C) 2017-2026, Daniel Roethlisberger <[email protected]> * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acefile-0.6.13/c/acebitstream_mod.c new/acefile-0.6.14/c/acebitstream_mod.c --- old/acefile-0.6.13/c/acebitstream_mod.c 2024-11-09 01:19:00.000000000 +0100 +++ new/acefile-0.6.14/c/acebitstream_mod.c 2026-01-20 00:47:08.000000000 +0100 @@ -1,6 +1,6 @@ /* * acefile - read/test/extract ACE 1.0 and 2.0 archives in pure python - * Copyright (C) 2017-2024, Daniel Roethlisberger <[email protected]> + * Copyright (C) 2017-2026, Daniel Roethlisberger <[email protected]> * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,17 +49,22 @@ PyObject *f = ctx; method = PyObject_GetAttrString(f, (char*)"read"); - if (method == NULL) + if (method == NULL) { return 0; + } arglist = Py_BuildValue("(k)", n); - if (arglist == NULL) + if (arglist == NULL) { + Py_DECREF(method); return 0; + } buffer = PyObject_CallObject(method, arglist); + Py_DECREF(method); Py_DECREF(arglist); - if (buffer == NULL) + if (buffer == NULL) { return 0; + } ret = PyBytes_Size(buffer); - if (ret % 4) { + if (ret % 4 != 0) { Py_DECREF(buffer); PyErr_SetString(PyExc_ValueError, "Truncated 32-bit word from file-like object"); @@ -107,8 +112,9 @@ BitStream *self; self = (BitStream*)type->tp_alloc(type, 0); - if (self == NULL) + if (self == NULL) { return NULL; + } self->ctx = NULL; return (PyObject*)self; } @@ -120,12 +126,16 @@ PyObject *f; size_t bufsz = 131072; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|I", kwlist, &f, &bufsz)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|I", kwlist, &f, &bufsz)) { return -1; + } + Py_INCREF(f); self->ctx = acebitstream_new(filelike_read, f, bufsz); - if (PyErr_Occurred()) + if (PyErr_Occurred()) { + /* filelike_read raised an exception */ return -1; + } return 0; } @@ -135,8 +145,9 @@ size_t ret; unsigned int n = 0; - if (!PyArg_ParseTuple(args, "I", &n)) + if (!PyArg_ParseTuple(args, "I", &n)) { return NULL; + } if (n > 31) { PyErr_SetString(PyExc_ValueError, "Cannot peek more than 31 bits"); @@ -144,8 +155,11 @@ } ret = acebitstream_peek_bits(self->ctx, n); - if (PyErr_Occurred()) + if (PyErr_Occurred()) { + /* filelike_read raised an exception */ return NULL; + } + /* peek_bits never returns ACEBITSTREAM_EOF */ return PyLong_FromLong(ret); } @@ -155,8 +169,9 @@ size_t ret; unsigned int n = 0; - if (!PyArg_ParseTuple(args, "I", &n)) + if (!PyArg_ParseTuple(args, "I", &n)) { return NULL; + } if (n > 31) { PyErr_SetString(PyExc_ValueError, "Cannot skip more than 31 bits"); @@ -164,8 +179,10 @@ } ret = acebitstream_skip_bits(self->ctx, n); - if (PyErr_Occurred()) + if (PyErr_Occurred()) { + /* filelike_read raised an exception */ return NULL; + } if (ret == ACEBITSTREAM_EOF) { PyErr_SetString(PyExc_EOFError, "Cannot skip bits beyond EOF"); @@ -181,8 +198,9 @@ size_t ret; unsigned int n = 0; - if (!PyArg_ParseTuple(args, "I", &n)) + if (!PyArg_ParseTuple(args, "I", &n)) { return NULL; + } if (n > 31) { PyErr_SetString(PyExc_ValueError, "Cannot read more than 31 bits"); @@ -190,8 +208,10 @@ } ret = acebitstream_read_bits(self->ctx, n); - if (PyErr_Occurred()) + if (PyErr_Occurred()) { + /* filelike_read raised an exception */ return NULL; + } if (ret == ACEBITSTREAM_EOF) { PyErr_SetString(PyExc_EOFError, "Cannot read bits beyond EOF"); @@ -261,17 +281,28 @@ { PyObject *module; - BitStreamType.tp_new = PyType_GenericNew; - if (PyType_Ready(&BitStreamType) < 0) + if (PyType_Ready(&BitStreamType) < 0) { return NULL; + } module = PyModule_Create(&acebitstream_module); - if (module == NULL) + if (module == NULL) { return NULL; + } + +#ifdef Py_GIL_DISABLED + /* + * There are no critical sections, because the premise is that + * decompression from the same file-like object is inherently not + * thread-safe. Decompressing archive members from the same non-solid + * archive in parallel would need to be made thread-safe at a higher + * layer than reading bits from a file-like object. + */ + PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED); +#endif Py_INCREF(&BitStreamType); PyModule_AddObject(module, "BitStream", (PyObject*)&BitStreamType); - return module; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acefile-0.6.13/requirements-dev.txt new/acefile-0.6.14/requirements-dev.txt --- old/acefile-0.6.13/requirements-dev.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/acefile-0.6.14/requirements-dev.txt 2026-01-20 01:31:38.000000000 +0100 @@ -0,0 +1,2 @@ +pytest +sphinx diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acefile-0.6.13/requirements.txt new/acefile-0.6.14/requirements.txt --- old/acefile-0.6.13/requirements.txt 2018-07-15 20:18:42.000000000 +0200 +++ new/acefile-0.6.14/requirements.txt 2026-01-18 21:05:22.000000000 +0100 @@ -0,0 +1 @@ +setuptools diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acefile-0.6.13/setup.py new/acefile-0.6.14/setup.py --- old/acefile-0.6.13/setup.py 2024-08-04 14:02:35.000000000 +0200 +++ new/acefile-0.6.14/setup.py 2026-01-21 00:04:45.000000000 +0100 @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# vim: set list et ts=8 sts=4 sw=4 ft=python: from setuptools import setup, find_packages from setuptools.extension import Extension @@ -33,9 +32,7 @@ license=acefile.__license__, platforms=['all'], classifiers=[ - # https://pypi.python.org/pypi?%3Aaction=list_classifiers 'Development Status :: 5 - Production/Stable', - 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', @@ -48,17 +45,17 @@ 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', 'Topic :: System :: Archiving :: Compression', ], keywords=['ace', 'unace', 'compression', 'decompression', 'archive'], py_modules=['acefile'], ext_modules=ext_modules, - entry_points = { + entry_points={ 'console_scripts': [ 'acefile-unace=acefile:unace', ], }, - test_suite = 'acefile.testsuite', ) try: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acefile-0.6.13/tests/conftest.py new/acefile-0.6.14/tests/conftest.py --- old/acefile-0.6.13/tests/conftest.py 1970-01-01 01:00:00.000000000 +0100 +++ new/acefile-0.6.14/tests/conftest.py 2026-01-20 00:11:44.000000000 +0100 @@ -0,0 +1,3 @@ +def pytest_addoption(parser): + parser.addoption("--fast", action="store_true", + help="Skip private testdata") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/acefile-0.6.13/tests/test_smoke.py new/acefile-0.6.14/tests/test_smoke.py --- old/acefile-0.6.13/tests/test_smoke.py 1970-01-01 01:00:00.000000000 +0100 +++ new/acefile-0.6.14/tests/test_smoke.py 2026-01-20 00:17:27.000000000 +0100 @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 + +import glob +import os +import re +import sys + +import pytest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)) + '/..') +import acefile + + + +def _metadict_from_path(path): + """ + >>> _metadict_from_path("winappdbg-winappdbg_v1.6.ace") + {} + >>> _metadict_from_path("winappdbg-winappdbg_v1.6_password_pw=infected.ace") + {'pw': 'infected'} + >>> _metadict_from_path("foo_foo=oof-bar=baz+qux=quux.ace") + {'foo': 'oof', 'bar': 'baz', 'qux': 'quux'} + """ + metadict = {} + for m in re.finditer(r'([a-zA-Z0-9]+)=([a-zA-Z0-9]+)', path): + metadict[m.group(1)] = m.group(2) + return metadict + + + +def pytest_generate_tests(metafunc): + archives = [] + here = os.path.dirname(os.path.abspath(__file__)) + subdirs = ['acefile-testdata'] + if not metafunc.config.getoption("fast"): + subdirs.append('acefile-testdata-private') + for subdir in subdirs: + path = os.path.realpath(f'{here}/../../{subdir}') + if os.path.exists(path): + archives += glob.glob(f'{path}/**/*.ace') + archives += glob.glob(f'{path}/**/*.exe') + metafunc.parametrize("archive_path", archives) + + + +def test_archive_test(archive_path): + metadict = _metadict_from_path(archive_path) + pwd = metadict.get('pw', None) + with acefile.open(archive_path) as f: + for member in f: + if member.is_dir(): + continue + assert f.test(member, pwd=pwd) + + + +def doctst(): + import doctest + fails, tests = doctest.testmod(optionflags=doctest.IGNORE_EXCEPTION_DETAIL) + sys.exit(min(1, fails)) + + + +if __name__ == '__main__': + if '--doctest' in sys.argv: + doctst() + raise NotImplementedError("main")
