@@ -801,10 +801,25 @@ def open_url(self, url, warning=None): # noqa: C901 # is too complex (12)
801801 else :
802802 raise DistutilsError ("Download error for %s: %s" % (url , v )) from v
803803
804- def _download_url (self , url , tmpdir ):
805- # Determine download filename
806- #
807- name , fragment = egg_info_for_url (url )
804+ @staticmethod
805+ def _resolve_download_filename (url , tmpdir ):
806+ """
807+ >>> import pathlib
808+ >>> du = PackageIndex._resolve_download_filename
809+ >>> root = getfixture('tmp_path')
810+ >>> url = 'https://files.pythonhosted.org/packages/a9/5a/0db.../setuptools-78.1.0.tar.gz'
811+ >>> str(pathlib.Path(du(url, root)).relative_to(root))
812+ 'setuptools-78.1.0.tar.gz'
813+
814+ Ensures the target is always in tmpdir.
815+
816+ >>> url = 'https://anyhost/%2fhome%2fuser%2f.ssh%2fauthorized_keys'
817+ >>> du(url, root)
818+ Traceback (most recent call last):
819+ ...
820+ ValueError: Invalid filename...
821+ """
822+ name , _fragment = egg_info_for_url (url )
808823 if name :
809824 while '..' in name :
810825 name = name .replace ('..' , '.' ).replace ('\\ ' , '_' )
@@ -816,6 +831,12 @@ def _download_url(self, url, tmpdir):
816831
817832 filename = os .path .join (tmpdir , name )
818833
834+ # ensure path resolves within the tmpdir
835+ if not filename .startswith (str (tmpdir )):
836+ raise ValueError (f"Invalid filename { filename } " )
837+
838+ return filename
839+
819840 return self ._download_vcs (url , filename ) or self ._download_other (url , filename )
820841
821842 @staticmethod
0 commit comments