1616class _RESP3Parser (_RESPBase , PushNotificationsParser ):
1717 """RESP3 protocol implementation"""
1818
19+ MAX_NESTING_DEPTH = 100
20+
1921 def __init__ (self , socket_read_size ):
2022 super ().__init__ (socket_read_size )
2123 self .pubsub_push_handler_func = self .handle_pubsub_push_response
@@ -61,6 +63,7 @@ def _read_response(
6163 disable_decoding = False ,
6264 push_request = False ,
6365 timeout : Union [float , object ] = SENTINEL ,
66+ _depth = 0 ,
6467 ):
6568 raw = self ._buffer .readline (timeout = timeout )
6669 if not raw :
@@ -106,41 +109,69 @@ def _read_response(
106109 response = self ._buffer .read (int (response ), timeout = timeout )[4 :]
107110 # array response
108111 elif byte == b"*" :
112+ if _depth >= self .MAX_NESTING_DEPTH :
113+ raise InvalidResponse (
114+ f"Response nesting depth exceeded { self .MAX_NESTING_DEPTH } "
115+ )
109116 response = [
110- self ._read_response (disable_decoding = disable_decoding , timeout = timeout )
117+ self ._read_response (
118+ disable_decoding = disable_decoding ,
119+ timeout = timeout ,
120+ _depth = _depth + 1 ,
121+ )
111122 for _ in range (int (response ))
112123 ]
113124 # set response
114125 elif byte == b"~" :
126+ if _depth >= self .MAX_NESTING_DEPTH :
127+ raise InvalidResponse (
128+ f"Response nesting depth exceeded { self .MAX_NESTING_DEPTH } "
129+ )
115130 # redis can return unhashable types (like dict) in a set,
116131 # so we return sets as list, all the time, for predictability
117132 response = [
118- self ._read_response (disable_decoding = disable_decoding , timeout = timeout )
133+ self ._read_response (
134+ disable_decoding = disable_decoding ,
135+ timeout = timeout ,
136+ _depth = _depth + 1 ,
137+ )
119138 for _ in range (int (response ))
120139 ]
121140 # map response
122141 elif byte == b"%" :
142+ if _depth >= self .MAX_NESTING_DEPTH :
143+ raise InvalidResponse (
144+ f"Response nesting depth exceeded { self .MAX_NESTING_DEPTH } "
145+ )
123146 # We cannot use a dict-comprehension to parse stream.
124147 # Evaluation order of key:val expression in dict comprehension only
125148 # became defined to be left-right in version 3.8
126149 resp_dict = {}
127150 for _ in range (int (response )):
128151 key = self ._read_response (
129- disable_decoding = disable_decoding , timeout = timeout
152+ disable_decoding = disable_decoding ,
153+ timeout = timeout ,
154+ _depth = _depth + 1 ,
130155 )
131156 resp_dict [key ] = self ._read_response (
132157 disable_decoding = disable_decoding ,
133158 push_request = push_request ,
134159 timeout = timeout ,
160+ _depth = _depth + 1 ,
135161 )
136162 response = resp_dict
137163 # push response
138164 elif byte == b">" :
165+ if _depth >= self .MAX_NESTING_DEPTH :
166+ raise InvalidResponse (
167+ f"Response nesting depth exceeded { self .MAX_NESTING_DEPTH } "
168+ )
139169 response = [
140170 self ._read_response (
141171 disable_decoding = disable_decoding ,
142172 push_request = push_request ,
143173 timeout = timeout ,
174+ _depth = _depth + 1 ,
144175 )
145176 for _ in range (int (response ))
146177 ]
@@ -164,6 +195,8 @@ def _read_response(
164195
165196
166197class _AsyncRESP3Parser (_AsyncRESPBase , AsyncPushNotificationsParser ):
198+ MAX_NESTING_DEPTH = 100
199+
167200 def __init__ (self , socket_read_size ):
168201 super ().__init__ (socket_read_size )
169202 self .pubsub_push_handler_func = self .handle_pubsub_push_response
@@ -190,7 +223,7 @@ async def read_response(
190223 return response
191224
192225 async def _read_response (
193- self , disable_decoding : bool = False , push_request : bool = False
226+ self , disable_decoding : bool = False , push_request : bool = False , _depth : int = 0
194227 ) -> Union [EncodableT , ResponseError , None ]:
195228 if not self ._stream or not self .encoder :
196229 raise ConnectionError (SERVER_CLOSED_CONNECTION_ERROR )
@@ -240,36 +273,54 @@ async def _read_response(
240273 response = (await self ._read (int (response )))[4 :]
241274 # array response
242275 elif byte == b"*" :
276+ if _depth >= self .MAX_NESTING_DEPTH :
277+ raise InvalidResponse (
278+ f"Response nesting depth exceeded { self .MAX_NESTING_DEPTH } "
279+ )
243280 response = [
244- (await self ._read_response (disable_decoding = disable_decoding ))
281+ (await self ._read_response (disable_decoding , _depth = _depth + 1 ))
245282 for _ in range (int (response ))
246283 ]
247284 # set response
248285 elif byte == b"~" :
286+ if _depth >= self .MAX_NESTING_DEPTH :
287+ raise InvalidResponse (
288+ f"Response nesting depth exceeded { self .MAX_NESTING_DEPTH } "
289+ )
249290 # redis can return unhashable types (like dict) in a set,
250291 # so we always convert to a list, to have predictable return types
251292 response = [
252- (await self ._read_response (disable_decoding = disable_decoding ))
293+ (await self ._read_response (disable_decoding , _depth = _depth + 1 ))
253294 for _ in range (int (response ))
254295 ]
255296 # map response
256297 elif byte == b"%" :
298+ if _depth >= self .MAX_NESTING_DEPTH :
299+ raise InvalidResponse (
300+ f"Response nesting depth exceeded { self .MAX_NESTING_DEPTH } "
301+ )
257302 # We cannot use a dict-comprehension to parse stream.
258303 # Evaluation order of key:val expression in dict comprehension only
259304 # became defined to be left-right in version 3.8
260305 resp_dict = {}
261306 for _ in range (int (response )):
262- key = await self ._read_response (disable_decoding = disable_decoding )
307+ key = await self ._read_response (
308+ disable_decoding , _depth = _depth + 1
309+ )
263310 resp_dict [key ] = await self ._read_response (
264- disable_decoding = disable_decoding , push_request = push_request
311+ disable_decoding , push_request = push_request , _depth = _depth + 1
265312 )
266313 response = resp_dict
267314 # push response
268315 elif byte == b">" :
316+ if _depth >= self .MAX_NESTING_DEPTH :
317+ raise InvalidResponse (
318+ f"Response nesting depth exceeded { self .MAX_NESTING_DEPTH } "
319+ )
269320 response = [
270321 (
271322 await self ._read_response (
272- disable_decoding = disable_decoding , push_request = push_request
323+ disable_decoding , push_request = push_request , _depth = _depth + 1
273324 )
274325 )
275326 for _ in range (int (response ))
0 commit comments