@@ -248,36 +248,31 @@ def pip_filter(member: tarfile.TarInfo, path: str) -> tarfile.TarInfo:
248
248
tar .close ()
249
249
250
250
251
+ def is_symlink_target_in_tar (tar : tarfile .TarFile , tarinfo : tarfile .TarInfo ) -> bool :
252
+ """Check if the file pointed to by the symbolic link is in the tar archive"""
253
+ linkname = os .path .join (os .path .dirname (tarinfo .name ), tarinfo .linkname )
254
+
255
+ linkname = os .path .normpath (linkname )
256
+ if "\\ " in linkname :
257
+ linkname = linkname .replace ("\\ " , "/" )
258
+
259
+ try :
260
+ tar .getmember (linkname )
261
+ return True
262
+ except KeyError :
263
+ return False
264
+
265
+
251
266
def _untar_without_filter (
252
267
filename : str ,
253
268
location : str ,
254
269
tar : tarfile .TarFile ,
255
270
leading : bool ,
256
271
) -> None :
257
272
"""Fallback for Python without tarfile.data_filter"""
258
-
259
- def _check_link_target (tar : tarfile .TarFile , tarinfo : tarfile .TarInfo ) -> None :
260
- linkname = "/" .join (
261
- filter (None , (os .path .dirname (tarinfo .name ), tarinfo .linkname ))
262
- )
263
-
264
- linkname = os .path .normpath (linkname )
265
-
266
- try :
267
- tar .getmember (linkname )
268
- except KeyError :
269
- if "\\ " in linkname or "/" in linkname :
270
- if "\\ " in linkname :
271
- linkname = linkname .replace ("\\ " , "/" )
272
- else :
273
- linkname = linkname .replace ("/" , "\\ " )
274
- try :
275
- tar .getmember (linkname )
276
- except KeyError :
277
- raise KeyError (linkname )
278
- else :
279
- raise KeyError (linkname )
280
-
273
+ # NOTE: This function can be removed once pip requires CPython ≥ 3.12.
274
+ # PEP 706 added tarfile.data_filter, made tarfile extraction operations more secure.
275
+ # This feature is fully supported from CPython 3.12 onward.
281
276
for member in tar .getmembers ():
282
277
fn = member .name
283
278
if leading :
@@ -292,14 +287,14 @@ def _check_link_target(tar: tarfile.TarFile, tarinfo: tarfile.TarInfo) -> None:
292
287
if member .isdir ():
293
288
ensure_dir (path )
294
289
elif member .issym ():
295
- try :
296
- _check_link_target (tar , member )
297
- except KeyError as exc :
290
+ if not is_symlink_target_in_tar (tar , member ):
298
291
message = (
299
292
"The tar file ({}) has a file ({}) trying to install "
300
293
"outside target directory ({})"
301
294
)
302
- raise InstallationError (message .format (filename , member .name , exc ))
295
+ raise InstallationError (
296
+ message .format (filename , member .name , member .linkname )
297
+ )
303
298
try :
304
299
tar ._extract_member (member , path )
305
300
except Exception as exc :
0 commit comments