18
18
19
19
from aiohttp import web
20
20
from botocore .config import Config as BotoCoreConfig
21
+ from packaging .utils import parse_sdist_filename , parse_wheel_filename
22
+ from packaging .utils import canonicalize_name , canonicalize_version
21
23
22
24
ANON_CONFIG = BotoCoreConfig (signature_version = botocore .UNSIGNED )
23
25
@@ -30,6 +32,48 @@ async def not_found(request):
30
32
return web .Response (status = 404 )
31
33
32
34
35
+ async def _normalize_filename (filename ):
36
+ if filename .endswith (".whl" ):
37
+ name , ver , build , tags = parse_wheel_filename (filename )
38
+ return (
39
+ "-" .join (
40
+ [
41
+ canonicalize_name (name ),
42
+ canonicalize_version (ver ),
43
+ ]
44
+ + (["" .join (str (x ) for x in build )] if build else [])
45
+ + [
46
+ "-" .join (str (x ) for x in tags ),
47
+ ]
48
+ )
49
+ + ".whl"
50
+ )
51
+ elif filename .endswith (".tar.gz" ):
52
+ name , ver = parse_sdist_filename (filename )
53
+ return (
54
+ "-" .join (
55
+ [
56
+ canonicalize_name (name ),
57
+ canonicalize_version (ver ),
58
+ ]
59
+ )
60
+ + ".tar.gz"
61
+ )
62
+ elif filename .endswith (".zip" ):
63
+ name , ver = parse_sdist_filename (filename )
64
+ return (
65
+ "-" .join (
66
+ [
67
+ canonicalize_name (name ),
68
+ canonicalize_version (ver ),
69
+ ]
70
+ )
71
+ + ".zip"
72
+ )
73
+ else :
74
+ return filename
75
+
76
+
33
77
async def redirect (request ):
34
78
python_version = request .match_info ["python_version" ]
35
79
project_l = request .match_info ["project_l" ]
@@ -38,7 +82,9 @@ async def redirect(request):
38
82
39
83
# If the letter bucket doesn't match the first letter of the project, then
40
84
# there is no point to going any further since it will be a 404 regardless.
41
- if project_l != project_name [0 ]:
85
+ # Allow specifiying the exact first character of the actual filename (which
86
+ # might not be lowercase, to maintain backwards compatibility
87
+ if project_l != project_name [0 ].lower () and project_l != project_name [0 ]:
42
88
return web .Response (status = 404 , headers = {"Reason" : "Incorrect project bucket" })
43
89
44
90
# If the filename we're looking for is a signature, then we'll need to turn
@@ -72,8 +118,13 @@ async def redirect(request):
72
118
# 302 redirect to that URL.
73
119
for release in data .get ("releases" , {}).values ():
74
120
for file_ in release :
75
- if (file_ ["filename" ] == filename
76
- and file_ ["python_version" ] == python_version ):
121
+ if (
122
+ # Prefer that the normalized filename has been specified
123
+ _normalize_filename (file_ ["filename" ]) == filename
124
+ # But also allow specifying the exact filename, to maintain
125
+ # backwards compatiblity
126
+ or file_ ["filename" ] == filename
127
+ ) and file_ ["python_version" ] == python_version :
77
128
# If we've found our filename, but we were actually looking for
78
129
# the *signature* of that file, then we need to check if it has
79
130
# a signature associated with it, and if so redirect to that,
0 commit comments