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()