Title: gh-111791: delegating extraction to zipfile module's extractall() method by sepastian · Pull Request #111824 · python/cpython · GitHub
Open Graph Title: gh-111791: delegating extraction to zipfile module's extractall() method by sepastian · Pull Request #111824 · python/cpython
X Title: gh-111791: delegating extraction to zipfile module's extractall() method by sepastian · Pull Request #111824 · python/cpython
Description: shutil.unpack_archive: deletage extracting ZIP files to zipfile (#111791) gh-111791: rely on `zipfile.extractall` when extraxting ZIP archives, preventing false negatives and code duplication As reported in #111791, if the path of a file inside a ZIP file contains "..", e.g. myfile..txt (probably misspelled), shutil.unpack_archive will silently skip extracting the file, because it wrongly assumes a relative path. This is problematic for two reasons: The current implementation of shutil.unpack_archive wrongly identifies relative path components. Scanning for ".." does not tell whether a path contains relative components, or not; one must scan for "../" instead. Besides, files inside a ZIP archive described through relative paths should be extracted, see below. Python's own zipfile module and the unzip are handling relative path components "../"and names containins ".." correctly. For reference, the man unzip page says: For security reasons, unzip normally removes parent dir path components (../) from the names of extracted file. This safety feature (new for version 5.50) prevents unzip from accidentally writing files to sensitive areas outside the active extraction folder tree head. Solution: delegate extracting ZIP archives to Python's own zipfile.extractall method. Appendix The following example shows, how extracting a ZIP archive containing paths containing relative components "../" and files with names containing ".." differs in shutil.unpack_archive, zipfile.extractall and the Linux tool unzip. # Create some files and directories. $ cd /tmp $ mkdir -p a/b/c $ touch a/a.txt a/a..txt a/b/b.txt a/b/b..txt a/b/c/c.txt a/b/c/c..txt $ find a a a/b a/b/b.txt a/b/b..txt a/b/c a/b/c/c.txt a/b/c/c..txt a/a.txt a/a..txt # Create a ZIP file for testing. $ cd /tmp/a/b $ zip /tmp/test.zip ../a.txt /tmp/a/b/c/c.txt ../../a/b/b.txt # Inspect the newly created ZIP file. $ zipinfo /tmp/test.zip zipinfo /tmp/test.zip Archive: /tmp/test.zip Zip file size: 482 bytes, number of entries: 3 -rw-rw-r-- 3.0 unx 0 bx stor 23-Nov-07 16:03 ../a.txt -rw-rw-r-- 3.0 unx 0 bx stor 23-Nov-07 16:04 tmp/a/b/c/c.txt -rw-rw-r-- 3.0 unx 0 bx stor 23-Nov-07 16:03 ../../a/b/b.txt 3 files, 0 bytes uncompressed, 0 bytes compressed: 0.0% # Prepare directories holding extraction results. $ mkdir -p /tmp/extract/unzip /tmp/extract/zipfile /tmp/extract/shutil # Extract using 'unzip' extracts all files # and warns about relative path components. $ cd /tmp/extract/unzip $ unzip /tmp/test.zip Archive: /tmp/test.zip warning: skipped "../" path component(s) in ../a.txt extracting: a.txt extracting: tmp/a/b/c/c.txt warning: skipped "../" path component(s) in ../../a/b/b.txt extracting: a/b/b.txt $ find . ./a ./a/b ./a/b/b.txt ./tmp ./tmp/a ./tmp/a/b ./tmp/a/b/c ./tmp/a/b/c/c.txt ./a.txt # Extract using 'zipfile' (CLI). $ cd /tmp/extract/zipfile $ python3 -m zipfile -e /tmp/test.zip . $ find . ./a ./a/b ./a/b/b.txt ./tmp ./tmp/a ./tmp/a/b ./tmp/a/b/c ./tmp/a/b/c/c.txt ./a.txt # Extract using 'shutil.unpack_archive'. $ cd /tmp/extract/shutil $ python3 -c 'import shutil; shutil.unpack_archive("/tmp/test.zip",".")' $ find . ./tmp ./tmp/a ./tmp/a/b ./tmp/a/b/c ./tmp/a/b/c/c.txt # As can be seen, and as can be confirmed using `diff`, # zipfile and unzip produce the same result, # while shutil silently skips paths containing "..", # i.e. both names containing ".." and paths containing # actual relative components. # Diff. $ cd /tmp/extract $ diff -r zipfile/ unzip/ $ diff -r unzip/ shutil/ Only in unzip/: a Only in unzip/: a..txt Only in unzip/: a.txt Only in unzip/: tmp
Open Graph Description: shutil.unpack_archive: deletage extracting ZIP files to zipfile (#111791) gh-111791: rely on `zipfile.extractall` when extraxting ZIP archives, preventing false negatives and code dupli...
X Description: shutil.unpack_archive: deletage extracting ZIP files to zipfile (#111791) gh-111791: rely on `zipfile.extractall` when extraxting ZIP archives, preventing false negatives and code dupli...
Opengraph URL: https://github.com/python/cpython/pull/111824
X: @github
Domain: github.com
| route-pattern | /:user_id/:repository/pull/:id/files(.:format) |
| route-controller | pull_requests |
| route-action | files |
| fetch-nonce | v2:2e37f2a3-8287-928b-555f-e9cfe46ba093 |
| current-catalog-service-hash | ae870bc5e265a340912cde392f23dad3671a0a881730ffdadd82f2f57d81641b |
| request-id | D078:3A3C76:158B68D:1F61AE5:6A562031 |
| html-safe-nonce | f0cc6e214b00c95b67d8d97295ed1a973f8c4fcf5bcf46b096b6d21651ffcee9 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJEMDc4OjNBM0M3NjoxNThCNjhEOjFGNjFBRTU6NkE1NjIwMzEiLCJ2aXNpdG9yX2lkIjoiNDQwNzQxMTI3MjQ2NTEyOTUyMSIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | 3974d2d0c2478dd8e5404837250fa407764e1a871941d2bf85bbccdfd3c2a3a7 |
| hovercard-subject-tag | pull_request:1590945877 |
| github-keyboard-shortcuts | repository,pull-request-list,pull-request-conversation,pull-request-files-changed,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/python/cpython/pull/111824/files |
| twitter:image | https://avatars.githubusercontent.com/u/14915?s=400&v=4 |
| twitter:card | summary_large_image |
| og:image | https://avatars.githubusercontent.com/u/14915?s=400&v=4 |
| og:image:alt | shutil.unpack_archive: deletage extracting ZIP files to zipfile (#111791) gh-111791: rely on `zipfile.extractall` when extraxting ZIP archives, preventing false negatives and code dupli... |
| og:site_name | GitHub |
| og:type | object |
| hostname | github.com |
| expected-hostname | github.com |
| None | eeb71977be324ce2bc7c4afd597296b8e3527d8922ad94632edd7a7296f8031b |
| turbo-cache-control | no-preview |
| diff-view | unified |
| go-import | github.com/python/cpython git https://github.com/python/cpython.git |
| octolytics-dimension-user_id | 1525981 |
| octolytics-dimension-user_login | python |
| octolytics-dimension-repository_id | 81598961 |
| octolytics-dimension-repository_nwo | python/cpython |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 81598961 |
| octolytics-dimension-repository_network_root_nwo | python/cpython |
| turbo-body-classes | logged-out env-production page-responsive full-width |
| disable-turbo | true |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 3e7957744c8a44d05ff1e464dc5d119e60b98fae |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width