/usr/lib/python3/dist-packages/diffoscope/comparators/apk.py is in diffoscope 93ubuntu1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | # -*- coding: utf-8 -*-
#
# diffoscope: in-depth comparison of files, archives, and directories
#
# Copyright © 2016 Reiner Herrmann <reiner@reiner-h.de>
#
# 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 re
import os.path
import logging
import subprocess
from diffoscope.tools import tool_required
from diffoscope.tempfiles import get_temporary_directory
from diffoscope.difference import Difference
from .utils.file import File
from .utils.archive import Archive
from .utils.compare import compare_files
from .zip import Zipinfo, ZipinfoVerbose
from .missing_file import MissingFile
logger = logging.getLogger(__name__)
class ApkContainer(Archive):
@property
def path(self):
return self._path
@tool_required('apktool')
@tool_required('zipinfo')
def open_archive(self):
self._members = []
self._unpacked = os.path.join(
get_temporary_directory().name,
os.path.basename(self.source.name),
)
self._andmanifest = None
self._andmanifest_orig = None
logger.debug("Extracting %s to %s", self.source.name, self._unpacked)
subprocess.check_call((
'apktool', 'd', '-k', '-m', '-o', self._unpacked, self.source.path,
), shell=False, stderr=None, stdout=subprocess.PIPE)
# Optionally extract the classes.dex file; apktool does not do this.
subprocess.call((
'unzip', '-d', self._unpacked, self.source.path, 'classes.dex',
), stderr=subprocess.PIPE, stdout=subprocess.PIPE)
for root, _, files in os.walk(self._unpacked):
current_dir = []
for filename in files:
abspath = os.path.join(root, filename)
# apktool.yml is a file created by apktool and containing
# metadata information. Rename it to clarify and always make it
# appear at the beginning of the directory listing for
# reproducibility.
if filename == 'apktool.yml':
abspath = filter_apk_metadata(
abspath,
os.path.basename(self.source.name),
)
relpath = abspath[len(self._unpacked) + 1:]
current_dir.insert(0, relpath)
continue
relpath = abspath[len(self._unpacked)+1:]
if filename == 'AndroidManifest.xml':
containing_dir = root[len(self._unpacked)+1:]
if containing_dir == 'original':
self._andmanifest_orig = relpath
if containing_dir == '':
self._andmanifest = relpath
continue
current_dir.append(relpath)
self._members.extend(current_dir)
return self
def get_android_manifest(self):
return self.get_member(self._andmanifest) \
if self._andmanifest else None
def get_original_android_manifest(self):
if self._andmanifest_orig:
return self.get_member(self._andmanifest_orig)
return MissingFile('/dev/null', self._andmanifest_orig)
def close_archive(self):
pass
def get_member_names(self):
return self._members
def extract(self, member_name, dest_dir):
return os.path.join(self._unpacked, member_name)
def compare_manifests(self, other):
my_android_manifest = self.get_android_manifest()
other_android_manifest = other.get_android_manifest()
comment = None
diff_manifests = None
if my_android_manifest and other_android_manifest:
source = 'AndroidManifest.xml (decoded)'
diff_manifests = compare_files(my_android_manifest,
other_android_manifest,
source=source)
if diff_manifests is None:
comment = 'No difference found for decoded AndroidManifest.xml'
else:
comment = 'No decoded AndroidManifest.xml found ' + \
'for one of the APK files.'
if diff_manifests:
return diff_manifests
source = 'AndroidManifest.xml (original / undecoded)'
diff_manifests = compare_files(self.get_original_android_manifest(),
other.get_original_android_manifest(),
source=source)
if diff_manifests is not None:
diff_manifests.add_comment(comment)
return diff_manifests
def compare(self, other, *args, **kwargs):
differences = []
try:
differences.append(self.compare_manifests(other))
except AttributeError: # no apk-specific methods, e.g. MissingArchive
pass
differences.extend(super().compare(other, *args, **kwargs))
return differences
class ApkFile(File):
DESCRIPTION = "Android APK files"
FILE_TYPE_HEADER_PREFIX = b"PK\x03\x04"
FILE_TYPE_RE = re.compile(r'^(Java|Zip) archive data.*\b')
FILE_EXTENSION_SUFFIX = '.apk'
CONTAINER_CLASS = ApkContainer
def compare_details(self, other, source=None):
zipinfo_difference = Difference.from_command(Zipinfo, self.path, other.path) or \
Difference.from_command(ZipinfoVerbose, self.path, other.path)
return [zipinfo_difference]
def filter_apk_metadata(filepath, archive_name):
new_filename = os.path.join(os.path.dirname(filepath), 'APK metadata')
logger.debug("Moving APK metadata from %s to %s", filepath, new_filename)
re_filename = re.compile(
r'^apkFileName: %s' % re.escape(os.path.basename(archive_name)),
)
with open(filepath) as in_, open(new_filename, 'w') as out:
out.writelines(x for x in in_ if not re_filename.match(x))
os.remove(filepath)
return new_filename
|