@@ -248,36 +248,31 @@ def pip_filter(member: tarfile.TarInfo, path: str) -> tarfile.TarInfo:
248248 tar .close ()
249249
250250
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+
251266def _untar_without_filter (
252267 filename : str ,
253268 location : str ,
254269 tar : tarfile .TarFile ,
255270 leading : bool ,
256271) -> None :
257272 """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.
281276 for member in tar .getmembers ():
282277 fn = member .name
283278 if leading :
@@ -292,14 +287,14 @@ def _check_link_target(tar: tarfile.TarFile, tarinfo: tarfile.TarInfo) -> None:
292287 if member .isdir ():
293288 ensure_dir (path )
294289 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 ):
298291 message = (
299292 "The tar file ({}) has a file ({}) trying to install "
300293 "outside target directory ({})"
301294 )
302- raise InstallationError (message .format (filename , member .name , exc ))
295+ raise InstallationError (
296+ message .format (filename , member .name , member .linkname )
297+ )
303298 try :
304299 tar ._extract_member (member , path )
305300 except Exception as exc :
0 commit comments