/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]
|