diff options
| -rw-r--r-- | libmat/__init__.py | 1 | ||||
| -rw-r--r-- | libmat/archive.py | 128 | ||||
| -rw-r--r-- | libmat/audio.py | 28 | ||||
| -rw-r--r-- | libmat/bencode/__init__.py | 1 | ||||
| -rw-r--r-- | libmat/bencode/bencode.py | 33 | ||||
| -rw-r--r-- | libmat/exceptions.py | 12 | ||||
| -rw-r--r-- | libmat/exiftool.py | 39 | ||||
| -rw-r--r-- | libmat/hachoir_editor/typed_field.py | 47 | ||||
| -rw-r--r-- | libmat/images.py | 20 | ||||
| -rw-r--r-- | libmat/mat.py | 55 | ||||
| -rw-r--r-- | libmat/misc.py | 39 | ||||
| -rw-r--r-- | libmat/mutagenstripper.py | 8 | ||||
| -rw-r--r-- | libmat/office.py | 55 | ||||
| -rw-r--r-- | libmat/parser.py | 48 | ||||
| -rw-r--r-- | libmat/strippers.py | 4 | ||||
| -rwxr-xr-x | mat | 51 | ||||
| -rwxr-xr-x | mat-gui | 93 | ||||
| -rw-r--r-- | nautilus/nautilus-mat.py | 13 | ||||
| -rwxr-xr-x | setup.py | 44 | ||||
| -rw-r--r-- | test/clitest.py | 68 | ||||
| -rw-r--r-- | test/libtest.py | 79 | ||||
| -rw-r--r-- | test/test.py | 30 |
22 files changed, 475 insertions, 421 deletions
diff --git a/libmat/__init__.py b/libmat/__init__.py index 8b13789..e69de29 100644 --- a/libmat/__init__.py +++ b/libmat/__init__.py | |||
| @@ -1 +0,0 @@ | |||
| 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 | ||
| 4 | import datetime | 4 | import datetime |
| 5 | import logging | 5 | import 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 |
| 17 | ZIP_EPOCH = (1980, 1, 1, 0, 0, 0) | 17 | ZIP_EPOCH = (1980, 1, 1, 0, 0, 0) |
| 18 | ZIP_EPOCH_SECONDS = (datetime.datetime(1980, 1, 1, 0, 0, 0) | 18 | ZIP_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 | ||
| 22 | class GenericArchiveStripper(parser.GenericParser): | 22 | class 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 | ||
| 58 | class ZipStripper(GenericArchiveStripper): | 59 | class 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 | ||
| 188 | class TarStripper(GenericArchiveStripper): | 198 | class 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 | ||
| 314 | class TerminalZipStripper(ZipStripper): | 328 | class 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 | ||
| 322 | class GzipStripper(TarStripper): | 336 | class 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 | ||
| 330 | class Bzip2Stripper(TarStripper): | 345 | class 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 | ||
| 4 | try: | 4 | try: |
| 5 | from mutagen.flac import FLAC | 5 | from mutagen.flac import FLAC |
| @@ -12,41 +12,41 @@ import mutagenstripper | |||
| 12 | 12 | ||
| 13 | 13 | ||
| 14 | class MpegAudioStripper(parser.GenericParser): | 14 | class 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 | ||
| 21 | class OggStripper(mutagenstripper.MutagenStripper): | 21 | class 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 | ||
| 28 | class FlacStripper(mutagenstripper.MutagenStripper): | 28 | class 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 | ||
| 29 | class BTFailure(Exception): | 29 | class BTFailure(Exception): |
| 30 | '''Custom Exception''' | 30 | """Custom Exception""" |
| 31 | pass | 31 | pass |
| 32 | 32 | ||
| 33 | 33 | ||
| 34 | class Bencached(object): | 34 | class 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 | ||
| 42 | def decode_int(x, f): | 42 | def 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 | ||
| 53 | def decode_string(x, f): | 53 | def 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 | ||
| 63 | def decode_list(x, f): | 63 | def 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 | ||
| 73 | def decode_dict(x, f): | 73 | def 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 | ||
| 83 | def encode_bool(x, r): | 83 | def 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 | ||
| 88 | def encode_int(x, r): | 88 | def 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 | ||
| 93 | def encode_list(x, r): | 93 | def 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 | ||
| 100 | def encode_dict(x, result): | 100 | def 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 | ||
| 111 | DECODE_FUNC = {str(x):decode_string for x in range(9)} | 111 | DECODE_FUNC = {str(x): decode_string for x in range(9)} |
| 112 | DECODE_FUNC['l'] = decode_list | 112 | DECODE_FUNC['l'] = decode_list |
| 113 | DECODE_FUNC['d'] = decode_dict | 113 | DECODE_FUNC['d'] = decode_dict |
| 114 | DECODE_FUNC['i'] = decode_int | 114 | DECODE_FUNC['i'] = decode_int |
| 115 | 115 | ||
| 116 | |||
| 117 | ENCODE_FUNC = {} | 116 | ENCODE_FUNC = {} |
| 118 | ENCODE_FUNC[Bencached] = lambda x, r: r.append(x.bencoded) | 117 | ENCODE_FUNC[Bencached] = lambda x, r: r.append(x.bencoded) |
| 119 | ENCODE_FUNC[int] = encode_int | 118 | ENCODE_FUNC[int] = encode_int |
| @@ -126,14 +125,14 @@ ENCODE_FUNC[bool] = encode_bool | |||
| 126 | 125 | ||
| 127 | 126 | ||
| 128 | def bencode(string): | 127 | def 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 | ||
| 135 | def bdecode(string): | 134 | def 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 | ||
| 5 | class UnableToRemoveFile(Exception): | 5 | class 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 | ||
| 10 | class UnableToWriteFile(Exception): | 10 | class 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 | ||
| 4 | import subprocess | 4 | import subprocess |
| 5 | 5 | ||
| @@ -7,25 +7,24 @@ import parser | |||
| 7 | 7 | ||
| 8 | 8 | ||
| 9 | class ExiftoolStripper(parser.GenericParser): | 9 | class 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 | ||
| 61 | class JpegStripper(ExiftoolStripper): | 60 | class 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 | ||
| 71 | class PngStripper(ExiftoolStripper): | 70 | class 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) |
| 6 | from field import FakeField | 6 | from field import FakeField |
| 7 | 7 | ||
| 8 | |||
| 8 | class EditableField(FakeField): | 9 | class 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 | |||
| 58 | class EditableFixedField(EditableField): | 67 | class 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 | |||
| 74 | class EditableBits(EditableFixedField): | 85 | class 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 | |||
| 101 | class EditableBytes(EditableField): | 113 | class 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 | |||
| 113 | class EditableString(EditableField): | 126 | class 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 | |||
| 169 | class EditableCharacter(EditableFixedField): | 183 | class 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 | |||
| 193 | class EditableInteger(EditableFixedField): | 208 | class 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 | ||
| 239 | def createEditableField(fieldset, field): | 255 | def 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 | ||
| 3 | References: | 3 | References: |
| 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 | ||
| 9 | import parser | 9 | import parser |
| 10 | 10 | ||
| 11 | 11 | ||
| 12 | class JpegStripper(parser.GenericParser): | 12 | class 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 | ||
| 37 | class PngStripper(parser.GenericParser): | 37 | class 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 | ||
| 6 | import logging | 6 | import logging |
| 7 | import mimetypes | 7 | import 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 |
| 22 | LOGGING_LEVEL = logging.CRITICAL | 22 | LOGGING_LEVEL = logging.CRITICAL |
| 23 | hachoir_core.config.quiet = True | 23 | hachoir_core.config.quiet = True |
| 24 | fname = '' | 24 | fname = '' |
| 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 | ||
| 31 | logging.basicConfig(filename=fname, level=LOGGING_LEVEL) | 31 | logging.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 | ||
| 36 | def get_logo(): | 36 | def 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 | ||
| 47 | def get_datafile_path(filename): | 47 | def 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 | ||
| 58 | def list_supported_formats(): | 58 | def 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 | ||
| 78 | class XMLParser(xml.sax.handler.ContentHandler): | 78 | class 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 | ||
| 113 | def secure_remove(filename): | 114 | def 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 | ||
| 143 | def create_class_file(name, backup, **kwargs): | 144 | def 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 | ||
| 4 | import parser | 4 | import parser |
| 5 | 5 | ||
| @@ -7,33 +7,34 @@ from bencode import bencode | |||
| 7 | 7 | ||
| 8 | 8 | ||
| 9 | class TorrentStripper(parser.GenericParser): | 9 | class 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 | ||
| 4 | import parser | 4 | import 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 | ||
| 5 | import logging | 5 | import logging |
| 6 | import os | 6 | import os |
| @@ -21,14 +21,14 @@ import archive | |||
| 21 | 21 | ||
| 22 | 22 | ||
| 23 | class OpenDocumentStripper(archive.TerminalZipStripper): | 23 | class 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 | ||
| 72 | class OpenXmlStripper(archive.TerminalZipStripper): | 72 | class 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 | ||
| 111 | class PdfStripper(parser.GenericParser): | 112 | class 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 | ||
| 4 | import os | 4 | import os |
| 5 | import shutil | 5 | import shutil |
| @@ -22,8 +22,8 @@ FIELD = object() | |||
| 22 | 22 | ||
| 23 | 23 | ||
| 24 | class GenericParser(object): | 24 | class 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 | ||
| 4 | import archive | 4 | import archive |
| 5 | import audio | 5 | import audio |
| @@ -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 | ||
| 6 | import sys | 6 | import sys |
| 7 | import xml.sax | ||
| 8 | import argparse | 7 | import argparse |
| 9 | import os | 8 | import os |
| 10 | 9 | ||
| 11 | import hachoir_core | 10 | import hachoir_core |
| 12 | 11 | ||
| 13 | from libmat import mat | 12 | from libmat import mat |
| 14 | from libmat import strippers | ||
| 15 | from libmat import archive | 13 | from libmat import archive |
| 16 | 14 | ||
| 17 | 15 | ||
| 18 | def parse(): | 16 | def 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 | ||
| 45 | def list_meta(class_file, filename, add2archive): | 43 | def 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 | ||
| 60 | def is_clean(class_file, filename, add2archive): | 58 | def 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 | ||
| 69 | def clean_meta(class_file, filename, add2archive): | 67 | def 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 | ||
| 94 | def list_supported(): | 92 | def 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 | ||
| 106 | def main(): | 103 | def 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 | |||
| 141 | if __name__ == '__main__': | 140 | if __name__ == '__main__': |
| 142 | main() | 141 | main() |
| @@ -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 | ||
| 6 | from gi.repository import GObject, Gtk, GLib | 6 | from gi.repository import GObject, Gtk, GLib |
| 7 | from gi.repository import Gdk, GdkPixbuf | 7 | from gi.repository import Gdk, GdkPixbuf |
| @@ -22,17 +22,19 @@ logging.basicConfig(level=mat.LOGGING_LEVEL) | |||
| 22 | 22 | ||
| 23 | 23 | ||
| 24 | class CFile(GObject.Object): | 24 | class 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 | ||
| 34 | class GUI(object): | 35 | class 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 | |||
| 433 | if __name__ == '__main__': | 440 | if __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 | ||
| 8 | import logging | 8 | import logging |
| 9 | import urllib | 9 | import urllib |
| 10 | |||
| 10 | try: | 11 | try: |
| 11 | import gettext | 12 | import gettext |
| 13 | |||
| 12 | gettext.install("mat") | 14 | gettext.install("mat") |
| 13 | except: | 15 | except: |
| 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) |
| @@ -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. |
| 13 | if os.path.exists('MANIFEST'): | 13 | if os.path.exists('MANIFEST'): |
| 14 | os.remove('MANIFEST') | 14 | os.remove('MANIFEST') |
| 15 | 15 | ||
| 16 | setup( | 16 | setup( |
| 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 | ||
| 8 | import os | 8 | import os |
| 9 | import unittest | 9 | import unittest |
| @@ -17,18 +17,19 @@ import test | |||
| 17 | 17 | ||
| 18 | 18 | ||
| 19 | class TestRemovecli(test.MATTest): | 19 | class 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 | ||
| 38 | class TestListcli(test.MATTest): | 39 | class 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\ |
| 58 | harmul metadata found" % dirty) | 60 | harmul metadata found" % dirty) |
| 59 | 61 | ||
| 60 | 62 | ||
| 61 | class TestisCleancli(test.MATTest): | 63 | class 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 | ||
| 82 | class TestFileAttributes(unittest.TestCase): | 85 | class 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 | |||
| 106 | class TestUnsupported(test.MATTest): | 111 | class 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 | ||
| 121 | def get_tests(): | 127 | def 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 | ||
| 8 | import os | 8 | import os |
| 9 | import sys | 9 | import sys |
| @@ -18,10 +18,11 @@ import libmat | |||
| 18 | 18 | ||
| 19 | 19 | ||
| 20 | class TestRemovelib(test.MATTest): | 20 | class 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 | ||
| 40 | class TestListlib(test.MATTest): | 41 | class 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 | ||
| 56 | class TestisCleanlib(test.MATTest): | 58 | class 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 | ||
| 72 | class TestFileAttributes(unittest.TestCase): | 75 | class 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 | ||
| 87 | class TestSecureRemove(unittest.TestCase): | 91 | class 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 | ||
| 102 | class TestArchiveProcessing(test.MATTest): | 107 | class 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 | |||
| 167 | def get_tests(): | 174 | def 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 | ||
| 12 | import shutil | 11 | import shutil |
| 13 | import os | 12 | import os |
| @@ -42,27 +41,28 @@ except ImportError: | |||
| 42 | 41 | ||
| 43 | 42 | ||
| 44 | class MATTest(unittest.TestCase): | 43 | class 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) |
