12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364 |
- import os
- import shutil
- import zipfile
- def is_unsafe_path(path):
- if os.path.isabs(path):
- return True
- for segment in path.split(os.path.sep):
- if segment == os.path.pardir:
- return True
- return False
- def find_common_prefix(members):
- qualifying_members = []
- for member in members:
- pieces = member.split("/")
- if pieces and pieces[0].startswith("."):
- continue
- qualifying_members.append(pieces)
- rv = os.path.commonprefix(qualifying_members)
- if rv:
- return rv[0] + "/"
- return ""
- def safe_extract_zip(f, path, strip_toplevel=True):
- """Safely extract a given zip file to a path. The zipfile can either
- be an open file or a filename. If the zip is unsafe an exception is
- raised. Optionally the toplevel folder is stripped off. If there are
- hidden files on toplevel then, these are silently ignored.
- """
- close = False
- if not isinstance(f, zipfile.ZipFile):
- close = isinstance(f, str)
- zf = zipfile.ZipFile(f, "r")
- else:
- zf = f
- try:
- members = zf.namelist()
- if strip_toplevel:
- prefix = find_common_prefix(members)
- else:
- prefix = ""
- for member in members:
- # Skip directories
- if member.endswith("/"):
- continue
- if not member.startswith(prefix) or is_unsafe_path(member):
- continue
- dst_path = os.path.join(path, member[len(prefix) :])
- try:
- os.makedirs(os.path.dirname(dst_path))
- except OSError:
- pass
- with open(dst_path, "wb") as df:
- with zf.open(member) as sf:
- shutil.copyfileobj(sf, df)
- finally:
- if close:
- zf.close()
|