Index: Doc/library/zipfile.rst =================================================================== --- Doc/library/zipfile.rst (revision 87407) +++ Doc/library/zipfile.rst (working copy) @@ -208,7 +208,12 @@ to extract to. *member* can be a filename or a :class:`ZipInfo` object. *pwd* is the password used for encrypted files. + If members that have absolute filenames starting with ``"/"``, + the leading ``"/"`` will be stripped, e.g.: ``///foo/bar`` becomes + ``foo/bar``. And all path component ``".."`` in member filename will be + removed, e.g.: ``../../foo../../ba..r`` will be ``foo../ba..r``. + .. method:: ZipFile.extractall(path=None, members=None, pwd=None) Extract all members from the archive to the current working directory. *path* @@ -216,14 +221,12 @@ be a subset of the list returned by :meth:`namelist`. *pwd* is the password used for encrypted files. - .. warning:: + If members that have absolute filenames starting with ``"/"``, + the leading ``"/"`` will be stripped, e.g.: ``///foo/bar`` becomes + ``foo/bar``. And all path component ``".."`` in member filename will be + removed, e.g.: ``../../foo../../ba..r`` will be ``foo../ba..r``. - Never extract archives from untrusted sources without prior inspection. - It is possible that files are created outside of *path*, e.g. members - that have absolute filenames starting with ``"/"`` or filenames with two - dots ``".."``. - .. method:: ZipFile.printdir() Print a table of contents for the archive to ``sys.stdout``. Index: Lib/zipfile.py =================================================================== --- Lib/zipfile.py (revision 87995) +++ Lib/zipfile.py (working copy) @@ -1014,12 +1014,16 @@ and len(os.path.splitdrive(targetpath)[1]) > 1): targetpath = targetpath[:-1] + # remove all '..' + arcname = member.filename + arcname = os.path.sep.join([x for x in arcname.split(os.path.sep) + if x != '..']) + # don't include leading "/" from file name if present - if member.filename[0] == '/': - targetpath = os.path.join(targetpath, member.filename[1:]) - else: - targetpath = os.path.join(targetpath, member.filename) + while arcname[0] in (os.sep, os.altsep): + arcname = arcname[1:] + targetpath = os.path.join(targetpath, arcname) targetpath = os.path.normpath(targetpath) # Create all upper directories if necessary. Index: Lib/test/test_zipfile.py =================================================================== --- Lib/test/test_zipfile.py (revision 87995) +++ Lib/test/test_zipfile.py (working copy) @@ -29,6 +29,7 @@ SMALL_TEST_DATA = [('_ziptest1', '1q2w3e4r5t'), ('ziptest2dir/_ziptest2', 'qawsedrftg'), ('/ziptest2dir/ziptest3dir/_ziptest3', 'azsxdcfvgb'), + ('//ziptest2dir/ziptest3dir/_ziptest4', 'ialpsfdlji'), ('ziptest2dir/ziptest3dir/ziptest4dir/_ziptest3', '6y7u8i9o0p')] @@ -391,7 +392,7 @@ # make sure it was written to the right place if os.path.isabs(fpath): - correctfile = os.path.join(os.getcwd(), fpath[1:]) + correctfile = os.path.join(os.getcwd(), fpath.lstrip(os.sep)) else: correctfile = os.path.join(os.getcwd(), fpath) correctfile = os.path.normpath(correctfile) @@ -416,7 +417,7 @@ zipfp.extractall() for fpath, fdata in SMALL_TEST_DATA: if os.path.isabs(fpath): - outfile = os.path.join(os.getcwd(), fpath[1:]) + outfile = os.path.join(os.getcwd(), fpath.lstrip(os.sep)) else: outfile = os.path.join(os.getcwd(), fpath) @@ -428,6 +429,17 @@ # remove the test file subdirectories shutil.rmtree(os.path.join(os.getcwd(), 'ziptest2dir')) + def test_extract_double_dot_arcnames(self): + fpath = "../../foo../../ba..r" + with zipfile.ZipFile(TESTFN2, "w", zipfile.ZIP_STORED) as zipfp: + zipfp.writestr(fpath, 'foobar') + + with zipfile.ZipFile(TESTFN2, "r") as zipfp: + writtenfile = zipfp.extract(fpath) + correctfile = os.path.join(os.getcwd(), "foo../ba..r") + self.assertEqual(writtenfile, correctfile) + os.remove(writtenfile) + def test_writestr_compression(self): zipfp = zipfile.ZipFile(TESTFN2, "w") zipfp.writestr("a.txt", "hello world", compress_type=zipfile.ZIP_STORED)