From 6ba3e3f20d7d52895bc44f9fc35b068cfce47133 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Sat, 25 Jul 2015 17:14:23 +0200 Subject: _MASSIVE_ pep8 revamp Thank you so much PyCharm --- libmat/__init__.py | 1 - libmat/archive.py | 128 ++++++++++++++++++++--------------- libmat/audio.py | 28 ++++---- libmat/bencode/__init__.py | 1 - libmat/bencode/bencode.py | 33 +++++---- libmat/exceptions.py | 12 ++-- libmat/exiftool.py | 39 ++++++----- libmat/hachoir_editor/typed_field.py | 47 ++++++++----- libmat/images.py | 20 +++--- libmat/mat.py | 55 +++++++-------- libmat/misc.py | 39 +++++------ libmat/mutagenstripper.py | 8 +-- libmat/office.py | 55 ++++++++------- libmat/parser.py | 48 ++++++------- libmat/strippers.py | 4 +- mat | 51 +++++++------- mat-gui | 93 +++++++++++++------------ nautilus/nautilus-mat.py | 13 ++-- setup.py | 44 ++++++------ test/clitest.py | 68 ++++++++++--------- test/libtest.py | 79 +++++++++++---------- test/test.py | 30 ++++---- 22 files changed, 475 insertions(+), 421 deletions(-) diff --git a/libmat/__init__.py b/libmat/__init__.py index 8b13789..e69de29 100644 --- a/libmat/__init__.py +++ b/libmat/__init__.py @@ -1 +0,0 @@ - diff --git a/libmat/archive.py b/libmat/archive.py index d483dcc..4c62dc8 100644 --- a/libmat/archive.py +++ b/libmat/archive.py @@ -1,5 +1,5 @@ -''' Take care of archives formats -''' +""" Take care of archives formats +""" import datetime import logging @@ -16,23 +16,24 @@ import parser # Zip files do not support dates older than 01/01/1980 ZIP_EPOCH = (1980, 1, 1, 0, 0, 0) ZIP_EPOCH_SECONDS = (datetime.datetime(1980, 1, 1, 0, 0, 0) - - datetime.datetime(1970, 1, 1, 1, 0, 0)).total_seconds() + - datetime.datetime(1970, 1, 1, 1, 0, 0)).total_seconds() class GenericArchiveStripper(parser.GenericParser): - ''' Represent a generic archive - ''' + """ Represent a generic archive + """ + def __init__(self, filename, parser, mime, backup, is_writable, **kwargs): super(GenericArchiveStripper, self).__init__(filename, - parser, mime, backup, is_writable, **kwargs) + parser, mime, backup, is_writable, **kwargs) self.compression = '' self.add2archive = kwargs['add2archive'] self.tempdir = tempfile.mkdtemp() def __del__(self): - ''' Remove the files inside the temp dir, + """ Remove the files inside the temp dir, then remove the temp dir - ''' + """ for root, dirs, files in os.walk(self.tempdir): for item in files: path_file = os.path.join(root, item) @@ -40,28 +41,30 @@ class GenericArchiveStripper(parser.GenericParser): shutil.rmtree(self.tempdir) def is_clean(self, list_unsupported=False): - ''' Virtual method to check for harmul metadata - ''' + """ Virtual method to check for harmul metadata + """ raise NotImplementedError def list_unsupported(self): - ''' Get a list of every non-supported files present in the archive - ''' + """ Get a list of every non-supported files present in the archive + """ return self.is_clean(list_unsupported=True) def remove_all(self): - ''' Virtual method to remove all metadata - ''' + """ Virtual method to remove all metadata + """ raise NotImplementedError class ZipStripper(GenericArchiveStripper): - ''' Represent a zip file - ''' - def __is_zipfile_clean(self, fileinfo): - ''' Check if a ZipInfo object is clean of metadata added + """ Represent a zip file + """ + + @staticmethod + def __is_zipfile_clean(fileinfo): + """ Check if a ZipInfo object is clean of metadata added by zip itself, independently of the corresponding file metadata - ''' + """ if fileinfo.comment != '': return False elif fileinfo.date_time != ZIP_EPOCH: @@ -71,11 +74,11 @@ class ZipStripper(GenericArchiveStripper): return True def is_clean(self, list_unsupported=False): - ''' Check if the given file is clean from harmful metadata + """ Check if the given file is clean from harmful metadata When list_unsupported is True, the method returns a list of all non-supported/archives files contained in the archive. - ''' + """ ret_list = [] zipin = zipfile.ZipFile(self.filename, 'r') if zipin.comment != '' and not list_unsupported: @@ -86,7 +89,7 @@ class ZipStripper(GenericArchiveStripper): path = os.path.join(self.tempdir, item.filename) if not self.__is_zipfile_clean(item) and not list_unsupported: logging.debug('%s from %s has compromising zipinfo' % - (item.filename, self.filename)) + (item.filename, self.filename)) return False if os.path.isfile(path): cfile = mat.create_class_file(path, False, add2archive=self.add2archive) @@ -97,7 +100,7 @@ class ZipStripper(GenericArchiveStripper): return False else: logging.info('%s\'s fileformat is not supported or harmless.' - % item.filename) + % item.filename) basename, ext = os.path.splitext(path) if os.path.basename(item.filename) not in ('mimetype', '.rels'): if ext not in parser.NOMETA: @@ -110,7 +113,7 @@ class ZipStripper(GenericArchiveStripper): return True def get_meta(self): - ''' Return all the metadata of a zip archive''' + """ Return all the metadata of a zip archive""" zipin = zipfile.ZipFile(self.filename, 'r') metadata = {} if zipin.comment != '': @@ -129,13 +132,14 @@ class ZipStripper(GenericArchiveStripper): metadata[item.filename] = str(cfile_meta) else: logging.info('%s\'s fileformat is not supported or harmless' - % item.filename) + % item.filename) zipin.close() return metadata - def __get_zipinfo_meta(self, zipinfo): - ''' Return all the metadata of a ZipInfo - ''' + @staticmethod + def __get_zipinfo_meta(zipinfo): + """ Return all the metadata of a ZipInfo + """ metadata = {} if zipinfo.comment != '': metadata['comment'] = zipinfo.comment @@ -145,13 +149,19 @@ class ZipStripper(GenericArchiveStripper): metadata['system'] = "windows" if zipinfo.create_system == 2 else "unknown" return metadata - def remove_all(self, whitelist=[], beginning_blacklist=[], ending_blacklist=[]): - ''' Remove all metadata from a zip archive, even thoses + def remove_all(self, whitelist=None, beginning_blacklist=None, ending_blacklist=None): + """ Remove all metadata from a zip archive, even thoses added by Python's zipfile itself. It will not add files starting with "begining_blacklist", or ending with "ending_blacklist". This method also add files present in whitelist to the archive. - ''' + """ + if not ending_blacklist: + ending_blacklist = [] + if not beginning_blacklist: + beginning_blacklist = [] + if not whitelist: + whitelist = [] zipin = zipfile.ZipFile(self.filename, 'r') zipout = zipfile.ZipFile(self.output, 'w', allowZip64=True) for item in zipin.infolist(): @@ -166,7 +176,7 @@ class ZipStripper(GenericArchiveStripper): if cfile is not None: # Handle read-only files inside archive old_stat = os.stat(path).st_mode - os.chmod(path, old_stat|stat.S_IWUSR) + os.chmod(path, old_stat | stat.S_IWUSR) cfile.remove_all() os.chmod(path, old_stat) logging.debug('Processing %s from %s' % (item.filename, self.filename)) @@ -186,11 +196,12 @@ class ZipStripper(GenericArchiveStripper): class TarStripper(GenericArchiveStripper): - ''' Represent a tarfile archive - ''' + """ Represent a tarfile archive + """ + def _remove(self, current_file): - ''' Remove the meta added by tarfile itself to the file - ''' + """ Remove the meta added by tarfile itself to the file + """ current_file.mtime = 0 current_file.uid = 0 current_file.gid = 0 @@ -198,11 +209,13 @@ class TarStripper(GenericArchiveStripper): current_file.gname = '' return current_file - def remove_all(self, whitelist=[]): - ''' Remove all harmful metadata from the tarfile. + def remove_all(self, whitelist=None): + """ Remove all harmful metadata from the tarfile. The method will also add every files matching whitelist in the produced archive. - ''' + """ + if not whitelist: + whitelist = [] tarin = tarfile.open(self.filename, 'r' + self.compression, encoding='utf-8') tarout = tarfile.open(self.output, 'w' + self.compression, encoding='utf-8') for item in tarin.getmembers(): @@ -213,14 +226,14 @@ class TarStripper(GenericArchiveStripper): if cfile is not None: # Handle read-only files inside archive old_stat = os.stat(path).st_mode - os.chmod(path, old_stat|stat.S_IWUSR) + os.chmod(path, old_stat | stat.S_IWUSR) cfile.remove_all() os.chmod(path, old_stat) elif self.add2archive or os.path.splitext(item.name)[1] in parser.NOMETA: logging.debug('%s\' format is either not supported or harmless' % item.name) elif item.name in whitelist: logging.debug('%s is not supported, but MAT was told to add it anyway.' - % item.name) + % item.name) else: # Don't add the file to the archive logging.debug('%s will not be added' % item.name) continue @@ -230,9 +243,10 @@ class TarStripper(GenericArchiveStripper): self.do_backup() return True - def is_file_clean(self, current_file): - ''' Check metadatas added by tarfile - ''' + @staticmethod + def is_file_clean(current_file): + """ Check metadatas added by tarfile + """ if current_file.mtime != 0: return False elif current_file.uid != 0: @@ -246,17 +260,17 @@ class TarStripper(GenericArchiveStripper): return True def is_clean(self, list_unsupported=False): - ''' Check if the file is clean from harmful metadatas + """ Check if the file is clean from harmful metadatas When list_unsupported is True, the method returns a list of all non-supported/archives files contained in the archive. - ''' + """ ret_list = [] tarin = tarfile.open(self.filename, 'r' + self.compression) for item in tarin.getmembers(): if not self.is_file_clean(item) and not list_unsupported: logging.debug('%s from %s has compromising tarinfo' % - (item.name, self.filename)) + (item.name, self.filename)) return False tarin.extract(item, self.tempdir) path = os.path.join(self.tempdir, item.name) @@ -265,7 +279,7 @@ class TarStripper(GenericArchiveStripper): if cfile is not None: if not cfile.is_clean(): logging.debug('%s from %s has metadata' % - (item.name.decode("utf8"), self.filename)) + (item.name.decode("utf8"), self.filename)) if not list_unsupported: return False # Nested archives are treated like unsupported files @@ -283,8 +297,8 @@ class TarStripper(GenericArchiveStripper): return True def get_meta(self): - ''' Return a dict with all the meta of the tarfile - ''' + """ Return a dict with all the meta of the tarfile + """ tarin = tarfile.open(self.filename, 'r' + self.compression) metadata = {} for item in tarin.getmembers(): @@ -312,24 +326,26 @@ class TarStripper(GenericArchiveStripper): class TerminalZipStripper(ZipStripper): - ''' Represent a terminal level archive. + """ Represent a terminal level archive. This type of archive can not contain nested archives. It is used for formats like docx, which are basically ziped xml. - ''' + """ class GzipStripper(TarStripper): - ''' Represent a tar.gz archive - ''' + """ Represent a tar.gz archive + """ + def __init__(self, filename, parser, mime, backup, is_writable, **kwargs): super(GzipStripper, self).__init__(filename, parser, mime, backup, is_writable, **kwargs) self.compression = ':gz' class Bzip2Stripper(TarStripper): - ''' Represent a tar.bz2 archive - ''' + """ Represent a tar.bz2 archive + """ + def __init__(self, filename, parser, mime, backup, is_writable, **kwargs): super(Bzip2Stripper, self).__init__(filename, parser, mime, backup, is_writable, **kwargs) self.compression = ':bz2' diff --git a/libmat/audio.py b/libmat/audio.py index dae9d75..2747dc1 100644 --- a/libmat/audio.py +++ b/libmat/audio.py @@ -1,5 +1,5 @@ -''' Care about audio fileformat -''' +""" Care about audio fileformat +""" try: from mutagen.flac import FLAC @@ -12,41 +12,41 @@ import mutagenstripper class MpegAudioStripper(parser.GenericParser): - ''' Represent mpeg audio file (mp3, ...) - ''' + """ Represent mpeg audio file (mp3, ...) + """ def _should_remove(self, field): return field.name in ("id3v1", "id3v2") class OggStripper(mutagenstripper.MutagenStripper): - ''' Represent an ogg vorbis file - ''' + """ Represent an ogg vorbis file + """ def _create_mfile(self): self.mfile = OggVorbis(self.filename) class FlacStripper(mutagenstripper.MutagenStripper): - ''' Represent a Flac audio file - ''' + """ Represent a Flac audio file + """ def _create_mfile(self): self.mfile = FLAC(self.filename) def remove_all(self): - ''' Remove the "metadata" block from the file - ''' + """ Remove the "metadata" block from the file + """ super(FlacStripper, self).remove_all() self.mfile.clear_pictures() self.mfile.save() return True def is_clean(self): - ''' Check if the "metadata" block is present in the file - ''' + """ Check if the "metadata" block is present in the file + """ return super(FlacStripper, self).is_clean() and not self.mfile.pictures def get_meta(self): - ''' Return the content of the metadata block if present - ''' + """ Return the content of the metadata block if present + """ metadata = super(FlacStripper, self).get_meta() if self.mfile.pictures: metadata['picture:'] = 'yes' diff --git a/libmat/bencode/__init__.py b/libmat/bencode/__init__.py index 8b13789..e69de29 100644 --- a/libmat/bencode/__init__.py +++ b/libmat/bencode/__init__.py @@ -1 +0,0 @@ - diff --git a/libmat/bencode/bencode.py b/libmat/bencode/bencode.py index a0cc99a..a7967fc 100644 --- a/libmat/bencode/bencode.py +++ b/libmat/bencode/bencode.py @@ -21,18 +21,18 @@ # THE SOFTWARE. # -''' +""" A quick (and also nice) lib to bencode/bdecode torrent files -''' +""" class BTFailure(Exception): - '''Custom Exception''' + """Custom Exception""" pass class Bencached(object): - '''Custom type : cached string''' + """Custom type : cached string""" __slots__ = ['bencoded'] def __init__(self, string): @@ -40,10 +40,10 @@ class Bencached(object): def decode_int(x, f): - '''decode an int''' + """decode an int""" f += 1 newf = x.index('e', f) - if x[f:f+1] == '-0': + if x[f:f + 1] == '-0': raise ValueError elif x[f] == '0' and newf != f + 1: raise ValueError @@ -51,7 +51,7 @@ def decode_int(x, f): def decode_string(x, f): - '''decode a string''' + """decode a string""" colon = x.index(':', f) if x[f] == '0' and colon != f + 1: raise ValueError @@ -61,7 +61,7 @@ def decode_string(x, f): def decode_list(x, f): - '''decode a list''' + """decode a list""" result = [] f += 1 while x[f] != 'e': @@ -71,7 +71,7 @@ def decode_list(x, f): def decode_dict(x, f): - '''decode a dict''' + """decode a dict""" result = {} f += 1 while x[f] != 'e': @@ -81,24 +81,24 @@ def decode_dict(x, f): def encode_bool(x, r): - '''bencode a boolean''' + """bencode a boolean""" encode_int(1 if r else 0, r) def encode_int(x, r): - '''bencode an integer/float''' + """bencode an integer/float""" r.extend(('i', str(x), 'e')) def encode_list(x, r): - '''bencode a list/tuple''' + """bencode a list/tuple""" r.append('l') [ENCODE_FUNC[type(item)](item, r) for item in x] r.append('e') def encode_dict(x, result): - '''bencode a dict''' + """bencode a dict""" result.append('d') ilist = list(x.items()) ilist.sort() @@ -108,12 +108,11 @@ def encode_dict(x, result): result.append('e') -DECODE_FUNC = {str(x):decode_string for x in range(9)} +DECODE_FUNC = {str(x): decode_string for x in range(9)} DECODE_FUNC['l'] = decode_list DECODE_FUNC['d'] = decode_dict DECODE_FUNC['i'] = decode_int - ENCODE_FUNC = {} ENCODE_FUNC[Bencached] = lambda x, r: r.append(x.bencoded) ENCODE_FUNC[int] = encode_int @@ -126,14 +125,14 @@ ENCODE_FUNC[bool] = encode_bool def bencode(string): - '''bencode $string''' + """bencode $string""" table = [] ENCODE_FUNC[type(string)](string, table) return ''.join(table) def bdecode(string): - '''decode $string''' + """decode $string""" try: result, lenght = DECODE_FUNC[string[0]](string, 0) except (IndexError, KeyError, ValueError): diff --git a/libmat/exceptions.py b/libmat/exceptions.py index 47da15c..e71c398 100644 --- a/libmat/exceptions.py +++ b/libmat/exceptions.py @@ -1,14 +1,14 @@ -''' Base exceptions for MAT -''' +""" Base exceptions for MAT +""" class UnableToRemoveFile(Exception): - '''This exception is raised when a file could not be removed - ''' + """This exception is raised when a file could not be removed + """ pass class UnableToWriteFile(Exception): - '''This exception is raised when a file + """This exception is raised when a file can could not be chmod +w - ''' + """ pass diff --git a/libmat/exiftool.py b/libmat/exiftool.py index aa6849d..0e1fefd 100644 --- a/libmat/exiftool.py +++ b/libmat/exiftool.py @@ -1,5 +1,5 @@ -''' Care about images with help of the amazing (perl) library Exiftool. -''' +""" Care about images with help of the amazing (perl) library Exiftool. +""" import subprocess @@ -7,25 +7,24 @@ import parser class ExiftoolStripper(parser.GenericParser): - ''' A generic stripper class using exiftool as backend - ''' + """ A generic stripper class using exiftool as backend + """ def __init__(self, filename, parser, mime, backup, is_writable, **kwargs): super(ExiftoolStripper, self).__init__(filename, parser, mime, backup, is_writable, **kwargs) - self.allowed = set(['ExifTool Version Number', 'File Name', 'Directory', - 'File Size', 'File Modification Date/Time', 'File Access Date/Time', 'File Permissions', - 'File Type', 'File Type Extension', 'MIME Type', 'Image Width', 'Image Height', - 'Image Size', 'File Inode Change Date/Time', 'Megapixels']) + self.allowed = {'ExifTool Version Number', 'File Name', 'Directory', 'File Size', 'File Modification Date/Time', + 'File Access Date/Time', 'File Permissions', 'File Type', 'File Type Extension', 'MIME Type', + 'Image Width', 'Image Height', 'Image Size', 'File Inode Change Date/Time', 'Megapixels'} self._set_allowed() def _set_allowed(self): - ''' Virtual method. Set the allowed/harmless list of metadata - ''' + """ Virtual method. Set the allowed/harmless list of metadata + """ raise NotImplementedError def remove_all(self): - ''' Remove all metadata with help of exiftool - ''' + """ Remove all metadata with help of exiftool + """ try: if self.backup: self.create_backup_copy() @@ -38,16 +37,16 @@ class ExiftoolStripper(parser.GenericParser): return False def is_clean(self): - ''' Check if the file is clean with the help of exiftool - ''' + """ Check if the file is clean with the help of exiftool + """ return not self.get_meta() def get_meta(self): - ''' Return every harmful meta with help of exiftool. + """ Return every harmful meta with help of exiftool. Exiftool output looks like this: field name : value field name : value - ''' + """ output = subprocess.Popen(['exiftool', self.filename], stdout=subprocess.PIPE).communicate()[0] meta = {} @@ -59,9 +58,9 @@ class ExiftoolStripper(parser.GenericParser): class JpegStripper(ExiftoolStripper): - ''' Care about jpeg files with help + """ Care about jpeg files with help of exiftool - ''' + """ def _set_allowed(self): self.allowed.update(['JFIF Version', 'Resolution Unit', 'X Resolution', 'Y Resolution', 'Encoding Process', @@ -69,9 +68,9 @@ class JpegStripper(ExiftoolStripper): class PngStripper(ExiftoolStripper): - ''' Care about png files with help + """ Care about png files with help of exiftool - ''' + """ def _set_allowed(self): self.allowed.update(['Bit Depth', 'Color Type', 'Compression', 'Filter', 'Interlace', 'Palette', diff --git a/libmat/hachoir_editor/typed_field.py b/libmat/hachoir_editor/typed_field.py index 0f0427b..606d39b 100644 --- a/libmat/hachoir_editor/typed_field.py +++ b/libmat/hachoir_editor/typed_field.py @@ -5,18 +5,21 @@ from hachoir_core.field import ( isInteger, isString) from field import FakeField + class EditableField(FakeField): """ Pure virtual class used to write editable field class. """ _is_altered = False + def __init__(self, parent, name, value=None): FakeField.__init__(self, parent, name) self._value = value def _isAltered(self): return self._is_altered + is_altered = property(_isAltered) def hasValue(self): @@ -24,8 +27,10 @@ class EditableField(FakeField): def _computeSize(self): raise NotImplementedError() + def _getValue(self): return self._value + def _setValue(self, value): self._value = value @@ -34,9 +39,11 @@ class EditableField(FakeField): return self._getValue() else: return FakeField._getValue(self) + def _propSetValue(self, value): self._setValue(value) self._is_altered = True + value = property(_propGetValue, _propSetValue) def _getSize(self): @@ -44,6 +51,7 @@ class EditableField(FakeField): return self._computeSize() else: return FakeField._getSize(self) + size = property(_getSize) def _write(self, output): @@ -55,6 +63,7 @@ class EditableField(FakeField): else: return FakeField.writeInto(self, output) + class EditableFixedField(EditableField): """ Editable field with fixed size. @@ -69,8 +78,10 @@ class EditableFixedField(EditableField): def _getSize(self): return self._size + size = property(_getSize) + class EditableBits(EditableFixedField): def __init__(self, parent, name, *args): if args: @@ -90,14 +101,15 @@ class EditableBits(EditableFixedField): self._is_altered = True def _setValue(self, value): - if not(0 <= value < (1 << self._size)): + if not (0 <= value < (1 << self._size)): raise ValueError("Invalid value, must be in range %s..%s" - % (0, (1 << self._size) - 1)) + % (0, (1 << self._size) - 1)) self._value = value def _write(self, output): output.writeBits(self._size, self._value, self._parent.endian) + class EditableBytes(EditableField): def _setValue(self, value): if not value: raise ValueError( @@ -110,11 +122,12 @@ class EditableBytes(EditableField): def _write(self, output): output.writeBytes(self._value) + class EditableString(EditableField): MAX_SIZE = { - "Pascal8": (1 << 8)-1, - "Pascal16": (1 << 16)-1, - "Pascal32": (1 << 32)-1, + "Pascal8": (1 << 8) - 1, + "Pascal16": (1 << 16) - 1, + "Pascal32": (1 << 32) - 1, } def __init__(self, parent, name, *args, **kw): @@ -152,7 +165,7 @@ class EditableString(EditableField): self._value = value def _computeSize(self): - return (self._prefix_size + len(self._value) + len(self._suffix_str))*8 + return (self._prefix_size + len(self._value) + len(self._suffix_str)) * 8 def _write(self, output): if self._format in GenericString.SUFFIX_FORMAT: @@ -166,6 +179,7 @@ class EditableString(EditableField): output.writeInteger(len(self._value), False, size, self._parent.endian) output.writeBytes(self._value) + class EditableCharacter(EditableFixedField): def __init__(self, parent, name, *args): if args: @@ -190,16 +204,17 @@ class EditableCharacter(EditableFixedField): def _write(self, output): output.writeBytes(self._value) + class EditableInteger(EditableFixedField): VALID_VALUE_SIGNED = { - 8: (-(1 << 8), (1 << 8)-1), - 16: (-(1 << 15), (1 << 15)-1), - 32: (-(1 << 31), (1 << 31)-1), + 8: (-(1 << 8), (1 << 8) - 1), + 16: (-(1 << 15), (1 << 15) - 1), + 32: (-(1 << 31), (1 << 31) - 1), } VALID_VALUE_UNSIGNED = { - 8: (0, (1 << 8)-1), - 16: (0, (1 << 16)-1), - 32: (0, (1 << 32)-1) + 8: (0, (1 << 8) - 1), + 16: (0, (1 << 16) - 1), + 32: (0, (1 << 32) - 1) } def __init__(self, parent, name, *args): @@ -227,14 +242,15 @@ class EditableInteger(EditableFixedField): else: valid = self.VALID_VALUE_UNSIGNED minval, maxval = valid[self._size] - if not(minval <= value <= maxval): + if not (minval <= value <= maxval): raise ValueError("Invalid value, must be in range %s..%s" - % (minval, maxval)) + % (minval, maxval)) self._value = value def _write(self, output): output.writeInteger( - self.value, self._signed, self._size//8, self._parent.endian) + self.value, self._signed, self._size // 8, self._parent.endian) + def createEditableField(fieldset, field): if isInteger(field): @@ -250,4 +266,3 @@ def createEditableField(fieldset, field): else: cls = FakeField return cls(fieldset, field.name) - diff --git a/libmat/images.py b/libmat/images.py index 67c710f..0c4f3e0 100644 --- a/libmat/images.py +++ b/libmat/images.py @@ -1,23 +1,23 @@ -''' Takes care about pictures formats +""" Takes care about pictures formats References: - JFIF: http://www.ecma-international.org/publications/techreports/E-TR-098.htm - PNG: http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PNG.html - PNG: http://www.w3.org/TR/PNG-Chunks.html -''' +""" import parser class JpegStripper(parser.GenericParser): - ''' Represents a jpeg file. + """ Represents a jpeg file. Custom Huffman and Quantization tables are stripped: they may leak some info, and the quality loss is minor. - ''' + """ def _should_remove(self, field): - ''' Return True if the field is compromising - ''' + """ Return True if the field is compromising + """ field_list = frozenset([ 'start_image', # start of the image 'app0', # JFIF data @@ -35,11 +35,11 @@ class JpegStripper(parser.GenericParser): class PngStripper(parser.GenericParser): - ''' Represents a png file - ''' + """ Represents a png file + """ def _should_remove(self, field): - ''' Return True if the field is compromising - ''' + """ Return True if the field is compromising + """ field_list = frozenset([ 'id', 'header', # PNG header diff --git a/libmat/mat.py b/libmat/mat.py index 6e56d54..954b9a3 100644 --- a/libmat/mat.py +++ b/libmat/mat.py @@ -1,7 +1,7 @@ #!/usr/bin/env python -''' Metadata anonymisation toolkit library -''' +""" Metadata anonymisation toolkit library +""" import logging import mimetypes @@ -18,15 +18,15 @@ import libmat.exceptions __version__ = '0.5.3' __author__ = 'jvoisin' -#Silence +# Silence LOGGING_LEVEL = logging.CRITICAL hachoir_core.config.quiet = True fname = '' -#Verbose -#LOGGING_LEVEL = logging.DEBUG -#hachoir_core.config.quiet = False -#logname = 'report.log' +# Verbose +# LOGGING_LEVEL = logging.DEBUG +# hachoir_core.config.quiet = False +# logname = 'report.log' logging.basicConfig(filename=fname, level=LOGGING_LEVEL) @@ -34,10 +34,10 @@ import strippers # this is loaded here because we need LOGGING_LEVEL def get_logo(): - ''' Return the path to the logo - ''' + """ Return the path to the logo + """ if os.path.isfile(os.path.join(os.path.curdir, 'data/mat.png')): - return os.path.join(os.path.curdir,'data/mat.png') + return os.path.join(os.path.curdir, 'data/mat.png') elif os.path.isfile('/usr/share/pixmaps/mat.png'): return '/usr/share/pixmaps/mat.png' elif os.path.isfile('/usr/local/share/pixmaps/mat.png'): @@ -45,8 +45,8 @@ def get_logo(): def get_datafile_path(filename): - ''' Return the path to the given ressource - ''' + """ Return the path to the given ressource + """ if os.path.isfile(os.path.join(os.path.curdir, 'data', filename)): return os.path.join(os.path.curdir, 'data', filename) elif os.path.isfile(os.path.join('/usr/local/share/mat/', filename)): @@ -56,10 +56,10 @@ def get_datafile_path(filename): def list_supported_formats(): - ''' Return a list of all locally supported fileformat. + """ Return a list of all locally supported fileformat. It parses that FORMATS file, and removes locally non-supported formats. - ''' + """ handler = XMLParser() parser = xml.sax.make_parser() parser.setContentHandler(handler) @@ -76,9 +76,10 @@ def list_supported_formats(): class XMLParser(xml.sax.handler.ContentHandler): - ''' Parse the supported format xml, and return a corresponding + """ Parse the supported format xml, and return a corresponding list of dict - ''' + """ + def __init__(self): self.dict = {} self.list = [] @@ -86,15 +87,15 @@ class XMLParser(xml.sax.handler.ContentHandler): self.between = False def startElement(self, name, attrs): - ''' Called when entering into xml tag - ''' + """ Called when entering into xml tag + """ self.between = True self.key = name self.content = '' def endElement(self, name): - ''' Called when exiting a xml tag - ''' + """ Called when exiting a xml tag + """ if name == 'format': # leaving a fileformat section self.list.append(self.dict.copy()) self.dict.clear() @@ -104,15 +105,15 @@ class XMLParser(xml.sax.handler.ContentHandler): self.between = False def characters(self, characters): - ''' Concatenate the content between opening and closing tags - ''' + """ Concatenate the content between opening and closing tags + """ if self.between: self.content += characters def secure_remove(filename): - ''' Securely remove the file - ''' + """ Securely remove the file + """ # I want the file removed, even if it's ro try: os.chmod(filename, 220) @@ -141,9 +142,9 @@ def secure_remove(filename): def create_class_file(name, backup, **kwargs): - ''' Return a $FILETYPEStripper() class, + """ Return a $FILETYPEStripper() class, corresponding to the filetype of the given file - ''' + """ if not os.path.isfile(name): # check if the file exists logging.error('%s is not a valid file' % name) return None @@ -153,7 +154,7 @@ def create_class_file(name, backup, **kwargs): return None if not os.path.getsize(name): - #check if the file is not empty (hachoir crash on empty files) + # check if the file is not empty (hachoir crash on empty files) logging.error('%s is empty' % name) return None diff --git a/libmat/misc.py b/libmat/misc.py index 450f381..b1a551c 100644 --- a/libmat/misc.py +++ b/libmat/misc.py @@ -1,5 +1,5 @@ -''' Care about misc formats -''' +""" Care about misc formats +""" import parser @@ -7,33 +7,34 @@ from bencode import bencode class TorrentStripper(parser.GenericParser): - ''' Represent a torrent file with the help + """ Represent a torrent file with the help of the bencode lib from Petru Paler - ''' + """ + def __init__(self, filename, parser, mime, backup, is_writable, **kwargs): super(TorrentStripper, self).__init__(filename, parser, mime, backup, is_writable, **kwargs) self.fields = frozenset(['announce', 'info', 'name', 'path', 'piece length', 'pieces', - 'length', 'files', 'announce-list', 'nodes', 'httpseeds', 'private', 'root hash']) + 'length', 'files', 'announce-list', 'nodes', 'httpseeds', 'private', 'root hash']) def __get_key_recursively(self, dictionary): - ''' Get recursively all keys from a dict and + """ Get recursively all keys from a dict and its subdicts - ''' + """ for i, j in list(dictionary.items()): if isinstance(j, dict): - return set([i]).union(self.__get_key_recursively(j)) - return set([i]) + return {i}.union(self.__get_key_recursively(j)) + return {i} def is_clean(self): - ''' Check if the file is clean from harmful metadata - ''' + """ Check if the file is clean from harmful metadata + """ with open(self.filename, 'r') as f: decoded = bencode.bdecode(f.read()) return self.fields.issuperset(self.__get_key_recursively(decoded)) def __get_meta_recursively(self, dictionary): - ''' Get recursively all harmful metadata - ''' + """ Get recursively all harmful metadata + """ d = dict() for i, j in list(dictionary.items()): if i not in self.fields: @@ -43,15 +44,15 @@ class TorrentStripper(parser.GenericParser): return d def get_meta(self): - ''' Return a dict with all the meta of the file - ''' + """ Return a dict with all the meta of the file + """ with open(self.filename, 'r') as f: decoded = bencode.bdecode(f.read()) return self.__get_meta_recursively(decoded) def __remove_all_recursively(self, dictionary): - ''' Remove recursively all compromizing fields - ''' + """ Remove recursively all compromizing fields + """ d = dict() for i, j in [i for i in list(dictionary.items()) if i in self.fields]: if isinstance(j, dict): @@ -61,8 +62,8 @@ class TorrentStripper(parser.GenericParser): return d def remove_all(self): - ''' Remove all comprimizing fields - ''' + """ Remove all comprimizing fields + """ decoded = '' with open(self.filename, 'r') as f: decoded = bencode.bdecode(f.read()) diff --git a/libmat/mutagenstripper.py b/libmat/mutagenstripper.py index 403c9a7..be89178 100644 --- a/libmat/mutagenstripper.py +++ b/libmat/mutagenstripper.py @@ -1,5 +1,5 @@ -''' Take care of mutagen-supported formats (audio) -''' +""" Take care of mutagen-supported formats (audio) +""" import parser @@ -23,9 +23,9 @@ class MutagenStripper(parser.GenericParser): return True def get_meta(self): - ''' + """ Return the content of the metadata block is present - ''' + """ metadata = {} if self.mfile.tags: for key, value in self.mfile.tags: diff --git a/libmat/office.py b/libmat/office.py index d020c46..bd4bd97 100644 --- a/libmat/office.py +++ b/libmat/office.py @@ -1,6 +1,6 @@ -''' Care about office's formats +""" Care about office's formats -''' +""" import logging import os @@ -21,14 +21,14 @@ import archive class OpenDocumentStripper(archive.TerminalZipStripper): - ''' An open document file is a zip, with xml file into. + """ An open document file is a zip, with xml file into. The one that interest us is meta.xml - ''' + """ def get_meta(self): - ''' Return a dict with all the meta of the file by + """ Return a dict with all the meta of the file by trying to read the meta.xml file. - ''' + """ metadata = super(OpenDocumentStripper, self).get_meta() zipin = zipfile.ZipFile(self.filename, 'r') try: @@ -49,13 +49,13 @@ class OpenDocumentStripper(archive.TerminalZipStripper): return metadata def remove_all(self): - ''' Removes metadata - ''' + """ Removes metadata + """ return super(OpenDocumentStripper, self).remove_all(ending_blacklist=['meta.xml']) def is_clean(self): - ''' Check if the file is clean from harmful metadatas - ''' + """ Check if the file is clean from harmful metadatas + """ clean_super = super(OpenDocumentStripper, self).is_clean() if clean_super is False: return False @@ -70,20 +70,21 @@ class OpenDocumentStripper(archive.TerminalZipStripper): class OpenXmlStripper(archive.TerminalZipStripper): - ''' Represent an office openxml document, which is like + """ Represent an office openxml document, which is like an opendocument format, with some tricky stuff added. It contains mostly xml, but can have media blobs, crap, ... (I don't like this format.) - ''' + """ + def remove_all(self): return super(OpenXmlStripper, self).remove_all( - beginning_blacklist=('docProps/'), whitelist=('.rels')) + beginning_blacklist='docProps/', whitelist='.rels') def is_clean(self): - ''' Check if the file is clean from harmful metadatas. + """ Check if the file is clean from harmful metadatas. This implementation is faster than something like "return this.get_meta() == {}". - ''' + """ clean_super = super(OpenXmlStripper, self).is_clean() if clean_super is False: return False @@ -96,8 +97,8 @@ class OpenXmlStripper(archive.TerminalZipStripper): return True def get_meta(self): - ''' Return a dict with all the meta of the file - ''' + """ Return a dict with all the meta of the file + """ metadata = super(OpenXmlStripper, self).get_meta() zipin = zipfile.ZipFile(self.filename, 'r') @@ -109,8 +110,9 @@ class OpenXmlStripper(archive.TerminalZipStripper): class PdfStripper(parser.GenericParser): - ''' Represent a PDF file - ''' + """ Represent a PDF file + """ + def __init__(self, filename, parser, mime, backup, is_writable, **kwargs): super(PdfStripper, self).__init__(filename, parser, mime, backup, is_writable, **kwargs) self.uri = 'file://' + os.path.abspath(self.filename) @@ -121,16 +123,16 @@ class PdfStripper(parser.GenericParser): self.pdf_quality = False self.meta_list = frozenset(['title', 'author', 'subject', - 'keywords', 'creator', 'producer', 'metadata']) + 'keywords', 'creator', 'producer', 'metadata']) def is_clean(self): - ''' Check if the file is clean from harmful metadatas - ''' + """ Check if the file is clean from harmful metadatas + """ document = Poppler.Document.new_from_file(self.uri, self.password) return not any(document.get_property(key) for key in self.meta_list) def remove_all(self): - ''' Opening the PDF with poppler, then doing a render + """ Opening the PDF with poppler, then doing a render on a cairo pdfsurface for each pages. http://cairographics.org/documentation/pycairo/2/ @@ -138,7 +140,7 @@ class PdfStripper(parser.GenericParser): The use of an intermediate tempfile is necessary because python-cairo segfaults on unicode. See http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=699457 - ''' + """ document = Poppler.Document.new_from_file(self.uri, self.password) try: output = tempfile.mkstemp()[1] @@ -169,6 +171,7 @@ class PdfStripper(parser.GenericParser): try: import pdfrw # For now, poppler cannot write meta, so we must use pdfrw + logging.debug('Removing %s\'s superficial metadata' % self.filename) trailer = pdfrw.PdfReader(self.output) trailer.Info.Producer = None @@ -183,8 +186,8 @@ class PdfStripper(parser.GenericParser): return True def get_meta(self): - ''' Return a dict with all the meta of the file - ''' + """ Return a dict with all the meta of the file + """ document = Poppler.Document.new_from_file(self.uri, self.password) metadata = {} for key in self.meta_list: diff --git a/libmat/parser.py b/libmat/parser.py index 1765da8..eed3140 100644 --- a/libmat/parser.py +++ b/libmat/parser.py @@ -1,5 +1,5 @@ -''' Parent class of all parser -''' +""" Parent class of all parser +""" import os import shutil @@ -22,8 +22,8 @@ FIELD = object() class GenericParser(object): - ''' Parent class of all parsers - ''' + """ Parent class of all parsers + """ def __init__(self, filename, parser, mime, backup, is_writable, **kwargs): self.filename = '' self.parser = parser @@ -40,15 +40,15 @@ class GenericParser(object): self.output = hachoir_core.cmd_line.unicodeFilename(output) def __del__(self): - ''' Remove tempfile if it was not used - ''' + """ Remove tempfile if it was not used + """ if os.path.exists(self.output): mat.secure_remove(self.output) def is_clean(self): - ''' + """ Check if the file is clean from harmful metadatas - ''' + """ for field in self.editor: if self._should_remove(field): return self._is_clean(self.editor) @@ -65,16 +65,16 @@ class GenericParser(object): return True def remove_all(self): - ''' Remove all compromising fields - ''' + """ Remove all compromising fields + """ state = self._remove_all(self.editor) hachoir_core.field.writeIntoFile(self.editor, self.output) self.do_backup() return state def _remove_all(self, fieldset): - ''' Recursive way to handle tree metadatas - ''' + """ Recursive way to handle tree metadatas + """ try: for field in fieldset: remove = self._should_remove(field) @@ -87,20 +87,20 @@ class GenericParser(object): return False def _remove(self, fieldset, field): - ''' Delete the given field - ''' + """ Delete the given field + """ del fieldset[field] def get_meta(self): - ''' Return a dict with all the meta of the file - ''' + """ Return a dict with all the meta of the file + """ metadata = {} self._get_meta(self.editor, metadata) return metadata def _get_meta(self, fieldset, metadata): - ''' Recursive way to handle tree metadatas - ''' + """ Recursive way to handle tree metadatas + """ for field in fieldset: remove = self._should_remove(field) if remove: @@ -112,22 +112,22 @@ class GenericParser(object): self._get_meta(field, None) def _should_remove(self, key): - ''' Return True if the field is compromising + """ Return True if the field is compromising abstract method - ''' + """ raise NotImplementedError def create_backup_copy(self): - ''' Create a backup copy - ''' + """ Create a backup copy + """ shutil.copy2(self.filename, self.filename + '.bak') def do_backup(self): - ''' Keep a backup of the file if asked. + """ Keep a backup of the file if asked. The process of double-renaming is not very elegant, but it greatly simplify new strippers implementation. - ''' + """ if self.backup: shutil.move(self.filename, self.filename + '.bak') else: diff --git a/libmat/strippers.py b/libmat/strippers.py index d873a39..008442e 100644 --- a/libmat/strippers.py +++ b/libmat/strippers.py @@ -1,5 +1,5 @@ -''' Manage which fileformat can be processed -''' +""" Manage which fileformat can be processed +""" import archive import audio diff --git a/mat b/mat index 18dd456..f66db41 100755 --- a/mat +++ b/mat @@ -1,50 +1,48 @@ #!/usr/bin/env python -''' +""" Metadata anonymisation toolkit - CLI edition -''' +""" import sys -import xml.sax import argparse import os import hachoir_core from libmat import mat -from libmat import strippers from libmat import archive def parse(): - ''' Get, and parse options passed to the program - ''' + """ Get, and parse options passed to the program + """ parser = argparse.ArgumentParser(description='Metadata anonymisation toolkit') parser.add_argument('files', nargs='*') options = parser.add_argument_group('Options') options.add_argument('-a', '--add2archive', action='store_true', - help='add to output archive non-supported filetypes (Off by default)') + help='add to output archive non-supported filetypes (Off by default)') options.add_argument('-b', '--backup', '-b', action='store_true', - help='keep a backup copy') + help='keep a backup copy') options.add_argument('-L', '--low-pdf-quality', '-L', action='store_true', - help='produces a lighter, but lower quality PDF') + help='produces a lighter, but lower quality PDF') info = parser.add_argument_group('Information') info.add_argument('-c', '--check', action='store_true', - help='check if a file is free of harmful metadatas') + help='check if a file is free of harmful metadatas') info.add_argument('-d', '--display', action='store_true', - help='list all the harmful metadata of a file without removing them') + help='list all the harmful metadata of a file without removing them') info.add_argument('-l', '--list', action='store_true', - help='list all supported fileformats') + help='list all supported fileformats') info.add_argument('-v', '--version', action='version', - version='MAT %s - Hachoir %s' % (mat.__version__, hachoir_core.__version__)) + version='MAT %s - Hachoir %s' % (mat.__version__, hachoir_core.__version__)) return parser.parse_args() def list_meta(class_file, filename, add2archive): - ''' Print all the metadata of 'filename' on stdout - ''' + """ Print all the metadata of 'filename' on stdout + """ print('[+] File %s :' % filename) if class_file.is_clean(): print('No harmful metadata found') @@ -58,7 +56,7 @@ def list_meta(class_file, filename, add2archive): def is_clean(class_file, filename, add2archive): - ''' Tell if 'filename' is clean or not ''' + """ Tell if 'filename' is clean or not """ if class_file.is_clean(): print('[+] %s is clean' % filename) else: @@ -67,7 +65,7 @@ def is_clean(class_file, filename, add2archive): def clean_meta(class_file, filename, add2archive): - ''' Clean the file 'filename' ''' + """ Clean the file 'filename' """ if not class_file.is_writable: print('[-] %s is not writable' % filename) return 1 @@ -78,8 +76,8 @@ def clean_meta(class_file, filename, add2archive): if is_archive and not is_terminal: unsupported_list = class_file.list_unsupported() if type(unsupported_list) == list and unsupported_list: - print('[-] Can not clean: %s.'\ - 'It contains unsupported filetypes:' % filename) + print('[-] Can not clean: %s.' + 'It contains unsupported filetypes:' % filename) for i in unsupported_list: print('- %s' % i) return 1 @@ -92,7 +90,7 @@ def clean_meta(class_file, filename, add2archive): def list_supported(): - ''' Print all supported fileformat, and exit ''' + """ Print all supported fileformat """ for item in mat.list_supported_formats(): print('%s (%s)' % (item['name'], item['extension'])) print('\tsupport: %s' % item['support']) @@ -100,20 +98,20 @@ def list_supported(): print('\tmethod: %s' % item['method']) print('\tremaining: %s' % item['remaining']) print('\n') - sys.exit(0) def main(): - ''' Main function: get args and launch the appropriate function ''' + """ Main function: get args and launch the appropriate function """ args = parse() - #func receives the function corresponding to the options given as parameters + # func receives the function corresponding to the options given as parameters if args.display: # only print metadatas func = list_meta elif args.check: # only check if the file is clean func = is_clean elif args.list: # print the list of all supported format list_supported() + sys.exit(0) else: # clean the file func = clean_meta @@ -125,12 +123,12 @@ def main(): filename = args.files.pop() if os.path.isdir(filename): for root, sub, files in os.walk(filename): - for file in files: - args.files.append(os.path.join(root, file)) + for fname in files: + args.files.append(os.path.join(root, fname)) continue class_file = mat.create_class_file(filename, args.backup, - add2archive=args.add2archive, low_pdf_quality=args.low_pdf_quality) + add2archive=args.add2archive, low_pdf_quality=args.low_pdf_quality) if class_file: ret += func(class_file, filename, args.add2archive) else: @@ -138,5 +136,6 @@ def main(): print('[-] Unable to process %s' % filename) sys.exit(ret) + if __name__ == '__main__': main() diff --git a/mat-gui b/mat-gui index dbea525..f481b07 100755 --- a/mat-gui +++ b/mat-gui @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -* -''' Metadata anonymisation toolkit - GUI edition ''' +""" Metadata anonymisation toolkit - GUI edition """ from gi.repository import GObject, Gtk, GLib from gi.repository import Gdk, GdkPixbuf @@ -22,17 +22,19 @@ logging.basicConfig(level=mat.LOGGING_LEVEL) class CFile(GObject.Object): - ''' Contain the "parser" class of the file "filename" + """ Contain the "parser" class of the file "filename" This class exist just to be "around" my parser.Generic_parser class, since the Gtk.ListStore does not accept it because it does not extends Gobject.Object - ''' + """ + def __init__(self, filename, **kwargs): self.file = mat.create_class_file(filename, 0, **kwargs) class GUI(object): - ''' Main GUI class ''' + """ Main GUI class """ + def __init__(self): # Preferences self.add2archive = False @@ -67,7 +69,7 @@ class GUI(object): self.window.show_all() def __init_supported_popup(self): - ''' Initialise the "supported formats" popup ''' + """ Initialise the "supported formats" popup """ self.supported_dict = mat.XMLParser() xml_parser = xml.sax.make_parser() xml_parser.setContentHandler(self.supported_dict) @@ -89,7 +91,7 @@ class GUI(object): self.cb_update_supported_popup(supported_cbox) # to initially fill the dialog def __set_drag_treeview(self): - ''' Setup the drag'n'drop handling by the treeview ''' + """ Setup the drag'n'drop handling by the treeview """ self.treeview.drag_dest_set( Gtk.DestDefaults.MOTION | Gtk.DestDefaults.HIGHLIGHT | @@ -99,17 +101,18 @@ class GUI(object): targets.add_uri_targets(80) self.treeview.drag_dest_set_target_list(targets) - def cb_hide_widget(self, widget, _): - ''' This function is a little hack to hide instead + @staticmethod + def cb_hide_widget(widget, _): + """ This function is a little hack to hide instead of close re-usable popups, like supported-fileformats, - popup-metadata, ...''' + popup-metadata, ...""" widget.hide() return False def cb_update_supported_popup(self, window): - ''' Fill GtkEntries of the supported_format_popups + """ Fill GtkEntries of the supported_format_popups with corresponding data. - ''' + """ index = window.get_model()[window.get_active_iter()][0] support = self.builder.get_object('supported_support') support.set_text(self.supported_dict.list[index]['support']) @@ -120,15 +123,16 @@ class GUI(object): remaining = self.builder.get_object('supported_remaining').get_buffer() remaining.set_text(self.supported_dict.list[index]['remaining']) - def cb_close_application(self, _): - ''' Close the application ''' + @staticmethod + def cb_close_application(_): + """ Close the application """ Gtk.main_quit() def cb_add_files(self, button): - ''' Add the files chosen by the filechooser ("Add" button) ''' + """ Add the files chosen by the filechooser ("Add" button) """ chooser = Gtk.FileChooserDialog(title=_('Choose files'), - parent=self.window, action=Gtk.FileChooserAction.OPEN, - buttons=(Gtk.STOCK_OK, 0, Gtk.STOCK_CANCEL, 1)) + parent=self.window, action=Gtk.FileChooserAction.OPEN, + buttons=(Gtk.STOCK_OK, 0, Gtk.STOCK_CANCEL, 1)) chooser.set_default_response(0) chooser.set_select_multiple(True) @@ -151,9 +155,9 @@ class GUI(object): chooser.destroy() def cb_popup_metadata(self, widget, row, col): - ''' Popup that display on double-click + """ Popup that display on double-click metadata from a file - ''' + """ metadataPopupListStore = self.builder.get_object('MetadataPopupListStore') metadataPopupListStore.clear() if self.liststore[row][0].file.is_clean(): @@ -171,7 +175,7 @@ class GUI(object): popup_metadata.hide() def cb_about_popup(self, button): - ''' About popup ''' + """ About popup """ w = Gtk.AboutDialog() w.set_authors(['Julien (jvoisin) Voisin', ]) w.set_artists(['Marine BenoƮt', ]) @@ -187,26 +191,26 @@ class GUI(object): w.destroy() def cb_supported_popup(self, w): - ''' Show the "supported formats" popup''' + """ Show the "supported formats" popup""" dialog = self.builder.get_object('SupportedWindow') dialog.show_all() dialog.run() dialog.hide() def cb_clear_list(self, _): - ''' Clear the file list ''' + """ Clear the file list """ self.liststore.clear() def cb_mat_check(self, button): - ''' Callback for checking files ''' + """ Callback for checking files """ self.__process_files(self.__mat_check) def cb_mat_clean(self, button): - ''' Callback for cleaning files ''' + """ Callback for cleaning files """ self.__process_files(self.__mat_clean) def cb_preferences_popup(self, button): - ''' Preferences popup ''' + """ Preferences popup """ dialog = Gtk.Dialog(_('Preferences'), self.window, 0, (Gtk.STOCK_OK, 0)) dialog.connect('delete-event', self.cb_hide_widget) dialog.set_resizable(False) @@ -242,15 +246,15 @@ non-anonymised) file to output archive')) dialog.hide() def cb_drag_data_received(self, widget, context, x, y, selection, target_type, timestamp): - ''' This function is called when something is + """ This function is called when something is drag'n'droped into mat. It basically add files. - ''' + """ def clean_path(url): - ''' Since the dragged urls are ugly, + """ Since the dragged urls are ugly, we need to process them - ''' + """ url = urllib2.unquote(url) # unquote url url = url.decode('utf-8') # decode in utf-8 if url.startswith('file:\\\\\\'): # windows @@ -265,7 +269,7 @@ non-anonymised) file to output archive')) GLib.idle_add(self.populate(cleaned_urls).next) # asynchronous processing def __add_file_to_treeview(self, filename): - ''' Add a file to the list if its format is supported ''' + """ Add a file to the list if its format is supported """ cf = CFile(filename, add2archive=self.add2archive, low_pdf_quality=self.pdf_quality) if cf.file and cf.file.is_writable: self.liststore.append([cf, cf.file.basename, _('Unknown')]) @@ -273,7 +277,7 @@ non-anonymised) file to output archive')) return True def __process_files(self, func): - ''' Launch the function "func" in a asynchronous way ''' + """ Launch the function "func" in a asynchronous way """ iterator = self.treeview.get_selection().get_selected_rows()[1] if not iterator: # if nothing is selected : select everything iterator = range(len(self.liststore)) @@ -281,14 +285,14 @@ non-anonymised) file to output archive')) GLib.idle_add(task.next) def __invert(self, button, name): - ''' Invert a preference state ''' + """ Invert a preference state """ if name == 'pdf_quality': self.pdf_quality = not self.pdf_quality elif name == 'add2archive': self.add2archive = not self.add2archive def populate(self, filenames): - ''' Append selected files by add_file to the self.liststore ''' + """ Append selected files by add_file to the self.liststore """ not_supported = [] for filename in filenames: # filenames : all selected files/folders if os.path.isdir(filename): # if "filename" is a directory @@ -308,11 +312,11 @@ non-anonymised) file to output archive')) yield False def __popup_non_supported(self, filelist): - ''' Popup that warn the user about the unsupported files + """ Popup that warn the user about the unsupported files that he want to process - ''' + """ dialog = Gtk.Dialog(title=_('Not-supported'), parent=self.window, - flags=Gtk.DialogFlags.MODAL, buttons=(Gtk.STOCK_OK, 0)) + flags=Gtk.DialogFlags.MODAL, buttons=(Gtk.STOCK_OK, 0)) dialog.set_size_request(220, 180) vbox = Gtk.VBox(spacing=5) sc = Gtk.ScrolledWindow() @@ -345,12 +349,12 @@ non-anonymised) file to output archive')) dialog.destroy() def __popup_archive(self, file_name, files_list): - ''' Popup that shows the user what files + """ Popup that shows the user what files are not going to be include into to outputed archive - ''' + """ dialog = Gtk.Dialog(title=_('Non-supported files in archive'), parent=self.window, - flags=Gtk.DialogFlags.MODAL, buttons=(_('Clean'), 0)) + flags=Gtk.DialogFlags.MODAL, buttons=(_('Clean'), 0)) dialog.set_size_request(220, 180) vbox = Gtk.VBox(spacing=5) sc = Gtk.ScrolledWindow() @@ -360,7 +364,7 @@ non-anonymised) file to output archive')) dialog.get_content_area().pack_start(sc, True, True, 0) store = Gtk.ListStore(bool, str) for i in files_list: # store.extend is not supported, wtf?! - store.append([0,os.path.basename(i)]) + store.append([0, os.path.basename(i)]) treeview = Gtk.TreeView(store) column_toggle = Gtk.TreeViewColumn(_('Include')) @@ -375,15 +379,17 @@ non-anonymised) file to output archive')) cellrenderer_toggle = Gtk.CellRendererToggle() column_toggle.pack_start(cellrenderer_toggle, True) column_toggle.add_attribute(cellrenderer_toggle, 'active', 0) + def cell_toggled(widget, path, model): model[path][0] = not model[path][0] + cellrenderer_toggle.connect('toggled', cell_toggled, store) vbox.pack_start(Gtk.Label(_('MAT is not able to clean the' - ' following files, found in the %s archive') % file_name), False, False, 0) + ' following files, found in the %s archive') % file_name), False, False, 0) label = Gtk.Label() label.set_markup('Select the files you want to include' - ' in the cleaned up archive anyway.') + ' in the cleaned up archive anyway.') vbox.pack_start(label, False, False, 0) vbox.pack_start(treeview, True, True, 0) @@ -393,7 +399,7 @@ non-anonymised) file to output archive')) return [i[1] for i in store if i[0]] def __mat_check(self, iterator): - ''' Check elements in iterator are clean ''' + """ Check elements in iterator are clean """ for line in iterator: # for each file in selection msg = _('Checking %s') % self.liststore[line][1].decode('utf-8', 'replace') logging.info(msg) @@ -408,7 +414,7 @@ non-anonymised) file to output archive')) yield False def __mat_clean(self, iterator): - ''' Clean elements in iterator ''' + """ Clean elements in iterator """ for line in iterator: # for each file in selection msg = _('Cleaning %s') % self.liststore[line][1].decode('utf-8', 'replace') logging.info(msg) @@ -430,6 +436,7 @@ non-anonymised) file to output archive')) self.statusbar.push(0, _('Ready')) yield False + if __name__ == '__main__': gettext.install('MAT', unicode=True) diff --git a/nautilus/nautilus-mat.py b/nautilus/nautilus-mat.py index f270ed8..11a47f3 100644 --- a/nautilus/nautilus-mat.py +++ b/nautilus/nautilus-mat.py @@ -1,14 +1,16 @@ #! /usr/bin/python -''' This file is an extension for the Nautilus +""" This file is an extension for the Nautilus file manager, to provide a contextual menu to clean metadata -''' +""" import logging import urllib + try: import gettext + gettext.install("mat") except: logging.warning("Failed to initialise gettext") @@ -54,7 +56,8 @@ class MatExtension(GObject.GObject, Nautilus.MenuProvider): item.connect('activate', self.menu_activate_cb, file) return item, - def show_message(self, message, type=Gtk.MessageType.INFO): + @staticmethod + def show_message(message, type=Gtk.MessageType.INFO): dialog = Gtk.MessageDialog(parent=None, flags=Gtk.DialogFlags.MODAL, type=type, @@ -71,8 +74,8 @@ class MatExtension(GObject.GObject, Nautilus.MenuProvider): file_path = urllib.unquote(file.get_uri()[7:]) class_file = libmat.mat.create_class_file(file_path, - backup=True, - add2archive=False) + backup=True, + add2archive=False) if class_file: if class_file.is_clean(): self.show_message(_("%s is already clean") % file_path) diff --git a/setup.py b/setup.py index 362e2a8..72a216f 100755 --- a/setup.py +++ b/setup.py @@ -7,33 +7,33 @@ from DistUtilsExtra.command import * __version__ = '0.5.3' -#Remove MANIFEST file, since distutils -#doesn't properly update it when -#the contents of directories changes. +# Remove MANIFEST file, since distutils +# doesn't properly update it when +# the contents of directories changes. if os.path.exists('MANIFEST'): os.remove('MANIFEST') setup( - name = 'MAT', - version = __version__, - description = 'Metadata Anonymisation Toolkit', - long_description = 'A Metadata Anonymisation Toolkit in Python, using python-hachoir', - author = 'jvoisin', - author_email = 'julien.voisin@dustri.org', - platforms = 'linux', - license = 'GPLv2', - url = 'https://mat.boum.org', - packages = ['libmat', 'libmat.hachoir_editor', 'libmat.bencode'], - scripts = ['mat', 'mat-gui'], - data_files = [ - ( 'share/applications', ['mat.desktop'] ), - ( 'share/mat', ['data/FORMATS', 'data/mat.glade'] ), - ( 'share/pixmaps', ['data/mat.png'] ), - ( 'share/doc/mat', ['README', 'README.security'] ), - ( 'share/man/man1', ['mat.1', 'mat-gui.1'] ), - ( 'share/nautilus-python/extensions', ['nautilus/nautilus-mat.py']) + name='MAT', + version=__version__, + description='Metadata Anonymisation Toolkit', + long_description='A Metadata Anonymisation Toolkit in Python, using python-hachoir', + author='jvoisin', + author_email='julien.voisin@dustri.org', + platforms='linux', + license='GPLv2', + url='https://mat.boum.org', + packages=['libmat', 'libmat.hachoir_editor', 'libmat.bencode'], + scripts=['mat', 'mat-gui'], + data_files=[ + ('share/applications', ['mat.desktop']), + ('share/mat', ['data/FORMATS', 'data/mat.glade']), + ('share/pixmaps', ['data/mat.png']), + ('share/doc/mat', ['README', 'README.security']), + ('share/man/man1', ['mat.1', 'mat-gui.1']), + ('share/nautilus-python/extensions', ['nautilus/nautilus-mat.py']) ], - cmdclass = { + cmdclass={ 'build': build_extra.build_extra, 'build_i18n': build_i18n.build_i18n, 'build_help': build_help.build_help, diff --git a/test/clitest.py b/test/clitest.py index 13d545a..0a29c02 100644 --- a/test/clitest.py +++ b/test/clitest.py @@ -1,9 +1,9 @@ #!/usr/bin/env python # -*- coding: utf-8 -* -''' +""" Unit test for the CLI interface -''' +""" import os import unittest @@ -17,18 +17,19 @@ import test class TestRemovecli(test.MATTest): - ''' + """ test if cli correctly remove metadatas - ''' + """ + def test_remove(self): - '''make sure that the cli remove all compromizing meta''' + """make sure that the cli remove all compromizing meta""" for _, dirty in self.file_list: subprocess.call(['../mat', '--add2archive', dirty]) current_file = mat.create_class_file(dirty, False, add2archive=True, low_pdf_quality=True) self.assertTrue(current_file.is_clean()) def test_remove_empty(self): - '''Test removal with clean files\n''' + """Test removal with clean files\n""" for clean, _ in self.file_list: subprocess.call(['../mat', '--add2archive', clean]) current_file = mat.create_class_file(clean, False, add2archive=True, low_pdf_quality=True) @@ -36,77 +37,81 @@ class TestRemovecli(test.MATTest): class TestListcli(test.MATTest): - ''' + """ test if cli correctly display metadatas - ''' + """ + def test_list_clean(self): - '''check if get_meta returns meta''' + """check if get_meta returns meta""" for clean, _ in self.file_list: proc = subprocess.Popen(['../mat', '-d', clean], - stdout=subprocess.PIPE) + stdout=subprocess.PIPE) stdout, _ = proc.communicate() self.assertEqual(str(stdout).strip('\n'), "[+] File %s \ :\nNo harmful metadata found" % clean) def test_list_dirty(self): - '''check if get_meta returns all the expected meta''' + """check if get_meta returns all the expected meta""" for _, dirty in self.file_list: proc = subprocess.Popen(['../mat', '-d', dirty], - stdout=subprocess.PIPE) + stdout=subprocess.PIPE) stdout, _ = proc.communicate() self.assertNotEqual(str(stdout), "[+] File %s :\n No\ harmul metadata found" % dirty) class TestisCleancli(test.MATTest): - ''' + """ check if cli correctly check if a file is clean or not - ''' + """ + def test_clean(self): - '''test is_clean on clean files''' + """test is_clean on clean files""" for clean, _ in self.file_list: proc = subprocess.Popen(['../mat', '-c', clean], - stdout=subprocess.PIPE) + stdout=subprocess.PIPE) stdout, _ = proc.communicate() self.assertEqual(str(stdout).strip('\n'), '[+] %s is clean' % clean) def test_dirty(self): - '''test is_clean on dirty files''' + """test is_clean on dirty files""" for _, dirty in self.file_list: proc = subprocess.Popen(['../mat', '-c', dirty], - stdout=subprocess.PIPE) + stdout=subprocess.PIPE) stdout, _ = proc.communicate() self.assertEqual(str(stdout).strip('\n'), '[+] %s is not clean' % dirty) class TestFileAttributes(unittest.TestCase): - ''' + """ test various stuffs about files (readable, writable, exist, ...) - ''' + """ + def test_not_writtable(self): - ''' test MAT's behaviour on non-writable file''' + """ test MAT's behaviour on non-writable file""" proc = subprocess.Popen(['../mat', 'not_writtable'], - stdout=subprocess.PIPE) + stdout=subprocess.PIPE) stdout, _ = proc.communicate() self.assertEqual(str(stdout).strip('\n'), '[-] %s is not writable' % 'not_writtable') def test_not_exist(self): - ''' test MAT's behaviour on non-existent file''' + """ test MAT's behaviour on non-existent file""" proc = subprocess.Popen(['../mat', 'ilikecookies'], - stdout=subprocess.PIPE) + stdout=subprocess.PIPE) stdout, _ = proc.communicate() self.assertEqual(str(stdout).strip('\n'), 'Unable to process %s' % 'ilikecookies') def test_empty(self): - ''' test MAT's behaviour on empty file''' + """ test MAT's behaviour on empty file""" proc = subprocess.Popen(['../mat', 'empty_file'], stdout=subprocess.PIPE) stdout, _ = proc.communicate() self.assertEqual(str(stdout).strip('\n'), 'Unable to process %s' % 'ilikecookies') + class TestUnsupported(test.MATTest): def test_abort_unsupported(self): - ''' test if the cli aborts on unsupported files - ''' + """ test if the cli aborts on unsupported files + """ tarpath = os.path.join(self.tmpdir, "test.tar.bz2") tar = tarfile.open(tarpath, "w") for f in ('../mat.desktop', '../README.security', '../setup.py'): @@ -114,12 +119,13 @@ class TestUnsupported(test.MATTest): tar.close() proc = subprocess.Popen(['../mat', tarpath], stdout=subprocess.PIPE) stdout, _ = proc.communicate() - self.assertTrue('It contains unsupported filetypes:'\ - '\n- mat.desktop\n- README.security\n- setup.py\n' - in str(stdout)) + self.assertTrue('It contains unsupported filetypes:' \ + '\n- mat.desktop\n- README.security\n- setup.py\n' + in str(stdout)) + def get_tests(): - ''' Return every clitests''' + """ Return every clitests""" suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestRemovecli)) suite.addTest(unittest.makeSuite(TestListcli)) diff --git a/test/libtest.py b/test/libtest.py index 1b25f86..25d7426 100644 --- a/test/libtest.py +++ b/test/libtest.py @@ -1,9 +1,9 @@ #!/usr/bin/env python # -*- coding: utf-8 -* -''' +""" Unit test for the library -''' +""" import os import sys @@ -18,10 +18,11 @@ import libmat class TestRemovelib(test.MATTest): - ''' test the remove_all() method - ''' + """ test the remove_all() method + """ + def test_remove(self): - '''make sure that the lib remove all compromizing meta''' + """make sure that the lib remove all compromizing meta""" for _, dirty in self.file_list: current_file = libmat.mat.create_class_file(dirty, False, add2archive=True) current_file.remove_all() @@ -29,7 +30,7 @@ class TestRemovelib(test.MATTest): self.assertTrue(current_file.is_clean()) def test_remove_empty(self): - '''Test removal with clean files''' + """Test removal with clean files""" for clean, _ in self.file_list: current_file = libmat.mat.create_class_file(clean, False, add2archive=True) current_file.remove_all() @@ -38,73 +39,78 @@ class TestRemovelib(test.MATTest): class TestListlib(test.MATTest): - ''' test the get_meta() method - ''' + """ test the get_meta() method + """ + def test_list(self): - '''check if get_meta returns metadata''' + """check if get_meta returns metadata""" for _, dirty in self.file_list: current_file = libmat.mat.create_class_file(dirty, False, add2archive=True) self.assertIsNotNone(current_file.get_meta()) def testlist_list_empty(self): - '''check that a listing of a clean file returns an empty dict''' + """check that a listing of a clean file returns an empty dict""" for clean, _ in self.file_list: current_file = libmat.mat.create_class_file(clean, False, add2archive=True) self.assertEqual(current_file.get_meta(), dict()) class TestisCleanlib(test.MATTest): - ''' Test the is_clean() method - ''' + """ Test the is_clean() method + """ + def test_dirty(self): - '''test is_clean on dirty files''' + """test is_clean on dirty files""" for _, dirty in self.file_list: current_file = libmat.mat.create_class_file(dirty, False, add2archive=True) self.assertFalse(current_file.is_clean()) def test_clean(self): - '''test is_clean on clean files''' + """test is_clean on clean files""" for clean, _ in self.file_list: current_file = libmat.mat.create_class_file(clean, False, add2archive=True) self.assertTrue(current_file.is_clean()) class TestFileAttributes(unittest.TestCase): - ''' + """ test various stuffs about files (readable, writable, exist, ...) - ''' + """ + def test_not_exist(self): - ''' test MAT's behaviour on non-existent file''' + """ test MAT's behaviour on non-existent file""" self.assertFalse(libmat.mat.create_class_file('non_existent_file', False, add2archive=True)) def test_empty(self): - ''' test MAT's behaviour on empty file''' + """ test MAT's behaviour on empty file""" open('empty_file', 'a').close() self.assertFalse(libmat.mat.create_class_file('empty_file', False, add2archive=True)) os.remove('empty_file') class TestSecureRemove(unittest.TestCase): - ''' Test the secure_remove function - ''' + """ Test the secure_remove function + """ + def test_remove_existing(self): - ''' test the secure removal of an existing file - ''' + """ test the secure removal of an existing file + """ _, file_to_remove = tempfile.mkstemp() self.assertTrue(libmat.mat.secure_remove(file_to_remove)) def test_remove_fail(self): - ''' test the secure removal of an non-removable file - ''' + """ test the secure removal of an non-removable file + """ self.assertRaises(libmat.exceptions.UnableToWriteFile, libmat.mat.secure_remove, '/NOTREMOVABLE') class TestArchiveProcessing(test.MATTest): - ''' Test archives processing - ''' + """ Test archives processing + """ + def test_remove_bz2(self): - ''' Test MAT's ability to process .tar.bz2 - ''' + """ Test MAT's ability to process .tar.bz2 + """ tarpath = os.path.join(self.tmpdir, "test.tar.bz2") tar = tarfile.open(tarpath, "w:bz2") for clean, dirty in self.file_list: @@ -117,8 +123,8 @@ class TestArchiveProcessing(test.MATTest): self.assertTrue(current_file.is_clean()) def test_remove_tar(self): - ''' Test MAT on tar files - ''' + """ Test MAT on tar files + """ tarpath = os.path.join(self.tmpdir, "test.tar") tar = tarfile.open(tarpath, "w") for clean, dirty in self.file_list: @@ -131,8 +137,8 @@ class TestArchiveProcessing(test.MATTest): self.assertTrue(current_file.is_clean()) def test_remove_gz(self): - ''' Test MAT on tar.gz files - ''' + """ Test MAT on tar.gz files + """ tarpath = os.path.join(self.tmpdir, "test.tar.gz") tar = tarfile.open(tarpath, "w") for clean, dirty in self.file_list: @@ -145,8 +151,8 @@ class TestArchiveProcessing(test.MATTest): self.assertTrue(current_file.is_clean()) def test_get_unsupported(self): - ''' Test the get_unsupported feature, used by the GUI - ''' + """ Test the get_unsupported feature, used by the GUI + """ tarpath = os.path.join(self.tmpdir, "test.tar.bz2") tar = tarfile.open(tarpath, "w") for f in ('../mat.desktop', '../README.security', '../setup.py'): @@ -154,7 +160,7 @@ class TestArchiveProcessing(test.MATTest): tar.close() current_file = libmat.mat.create_class_file(tarpath, False, add2archive=False) unsupported_files = set(current_file.is_clean(list_unsupported=True)) - self.assertEqual(unsupported_files, set(('mat.desktop', 'README.security', 'setup.py'))) + self.assertEqual(unsupported_files, {'mat.desktop', 'README.security', 'setup.py'}) def test_archive_unwritable_content(self): path = os.path.join(self.tmpdir, './unwritable_content.zip') @@ -164,8 +170,9 @@ class TestArchiveProcessing(test.MATTest): current_file = libmat.mat.create_class_file(path, False, add2archive=False) self.assertTrue(current_file.is_clean()) + def get_tests(): - ''' Returns every libtests''' + """ Returns every libtests""" suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestRemovelib)) suite.addTest(unittest.makeSuite(TestListlib)) diff --git a/test/test.py b/test/test.py index ed68e13..40cbf0c 100644 --- a/test/test.py +++ b/test/test.py @@ -1,13 +1,12 @@ #!/usr/bin/env python # -*- coding: utf-8 -* -''' +""" Class for the testing suite : - get the list of all test files - create a copy of them on start - remove the copy on end -''' - +""" import shutil import os @@ -42,27 +41,28 @@ except ImportError: class MATTest(unittest.TestCase): - ''' + """ Parent class of all test-functions - ''' + """ + def setUp(self): - ''' + """ Create working copy of the clean and the dirty file in the TMP dir - ''' + """ self.file_list = [] self.tmpdir = tempfile.mkdtemp() - for clean, dirty in FILE_LIST: - clean_dir = os.path.join(self.tmpdir, clean) - dirty_dir = os.path.join(self.tmpdir, dirty) - shutil.copy2(clean, clean_dir) - shutil.copy2(dirty, dirty_dir) + for clean_file, dirty_file in FILE_LIST: + clean_dir = os.path.join(self.tmpdir, clean_file) + dirty_dir = os.path.join(self.tmpdir, dirty_file) + shutil.copy2(clean_file, clean_dir) + shutil.copy2(dirty_file, dirty_dir) self.file_list.append((clean_dir, dirty_dir)) def tearDown(self): - ''' + """ Remove the tmp folder - ''' + """ for root, dirs, files in os.walk(self.tmpdir): for d in dirs + files: os.chmod(os.path.join(root, d), 0o777) @@ -78,4 +78,4 @@ if __name__ == '__main__': SUITE.addTests(libtest.get_tests()) ret = unittest.TextTestRunner(verbosity=VERBOSITY).run(SUITE).wasSuccessful() - sys.exit(ret == False) + sys.exit(ret is False) -- cgit v1.3