@@ -807,21 +807,63 @@ def open_url(self, url, warning=None): # noqa: C901 # is too complex (12)
807
807
else :
808
808
raise DistutilsError (f"Download error for { url } : { v } " ) from v
809
809
810
- def _download_url (self , url , tmpdir ):
811
- # Determine download filename
812
- #
810
+ @staticmethod
811
+ def _sanitize (name ):
812
+ r"""
813
+ Replace unsafe path directives with underscores.
814
+
815
+ >>> san = PackageIndex._sanitize
816
+ >>> san('/home/user/.ssh/authorized_keys')
817
+ '_home_user_.ssh_authorized_keys'
818
+ >>> san('..\\foo\\bing')
819
+ '__foo_bing'
820
+ >>> san('D:bar')
821
+ 'D_bar'
822
+ >>> san('C:\\bar')
823
+ 'C__bar'
824
+ >>> san('foo..bar')
825
+ 'foo..bar'
826
+ >>> san('D:../foo')
827
+ 'D___foo'
828
+ """
829
+ pattern = '|' .join ((
830
+ # drive letters
831
+ r':' ,
832
+ # path separators
833
+ r'[/\\]' ,
834
+ # parent dirs
835
+ r'(?:(?<=([/\\]|:))\.\.(?=[/\\]|$))|(?:^\.\.(?=[/\\]|$))' ,
836
+ ))
837
+ return re .sub (pattern , r'_' , name )
838
+
839
+ @classmethod
840
+ def _resolve_download_filename (cls , url , tmpdir ):
841
+ """
842
+ >>> import pathlib
843
+ >>> du = PackageIndex._resolve_download_filename
844
+ >>> root = getfixture('tmp_path')
845
+ >>> url = 'https://files.pythonhosted.org/packages/a9/5a/0db.../setuptools-78.1.0.tar.gz'
846
+ >>> str(pathlib.Path(du(url, root)).relative_to(root))
847
+ 'setuptools-78.1.0.tar.gz'
848
+ """
813
849
name , _fragment = egg_info_for_url (url )
814
- if name :
815
- while '..' in name :
816
- name = name .replace ('..' , '.' ).replace ('\\ ' , '_' )
817
- else :
818
- name = "__downloaded__" # default if URL has no path contents
850
+ name = cls ._sanitize (
851
+ name
852
+ or
853
+ # default if URL has no path contents
854
+ '__downloaded__'
855
+ )
819
856
820
- if name . endswith ( '.egg. zip' ):
821
- name = name [: - 4 ] # strip the extra . zip before download
857
+ # strip any extra . zip before download
858
+ name = re . sub ( r'\.egg\. zip$' , '.egg' , name )
822
859
823
- filename = os .path .join (tmpdir , name )
860
+ return os .path .join (tmpdir , name )
824
861
862
+ def _download_url (self , url , tmpdir ):
863
+ """
864
+ Determine the download filename.
865
+ """
866
+ filename = self ._resolve_download_filename (url , tmpdir )
825
867
return self ._download_vcs (url , filename ) or self ._download_other (url , filename )
826
868
827
869
@staticmethod
0 commit comments