import os
import tarfile
import zipfile


class ArchiveException(Exception):
    """Base exception class for all archive errors."""

class UnrecognizedArchiveFormat(ArchiveException):
    """Error raised when passed file is not a recognized archive format."""


def extract(path, to_path=''):
    """
    Unpack the tar or zip file at the specified path to the directory
    specified by to_path.
    """
    Archive(path).extract(to_path)


class Archive(object):
    """
    The external API class that encapsulates an archive implementation.
    """

    def __init__(self, file):
        self._archive = self._archive_cls(file)(file)

    @staticmethod
    def _archive_cls(file):
        cls = None
        if isinstance(file, basestring):
            filename = file
        else:
            try:
                filename = file.name
            except AttributeError:
                raise UnrecognizedArchiveFormat(
                    "File object not a recognized archive format.")
        base, tail_ext = os.path.splitext(filename.lower())
        cls = extension_map.get(tail_ext)
        if not cls:
            base, ext = os.path.splitext(base)
            cls = extension_map.get(ext)
        if not cls:
            raise UnrecognizedArchiveFormat(
                "Path not a recognized archive format: %s" % filename)
        return cls

    def extract(self, to_path=''):
        self._archive.extract(to_path)

    def namelist(self):
        return self._archive.namelist()

    def printdir(self):
        self._archive.printdir()


class BaseArchive(object):
    """
    Base Archive class.  Implementations should inherit this class.
    """

    def extract(self):
        raise NotImplementedError

    def namelist(self):
        raise NotImplementedError

    def printdir(self):
        raise NotImplementedError


class TarArchive(BaseArchive):

    def __init__(self, file):
        self._archive = tarfile.open(file)

    def printdir(self, *args, **kwargs):
        self._archive.list(*args, **kwargs)

    def namelist(self, *args, **kwargs):
        return self._archive.getnames(*args, **kwargs)

    def extract(self, to_path):
        self._archive.extractall(to_path)


class ZipArchive(BaseArchive):

    def __init__(self, file):
        self._archive = zipfile.ZipFile(file)

    def printdir(self, *args, **kwargs):
        self._archive.printdir(*args, **kwargs)

    def namelist(self, *args, **kwargs):
        return self._archive.namelist(*args, **kwargs)

    def extract(self, to_path):
        self._archive.extractall(to_path)


extension_map = {
    '.egg': ZipArchive,
    '.jar': ZipArchive,
    '.tar': TarArchive,
    '.tar.bz2': TarArchive,
    '.tar.gz': TarArchive,
    '.tgz': TarArchive,
    '.tz2': TarArchive,
    '.zip': ZipArchive,
}

@@ -254,16 +254,182 @@
 at.%22%22%22%0A%0A
+class UnsafeArchive(ArchiveException):%0A    %22%22%22Error raised when passed file contains absolute paths which could be%0A    extracted outside of the target directory.%22%22%22%0A%0A
 %0Adef ext
@@ -445,24 +445,36 @@
 , to_path=''
+, safe=False
 ):%0A    %22%22%22%0A 
@@ -601,32 +601,38 @@
 .extract(to_path
+, safe
 )%0A%0A%0Aclass Archiv
@@ -1568,18 +1568,401 @@
 _path=''
+, safe=False):%0A        if safe:%0A            to_abspath = os.path.abspath(to_path)%0A            for name in self._archive.namelist():%0A                dest = os.path.join(to_path, name)%0A                if not os.path.abspath(dest).startswith(to_abspath):%0A                    raise UnsafeArchive(%22Unsafe destination path %22 %5C%0A                            %22(outside of the target directory)%22
 )
-:
 %0A       
@@ -2717,32 +2717,35 @@
 ct(self, to_path
+=''
 ):%0A        self.
@@ -3096,24 +3096,39 @@
 elf, to_path
+='', safe=False
 ):%0A        s

add 'safe' param to check if the archive is safe before extraction