This file is indexed.

/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