zip.py 1.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  1. import os
  2. import shutil
  3. import zipfile
  4. def is_unsafe_path(path):
  5. if os.path.isabs(path):
  6. return True
  7. for segment in path.split(os.path.sep):
  8. if segment == os.path.pardir:
  9. return True
  10. return False
  11. def find_common_prefix(members):
  12. qualifying_members = []
  13. for member in members:
  14. pieces = member.split("/")
  15. if pieces and pieces[0].startswith("."):
  16. continue
  17. qualifying_members.append(pieces)
  18. rv = os.path.commonprefix(qualifying_members)
  19. if rv:
  20. return rv[0] + "/"
  21. return ""
  22. def safe_extract_zip(f, path, strip_toplevel=True):
  23. """Safely extract a given zip file to a path. The zipfile can either
  24. be an open file or a filename. If the zip is unsafe an exception is
  25. raised. Optionally the toplevel folder is stripped off. If there are
  26. hidden files on toplevel then, these are silently ignored.
  27. """
  28. close = False
  29. if not isinstance(f, zipfile.ZipFile):
  30. close = isinstance(f, str)
  31. zf = zipfile.ZipFile(f, "r")
  32. else:
  33. zf = f
  34. try:
  35. members = zf.namelist()
  36. if strip_toplevel:
  37. prefix = find_common_prefix(members)
  38. else:
  39. prefix = ""
  40. for member in members:
  41. # Skip directories
  42. if member.endswith("/"):
  43. continue
  44. if not member.startswith(prefix) or is_unsafe_path(member):
  45. continue
  46. dst_path = os.path.join(path, member[len(prefix) :])
  47. try:
  48. os.makedirs(os.path.dirname(dst_path))
  49. except OSError:
  50. pass
  51. with open(dst_path, "wb") as df:
  52. with zf.open(member) as sf:
  53. shutil.copyfileobj(sf, df)
  54. finally:
  55. if close:
  56. zf.close()