1
1
import aiohttp
2
2
import asyncio
3
+ import fnmatch
3
4
import json
4
5
import re
5
6
10
11
11
12
from pulpcore .plugin .download import DownloaderFactory , HttpDownloader
12
13
13
- from pulp_container .constants import V2_ACCEPT_HEADERS
14
+ from pulp_container .constants import (
15
+ MANIFEST_MEDIA_TYPES ,
16
+ MANIFEST_PAYLOAD_MAX_SIZE ,
17
+ MEGABYTE ,
18
+ SIGNATURE_PAYLOAD_MAX_SIZE ,
19
+ V2_ACCEPT_HEADERS ,
20
+ )
21
+ from pulp_container .app .exceptions import InvalidRequest
14
22
15
23
log = getLogger (__name__ )
16
24
20
28
)
21
29
22
30
23
- class RegistryAuthHttpDownloader (HttpDownloader ):
31
+ class ValidateResourceSizeMixin :
32
+ async def validate_resource_size (self , response , request_method = None ):
33
+ """
34
+ Verify if the constrained resources are not exceeding the maximum size allowed.
35
+ """
36
+ if request_method == "head" :
37
+ return
38
+
39
+ content_type = response .content_type
40
+ max_resource_size = 0
41
+ is_cosign_tag = fnmatch .fnmatch (response .url .name , "sha256-*.sig" )
42
+
43
+ if isinstance (self , NoAuthSignatureDownloader ) or is_cosign_tag :
44
+ max_resource_size = SIGNATURE_PAYLOAD_MAX_SIZE
45
+ content_type = "Signature"
46
+ elif content_type in MANIFEST_MEDIA_TYPES .IMAGE + MANIFEST_MEDIA_TYPES .LIST :
47
+ max_resource_size = MANIFEST_PAYLOAD_MAX_SIZE
48
+ content_type = "Manifest"
49
+ else :
50
+ return
51
+
52
+ total_size = 0
53
+ buffer = b""
54
+ async for chunk in response .content .iter_chunked (MEGABYTE ):
55
+ total_size += len (chunk )
56
+ buffer += chunk
57
+ if total_size > max_resource_size :
58
+ raise InvalidRequest (
59
+ f"{ content_type } size exceeded the { max_resource_size } bytes "
60
+ f"limit ({ total_size } bytes)."
61
+ )
62
+ response .content .unread_data (buffer )
63
+
64
+
65
+ class RegistryAuthHttpDownloader (HttpDownloader , ValidateResourceSizeMixin ):
24
66
"""
25
67
Custom Downloader that automatically handles Token Based and Basic Authentication.
26
68
@@ -77,6 +119,7 @@ async def _run(self, handle_401=True, extra_data=None):
77
119
async with session_http_method (
78
120
self .url , headers = headers , proxy = self .proxy , proxy_auth = self .proxy_auth
79
121
) as response :
122
+ await self .validate_resource_size (response , http_method )
80
123
try :
81
124
response .raise_for_status ()
82
125
except ClientResponseError as e :
@@ -193,7 +236,7 @@ async def _handle_head_response(self, response):
193
236
)
194
237
195
238
196
- class NoAuthSignatureDownloader (HttpDownloader ):
239
+ class NoAuthSignatureDownloader (HttpDownloader , ValidateResourceSizeMixin ):
197
240
"""A downloader class suited for signature downloads."""
198
241
199
242
def raise_for_status (self , response ):
@@ -208,6 +251,20 @@ def raise_for_status(self, response):
208
251
else :
209
252
response .raise_for_status ()
210
253
254
+ async def _run (self , extra_data = None ):
255
+ if self .download_throttler :
256
+ await self .download_throttler .acquire ()
257
+ async with self .session .get (
258
+ self .url , proxy = self .proxy , proxy_auth = self .proxy_auth , auth = self .auth
259
+ ) as response :
260
+ await self .validate_resource_size (response )
261
+ self .raise_for_status (response )
262
+ to_return = await self ._handle_response (response )
263
+ await response .release ()
264
+ if self ._close_session_on_finalize :
265
+ await self .session .close ()
266
+ return to_return
267
+
211
268
212
269
class NoAuthDownloaderFactory (DownloaderFactory ):
213
270
"""
0 commit comments