[Git][reproducible-builds/diffoscope][master] Add support for Android resources.arsc files. (Closes: reproducible-builds/diffoscope!116)

Chris Lamb (@lamby) gitlab at salsa.debian.org
Fri Feb 10 18:03:55 UTC 2023



Chris Lamb pushed to branch master at Reproducible Builds / diffoscope


Commits:
7cf77ed1 by FC Stegerman at 2023-02-10T10:03:27-08:00
Add support for Android resources.arsc files. (Closes: reproducible-builds/diffoscope!116)

- - - - -


8 changed files:

- debian/control
- diffoscope/comparators/__init__.py
- + diffoscope/comparators/arsc.py
- diffoscope/external_tools.py
- + tests/comparators/test_arsc.py
- + tests/data/arsc_expected_diff
- + tests/data/resources1.arsc
- + tests/data/resources2.arsc


Changes:

=====================================
debian/control
=====================================
@@ -7,6 +7,7 @@ Uploaders:
  Holger Levsen <holger at debian.org>,
  Mattia Rizzolo <mattia at debian.org>,
 Build-Depends:
+ aapt <!nocheck>,
  abootimg <!nocheck>,
  androguard <!nocheck>,
  apksigcopier <!nocheck>,


=====================================
diffoscope/comparators/__init__.py
=====================================
@@ -121,6 +121,7 @@ class ComparatorManager:
         ("berkeley_db.BerkeleyDBFile",),
         ("zst.ZstFile",),
         ("vmlinuz.VmlinuzFile",),
+        ("arsc.ArscFile",),
     )
 
     _singleton = {}


=====================================
diffoscope/comparators/arsc.py
=====================================
@@ -0,0 +1,133 @@
+#
+# diffoscope: in-depth comparison of files, archives, and directories
+#
+# Copyright © 2023 FC Stegerman <flx at obfusk.net>
+#
+# diffoscope is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# diffoscope is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with diffoscope.  If not, see <https://www.gnu.org/licenses/>.
+
+import os.path
+import re
+import tempfile
+import zipfile
+
+from contextlib import contextmanager
+
+from diffoscope.tools import tool_required
+from diffoscope.difference import Difference
+
+from .utils.command import Command
+from .utils.file import File
+
+# The binary representation of this dummy Android XML file:
+# <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1" android:compileSdkVersion="29" android:compileSdkVersionCodename="10.0.0" package="com.example" platformBuildVersionCode="29" platformBuildVersionName="10.0.0">
+#   <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/>
+# </manifest>
+AXML = (
+    b"\x03\x00\x08\x00\xf0\x03\x00\x00\x01\x00\x1c\x00l\x02\x00\x00"
+    b"\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\\\x00\x00\x00"
+    b"\x00\x00\x00\x00\x00\x00\x00\x00\x1e\x00\x00\x008\x00\x00\x00R\x00\x00\x00"
+    b"v\x00\x00\x00\x9c\x00\x00\x00\xd2\x00\x00\x00\xd8\x00\x00\x00"
+    b"\xe8\x00\x00\x00\xfa\x00\x00\x00\x14\x01\x00\x00l\x01\x00\x00"
+    b"\x80\x01\x00\x00\x92\x01\x00\x00\xc6\x01\x00\x00\xfa\x01\x00\x00\r\x00m\x00"
+    b"i\x00n\x00S\x00d\x00k\x00V\x00e\x00r\x00s\x00i\x00o\x00n\x00\x00\x00\x0b\x00"
+    b"v\x00e\x00r\x00s\x00i\x00o\x00n\x00C\x00o\x00d\x00e\x00\x00\x00\x0b\x00v\x00"
+    b"e\x00r\x00s\x00i\x00o\x00n\x00N\x00a\x00m\x00e\x00\x00\x00\x10\x00t\x00a\x00"
+    b"r\x00g\x00e\x00t\x00S\x00d\x00k\x00V\x00e\x00r\x00s\x00i\x00o\x00n\x00"
+    b"\x00\x00\x11\x00c\x00o\x00m\x00p\x00i\x00l\x00e\x00S\x00d\x00k\x00V\x00e\x00"
+    b"r\x00s\x00i\x00o\x00n\x00\x00\x00\x19\x00c\x00o\x00m\x00p\x00i\x00l\x00e\x00"
+    b"S\x00d\x00k\x00V\x00e\x00r\x00s\x00i\x00o\x00n\x00C\x00o\x00d\x00e\x00"
+    b"n\x00a\x00m\x00e\x00\x00\x00\x01\x001\x00\x00\x00\x06\x001\x000\x00.\x00"
+    b"0\x00.\x000\x00\x00\x00\x07\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00"
+    b"\x00\x00\x0b\x00c\x00o\x00m\x00.\x00e\x00x\x00a\x00m\x00p\x00l\x00"
+    b"e\x00\x00\x00*\x00h\x00t\x00t\x00p\x00:\x00/\x00/\x00s\x00c\x00h\x00e\x00"
+    b"m\x00a\x00s\x00.\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00c\x00o\x00"
+    b"m\x00/\x00a\x00p\x00k\x00/\x00r\x00e\x00s\x00/\x00a\x00n\x00d\x00r\x00"
+    b"o\x00i\x00d\x00\x00\x00\x08\x00m\x00a\x00n\x00i\x00f\x00e\x00s\x00"
+    b"t\x00\x00\x00\x07\x00p\x00a\x00c\x00k\x00a\x00g\x00e\x00\x00\x00\x18\x00"
+    b"p\x00l\x00a\x00t\x00f\x00o\x00r\x00m\x00B\x00u\x00i\x00l\x00d\x00V\x00"
+    b"e\x00r\x00s\x00i\x00o\x00n\x00C\x00o\x00d\x00e\x00\x00\x00\x18\x00p\x00l\x00"
+    b"a\x00t\x00f\x00o\x00r\x00m\x00B\x00u\x00i\x00l\x00d\x00V\x00e\x00r\x00"
+    b"s\x00i\x00o\x00n\x00N\x00a\x00m\x00e\x00\x00\x00\x08\x00u\x00s\x00e\x00s\x00"
+    b"-\x00s\x00d\x00k\x00\x00\x00\x00\x00\x80\x01\x08\x00 \x00\x00\x00"
+    b"\x0c\x02\x01\x01\x1b\x02\x01\x01\x1c\x02\x01\x01p\x02\x01\x01r\x05\x01\x01"
+    b"s\x05\x01\x01\x00\x01\x10\x00\x18\x00\x00\x00\x01\x00\x00\x00"
+    b"\xff\xff\xff\xff\x08\x00\x00\x00\n\x00\x00\x00\x02\x01\x10\x00"
+    b"\xb0\x00\x00\x00\x01\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff"
+    b"\x0b\x00\x00\x00\x14\x00\x14\x00\x07\x00\x00\x00\x00\x00\x00\x00"
+    b"\n\x00\x00\x00\x01\x00\x00\x00\xff\xff\xff\xff\x08\x00\x00\x10"
+    b"\x01\x00\x00\x00\n\x00\x00\x00\x02\x00\x00\x00\x06\x00\x00\x00"
+    b"\x08\x00\x00\x03\x06\x00\x00\x00\n\x00\x00\x00\x04\x00\x00\x00"
+    b"\xff\xff\xff\xff\x08\x00\x00\x10\x1d\x00\x00\x00\n\x00\x00\x00"
+    b"\x05\x00\x00\x00\x07\x00\x00\x00\x08\x00\x00\x03\x07\x00\x00\x00"
+    b"\xff\xff\xff\xff\x0c\x00\x00\x00\t\x00\x00\x00\x08\x00\x00\x03\t\x00\x00\x00"
+    b"\xff\xff\xff\xff\r\x00\x00\x00\xff\xff\xff\xff\x08\x00\x00\x10"
+    b"\x1d\x00\x00\x00\xff\xff\xff\xff\x0e\x00\x00\x00\x07\x00\x00\x00"
+    b"\x08\x00\x00\x03\x07\x00\x00\x00\x02\x01\x10\x00L\x00\x00\x00"
+    b"\x02\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0f\x00\x00\x00"
+    b"\x14\x00\x14\x00\x02\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00"
+    b"\x00\x00\x00\x00\xff\xff\xff\xff\x08\x00\x00\x10\x15\x00\x00\x00"
+    b"\n\x00\x00\x00\x03\x00\x00\x00\xff\xff\xff\xff\x08\x00\x00\x10"
+    b"\x1d\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x02\x00\x00\x00"
+    b"\xff\xff\xff\xff\xff\xff\xff\xff\x0f\x00\x00\x00\x03\x01\x10\x00"
+    b"\x18\x00\x00\x00\x01\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff"
+    b"\x0b\x00\x00\x00\x01\x01\x10\x00\x18\x00\x00\x00\x01\x00\x00\x00"
+    b"\xff\xff\xff\xff\x08\x00\x00\x00\n\x00\x00\x00"
+)
+
+
+class ArscDumpWithAapt(Command):
+    MASK_STDERR = True
+
+    @tool_required("aapt2")
+    def cmdline(self):
+        return ["aapt2", "dump", "resources", self.path]
+
+
+class ArscFile(File):
+    DESCRIPTION = "Android package resource table (ARSC)"
+    FILE_TYPE_HEADER_PREFIX = b"\x02\x00\x0c\x00"
+    FILE_TYPE_RE = re.compile(r"^Android package resource table\b")
+    FILE_EXTENSION_SUFFIX = {".arsc"}
+
+    @tool_required("aapt2")
+    def compare_details(self, other, source=None):
+        with as_apk(self.path) as self_apk, as_apk(other.path) as other_apk:
+            return [
+                Difference.from_operation(
+                    ArscDumpWithAapt, self_apk, other_apk
+                )
+            ]
+
+
+ at contextmanager
+def as_apk(path):
+    # Generate a temporary ZIP file containing the resources.arsc file and a
+    # dummy AndroidManifest.xml.  The aapt2 dump resources command requires the
+    # resources.arsc file to be inside an APK (ZIP file), and for a valid
+    # AndroidManifest.xml file to be present as well.  However, the XML file
+    # does not actually need to correspond to the resources.arsc file (though
+    # this will result in warnings on stderr, which we ignore); providing a
+    # dummy XML file produces the exact same dump.
+    with open(path, "rb") as fhi:
+        with tempfile.TemporaryDirectory() as tdir:
+            apk = os.path.join(tdir, "out.apk")
+            with zipfile.ZipFile(apk, "w") as zf:
+                zf.writestr("AndroidManifest.xml", AXML)
+                with zf.open("resources.arsc", "w") as fho:
+                    while True:
+                        data = fhi.read(4096)
+                        if not data:
+                            break
+                        fho.write(data)
+            yield apk


=====================================
diffoscope/external_tools.py
=====================================
@@ -23,6 +23,7 @@ that might resolve to, for example, `/usr/bin/abootimg`..
 """
 
 EXTERNAL_TOOLS = {
+    "aapt2": {"debian": "aapt"},
     "abootimg": {"debian": "abootimg", "guix": "abootimg"},
     "androguard": {"debian": "androguard"},
     "apksigcopier": {"debian": "apksigcopier"},


=====================================
tests/comparators/test_arsc.py
=====================================
@@ -0,0 +1,63 @@
+#
+# diffoscope: in-depth comparison of files, archives, and directories
+#
+# Copyright © 2023 FC Stegerman <flx at obfusk.net>
+#
+# diffoscope is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# diffoscope is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with diffoscope.  If not, see <https://www.gnu.org/licenses/>.
+
+import pytest
+
+from diffoscope.config import Config
+from diffoscope.comparators.arsc import ArscFile
+from diffoscope.comparators.missing_file import MissingFile
+
+from ..utils.data import load_fixture, get_data
+from ..utils.tools import skip_unless_tools_exist
+
+
+arsc1 = load_fixture("resources1.arsc")
+arsc2 = load_fixture("resources2.arsc")
+
+
+def test_identification(arsc1):
+    assert isinstance(arsc1, ArscFile)
+
+
+def test_no_differences(arsc1):
+    difference = arsc1.compare(arsc1)
+    assert difference is None
+
+
+ at pytest.fixture
+def differences(arsc1, arsc2):
+    return arsc1.compare(arsc2).details
+
+
+def check_arsc_differences(differences, expected_diff):
+    assert differences[0].source1 == "aapt2 dump resources {}"
+    assert differences[0].source2 == "aapt2 dump resources {}"
+    assert expected_diff == differences[0].unified_diff
+
+
+ at skip_unless_tools_exist("aapt2")
+def test_differences(differences):
+    expected_diff = get_data("arsc_expected_diff")
+    check_arsc_differences(differences, expected_diff)
+
+
+ at skip_unless_tools_exist("aapt2")
+def test_compare_non_existing(monkeypatch, arsc1):
+    monkeypatch.setattr(Config(), "new_file", True)
+    difference = arsc1.compare(MissingFile("/nonexisting", arsc1))
+    assert difference.source2 == "/nonexisting"


=====================================
tests/data/arsc_expected_diff
=====================================
@@ -0,0 +1,10 @@
+@@ -8,8 +8,8 @@
+   type layout id=02 entryCount=1
+     resource 0x7f020000 layout/main
+       () (file) res/layout/main.xml type=XML
+   type string id=03 entryCount=2
+     resource 0x7f030000 string/app_name
+       () "Sokoban(g)"
+     resource 0x7f030001 string/private_version
+-      () "a519b310bcfab08227888424a412e4b2e781e1da"
++      () "3bd5987b1d0faebbdd76745afdbe992dcb331c73"


=====================================
tests/data/resources1.arsc
=====================================
Binary files /dev/null and b/tests/data/resources1.arsc differ


=====================================
tests/data/resources2.arsc
=====================================
Binary files /dev/null and b/tests/data/resources2.arsc differ



View it on GitLab: https://salsa.debian.org/reproducible-builds/diffoscope/-/commit/7cf77ed15da62df27b43d8801ba2a9af3bff65b4

-- 
View it on GitLab: https://salsa.debian.org/reproducible-builds/diffoscope/-/commit/7cf77ed15da62df27b43d8801ba2a9af3bff65b4
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.reproducible-builds.org/pipermail/rb-commits/attachments/20230210/0af4d9e0/attachment.htm>


More information about the rb-commits mailing list