243 def check_contents(self): |
243 def check_contents(self): |
244 TarExtractor.check_contents(self) |
244 TarExtractor.check_contents(self) |
245 return BOMB |
245 return BOMB |
246 |
246 |
247 |
247 |
|
248 class MatchHandler(object): |
|
249 def __init__(self, extractor, contents): |
|
250 self.extractor = extractor |
|
251 self.contents = contents |
|
252 self.directory = extractor.basename() |
|
253 |
|
254 def extract(self, directory='.'): |
|
255 try: |
|
256 self.extractor.extract(directory) |
|
257 except ExtractorError, error: |
|
258 return error.strerror |
|
259 |
|
260 def cleanup(self): |
|
261 command = 'chmod' |
|
262 status = subprocess.call(['chmod', '-R', 'u+rw', self.directory]) |
|
263 if status == 0: |
|
264 command = 'find' |
|
265 status = subprocess.call(['find', self.directory, '-type', 'd', |
|
266 '-exec', 'chmod', 'u+x', '{}', ';']) |
|
267 if status != 0: |
|
268 return "%s returned with exit status %s" % (command, status) |
|
269 |
|
270 |
|
271 class BombHandler(MatchHandler): |
|
272 def __init__(self, extractor, contents): |
|
273 MatchHandler.__init__(self, extractor, contents) |
|
274 basename = self.directory |
|
275 for suffix in [''] + ['.%s' % (x,) for x in range(1, 10)]: |
|
276 self.directory = '%s%s' % (basename, suffix) |
|
277 try: |
|
278 os.mkdir(self.directory) |
|
279 except OSError, error: |
|
280 if error.errno == errno.EEXIST: |
|
281 continue |
|
282 raise ValueError("could not make extraction directory %s: %s" % |
|
283 (error.filename, error.strerror)) |
|
284 ## if suffix != '': |
|
285 ## self.show_error("extracted to %s" % (directory,)) |
|
286 break |
|
287 else: |
|
288 raise ValueError("all good names for an extraction directory taken") |
|
289 |
|
290 def extract(self): |
|
291 os.chdir(self.directory) |
|
292 return MatchHandler.extract(self, '..') |
|
293 |
|
294 def cleanup(self): |
|
295 os.chdir('..') |
|
296 return MatchHandler.cleanup(self) |
|
297 |
|
298 |
|
299 class EmptyHandler(object): |
|
300 def __init__(self, extractor, contents): pass |
|
301 def extract(self): pass |
|
302 def cleanup(self): pass |
|
303 |
248 extractor_map = {'application/x-tar': TarExtractor, |
304 extractor_map = {'application/x-tar': TarExtractor, |
249 'application/zip': ZipExtractor, |
305 'application/zip': ZipExtractor, |
250 'application/x-msdos-program': ZipExtractor, |
306 'application/x-msdos-program': ZipExtractor, |
251 'application/x-debian-package': DebExtractor, |
307 'application/x-debian-package': DebExtractor, |
252 'application/x-redhat-package-manager': RPMExtractor, |
308 'application/x-redhat-package-manager': RPMExtractor, |
253 'application/x-rpm': RPMExtractor, |
309 'application/x-rpm': RPMExtractor, |
254 'application/x-cpio': CpioExtractor} |
310 'application/x-cpio': CpioExtractor} |
255 |
311 |
|
312 handler_map = {EMPTY: EmptyHandler, |
|
313 MATCHING_DIRECTORY: MatchHandler} |
|
314 |
256 class ExtractorApplication(object): |
315 class ExtractorApplication(object): |
257 actions = ['get_extractor', 'prepare_extraction', 'extract', 'recurse'] |
|
258 |
|
259 def __init__(self, arguments): |
316 def __init__(self, arguments): |
260 self.parse_options(arguments) |
317 self.parse_options(arguments) |
261 self.successes = [] |
318 self.successes = [] |
262 self.failures = [] |
319 self.failures = [] |
263 |
320 |
279 print >>sys.stderr, "%s: %s" % (self.current_filename, message) |
336 print >>sys.stderr, "%s: %s" % (self.current_filename, message) |
280 |
337 |
281 def get_extractor(self): |
338 def get_extractor(self): |
282 mimetype, encoding = mimetypes.guess_type(self.current_filename) |
339 mimetype, encoding = mimetypes.guess_type(self.current_filename) |
283 try: |
340 try: |
284 handler = extractor_map[mimetype] |
341 extractor = extractor_map[mimetype] |
285 except KeyError: |
342 except KeyError: |
286 self.show_error("not a known archive type") |
343 return "not a known archive type" |
287 return False |
344 try: |
288 try: |
345 self.current_extractor = extractor(self.current_filename, mimetype, |
289 self.current_extractor = handler(self.current_filename, mimetype, |
346 encoding) |
290 encoding) |
347 content = self.current_extractor.check_contents() |
|
348 handler = handler_map.get(content, BombHandler) |
|
349 self.current_handler = handler(self.current_extractor, content) |
291 except ExtractorError, error: |
350 except ExtractorError, error: |
|
351 return str(error) |
|
352 |
|
353 def recurse(self): |
|
354 if not self.options.recursive: |
|
355 return |
|
356 archive_path = os.path.split(self.current_filename)[0] |
|
357 for filename in self.current_extractor.included_archives: |
|
358 tail_path, basename = os.path.split(filename) |
|
359 directory = os.path.join(self.current_directory, archive_path, |
|
360 self.current_handler.directory, tail_path) |
|
361 self.archives.setdefault(directory, []).append(basename) |
|
362 |
|
363 def report(self, function, *args): |
|
364 error = function(*args) |
|
365 if error: |
292 self.show_error(error) |
366 self.show_error(error) |
293 return False |
367 return False |
294 return True |
368 return True |
295 |
|
296 def prepare_target_directory(self): |
|
297 basename = self.current_extractor.basename() |
|
298 for suffix in [''] + ['.%s' % (x,) for x in range(1, 10)]: |
|
299 directory = '%s%s' % (basename, suffix) |
|
300 try: |
|
301 os.mkdir(directory) |
|
302 except OSError, error: |
|
303 if error.errno == errno.EEXIST: |
|
304 continue |
|
305 self.show_error("could not create extraction directory %s: %s" % |
|
306 (error.filename, error.strerror)) |
|
307 return None |
|
308 if suffix != '': |
|
309 self.show_error("extracted to %s" % (directory,)) |
|
310 break |
|
311 else: |
|
312 self.show_error("all good names for an extraction directory taken") |
|
313 return directory |
|
314 |
|
315 def move_to_directory(self, filename, target): |
|
316 if not os.path.isdir(filename): |
|
317 filename = os.path.split(filename)[0] |
|
318 target = os.path.join(target, filename) |
|
319 os.rename(filename, target) |
|
320 |
|
321 def prepare_extraction(self): |
|
322 self.current_path = '.' |
|
323 contents = self.current_extractor.check_contents() |
|
324 if contents == MATCHING_DIRECTORY: |
|
325 self.target_directory = self.current_filename |
|
326 elif contents != EMPTY: |
|
327 self.target_directory = self.prepare_target_directory() |
|
328 if self.target_directory is None: |
|
329 return False |
|
330 if contents == BOMB: |
|
331 os.chdir(self.target_directory) |
|
332 self.current_path = '..' |
|
333 else: |
|
334 self.cleanup_actions.append((self.move_to_directory, contents, |
|
335 self.target_directory)) |
|
336 else: |
|
337 self.target_directory = None |
|
338 return True |
|
339 |
|
340 def extract(self): |
|
341 try: |
|
342 self.current_extractor.extract(self.current_path) |
|
343 except ExtractorError, error: |
|
344 self.show_error(error) |
|
345 return False |
|
346 return True |
|
347 |
|
348 def recurse(self): |
|
349 if not self.options.recursive: |
|
350 return True |
|
351 for filename in self.current_extractor.included_archives: |
|
352 tail_path, basename = os.path.split(filename) |
|
353 directory = os.path.join(self.current_directory, |
|
354 self.target_directory, tail_path) |
|
355 self.archives.setdefault(directory, []).append(basename) |
|
356 return True |
|
357 |
|
358 def fix_perms(self): |
|
359 if self.target_directory is None: |
|
360 return True |
|
361 status = subprocess.call(['chmod', '-R', 'u+rw', |
|
362 os.path.join(self.current_directory, |
|
363 self.target_directory)]) |
|
364 if status == 0: |
|
365 status = subprocess.call(['find', |
|
366 os.path.join(self.current_directory, |
|
367 self.target_directory), |
|
368 '-type', 'd', |
|
369 '-exec', 'chmod', 'u+x', '{}', ';']) |
|
370 return status == 0 |
|
371 |
369 |
372 def run(self): |
370 def run(self): |
373 while self.archives: |
371 while self.archives: |
374 self.current_directory, filenames = self.archives.popitem() |
372 self.current_directory, filenames = self.archives.popitem() |
375 for filename in filenames: |
373 for filename in filenames: |
376 os.chdir(self.current_directory) |
374 os.chdir(self.current_directory) |
377 running = True |
|
378 self.current_filename = filename |
375 self.current_filename = filename |
379 self.cleanup_actions = [] |
376 self.cleanup_actions = [] |
380 actions = [getattr(self, name) for name in self.actions] |
377 success = self.report(self.get_extractor) |
381 while running and actions: |
378 if success: |
382 running = actions.pop(0)() |
379 for name in 'extract', 'cleanup': |
383 for action in self.cleanup_actions: |
380 success = (self.report(getattr(self.current_handler, |
384 action[0](*action[1:]) |
381 name)) and success) |
385 running = self.fix_perms() |
382 self.recurse() |
386 if running: |
383 if success: |
387 self.successes.append(self.current_filename) |
384 self.successes.append(self.current_filename) |
388 else: |
385 else: |
389 self.failures.append(self.current_filename) |
386 self.failures.append(self.current_filename) |
390 if self.failures: |
387 if self.failures: |
391 return 1 |
388 return 1 |