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