import os.path
import logging
import copy
import operator


logger = logging.getLogger(__name__)


def safe_extractall(tar, path):
  """Extract all members from the archive to the current working
     directory and set owner, modification time and permissions on
     directories afterwards. `path' specifies a different directory
     to extract to. `members' is optional and must be a subset of the
     list returned by getmembers().
  """
  abs_extract_path = os.path.abspath(path)
  def is_member_safe(member):
    """ Returns True if the member is safe, False otherwise. """
    if member.issym():
      link_tar_path = os.path.dirname(member.path)
      abs_link_path = os.path.abspath(os.path.join(path, link_tar_path, member.linkpath))
      if not abs_link_path.startswith(abs_extract_path):
        logger.warning('Filtering symlink outside of extract dir: %s', member.linkpath)
        return False
    elif member.isblk():
      logger.warning('Filtering block device from tarfile: %s', member.path)
      return False

    return True

  directories = []

  for tarinfo in tar:
    if is_member_safe(tarinfo):
      if tarinfo.isdir():
        # Extract directories with a safe mode.
        directories.append(tarinfo)
        tarinfo = copy.copy(tarinfo)
        tarinfo.mode = 0700
      tar.extract(tarinfo, path)

  # Reverse sort directories.
  directories.sort(key=operator.attrgetter('name'))
  directories.reverse()

  # Set correct owner, mtime and filemode on directories.
  for tarinfo in directories:
    dirpath = os.path.join(path, tarinfo.name)
    tar.chown(tarinfo, dirpath)
    tar.utime(tarinfo, dirpath)
    tar.chmod(tarinfo, dirpath)
