This file is indexed.

/usr/lib/python3/dist-packages/diffoscope/comparators/zip.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
# -*- coding: utf-8 -*-
#
# diffoscope: in-depth comparison of files, archives, and directories
#
# Copyright © 2014-2015 Jérémy Bobbio <lunar@debian.org>
#
# 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 sys
import shutil
import os.path
import zipfile

from diffoscope.tools import tool_required
from diffoscope.difference import Difference

from .utils.file import File
from .directory import Directory
from .utils.archive import Archive, ArchiveMember
from .utils.command import Command


class Zipinfo(Command):
    @tool_required('zipinfo')
    def cmdline(self):
        # zipinfo (without -v) puts warning messages (some of which contain
        # $path) into stdin when stderr is not a tty, see #879011 for details.
        # to work around it, we run it on /dev/stdin instead, seems to work ok.
        return ['zipinfo', '/dev/stdin']

    def stdin(self):
        return open(self.path, 'rb')

    def filter(self, line):
        # we don't care about the archive file path
        if line.startswith(b'Archive:'):
            return b''
        return line


class ZipinfoVerbose(Zipinfo):
    @tool_required('zipinfo')
    def cmdline(self):
        return ['zipinfo', '-v', self.path]


class BsdtarVerbose(Command):
    @tool_required('bsdtar')
    def cmdline(self):
        return ['bsdtar', '-tvf', self.path]


class ZipDirectory(Directory, ArchiveMember):
    def __init__(self, archive, member_name):
        ArchiveMember.__init__(self, archive, member_name)

    def compare(self, other, source=None):
        return None

    def has_same_content_as(self, other):
        return False

    def is_directory(self):
        return True

    def get_member_names(self):
        raise ValueError("Zip archives are compared as a whole.")  # noqa

    def get_member(self, member_name):
        raise ValueError("Zip archives are compared as a whole.")  # noqa


class ZipContainer(Archive):
    def open_archive(self):
        return zipfile.ZipFile(self.source.path, 'r')

    def close_archive(self):
        self.archive.close()

    def get_member_names(self):
        return self.archive.namelist()

    def extract(self, member_name, dest_dir):
        # We don't really want to crash if the filename in the zip archive
        # can't be encoded using the filesystem encoding. So let's replace
        # any weird character so we can get to the bytes.
        targetpath = os.path.join(dest_dir, os.path.basename(member_name)).encode(sys.getfilesystemencoding(), errors='replace')
        with self.archive.open(member_name) as source, open(targetpath, 'wb') as target:
            shutil.copyfileobj(source, target)
        return targetpath.decode(sys.getfilesystemencoding())

    def get_member(self, member_name):
        zipinfo = self.archive.getinfo(member_name)
        if zipinfo.filename[-1] == '/':
            return ZipDirectory(self, member_name)
        return ArchiveMember(self, member_name)


class ZipFile(File):
    CONTAINER_CLASS = ZipContainer
    FILE_TYPE_RE = re.compile(r'^(Zip archive|Java archive|EPUB document|OpenDocument (Text|Spreadsheet|Presentation|Drawing|Formula|Template|Text Template))\b')

    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) or \
                             Difference.from_command(BsdtarVerbose, self.path, other.path)
        return [zipinfo_difference]


class MozillaZipCommandMixin(object):
    @property
    def returncode(self):
        # zipinfo emits an error when reading Mozilla-optimized ZIPs,
        # which is fine to ignore.
        return 0

class MozillaZipinfo(MozillaZipCommandMixin, Zipinfo):
    pass


class MozillaZipinfoVerbose(MozillaZipCommandMixin, ZipinfoVerbose):
    pass


class MozillaZipContainer(ZipContainer):
    def open_archive(self):
        # This is gross: Monkeypatch zipfile._EndRecData to work with
        # Mozilla-optimized ZIPs
        _orig_EndRecData = zipfile._EndRecData

        def _EndRecData(fh):
            endrec = _orig_EndRecData(fh)
            if endrec:
                endrec[zipfile._ECD_LOCATION] = (endrec[zipfile._ECD_OFFSET] +
                                                 endrec[zipfile._ECD_SIZE])
            return endrec
        zipfile._EndRecData = _EndRecData
        result = super(MozillaZipContainer, self).open_archive()
        zipfile._EndRecData = _orig_EndRecData
        return result


class MozillaZipFile(File):
    CONTAINER_CLASS = MozillaZipContainer

    @classmethod
    def recognizes(cls, file):
        # Mozilla-optimized ZIPs start with a 32-bit little endian integer
        # indicating the amount of data to preload, followed by the ZIP
        # central directory (with a PK\x01\x02 signature)
        return file.file_header[4:8] == b'PK\x01\x02'

    def compare_details(self, other, source=None):
        zipinfo_difference = Difference.from_command(MozillaZipinfo, self.path, other.path) or \
                             Difference.from_command(MozillaZipinfoVerbose, self.path, other.path) or \
                             Difference.from_command(BsdtarVerbose, self.path, other.path)
        return [zipinfo_difference]