1414import httpx
1515
1616from .codes import RDAPStatusCodes
17- from .errors import RateLimitError , NotFoundError , MalformedQueryError , BadStatusCode
17+ from .errors import (
18+ RateLimitError ,
19+ NotFoundError ,
20+ MalformedQueryError ,
21+ BadStatusCode ,
22+ WhodapError ,
23+ )
1824from .response import DomainResponse , IPv4Response , IPv6Response , ASNResponse
1925
2026
@@ -33,7 +39,6 @@ def __init__(self, httpx_client: Union[httpx.Client, httpx.AsyncClient]):
3339 self .httpx_client = httpx_client
3440 self .version : str = ""
3541 self .publication : str = ""
36- self .rdap_hrefs : List [str ] = []
3742 self ._target : Union [str , int , ipaddress .IPv4Address , ipaddress .IPv6Address ] = ""
3843
3944 def lookup (self , * args , ** kwargs ):
@@ -97,7 +102,7 @@ def new_client_context(cls, httpx_client: Optional[httpx.Client] = None):
97102 @classmethod
98103 def new_client (cls , httpx_client : Optional [httpx .Client ] = None ):
99104 """
100- Classmethod for instantiating an synchronous instance of Client
105+ Classmethod for instantiating a synchronous instance of Client
101106
102107 :httpx_client: pre-configured instance of `httpx.Client`
103108 :return: DNSClient with a sync httpx_client
@@ -175,7 +180,7 @@ async def _aio_get_request(self, uri: str) -> httpx.Response:
175180 return await self .httpx_client .get (uri )
176181
177182 def _get_authoritative_response (
178- self , href : str , depth : int = 0
183+ self , href : str , seen : List [ str ], depth : int = 0
179184 ) -> Optional [httpx .Response ]:
180185 """
181186 Makes HTTP calls to RDAP servers until it finds
@@ -196,8 +201,6 @@ def _get_authoritative_response(
196201 return None
197202 else :
198203 raise
199- # save href chain
200- self .rdap_hrefs .append (href )
201204 # check for more authoritative source
202205 try :
203206 # If for some reason the response is invalid json, then just return None.
@@ -209,13 +212,16 @@ def _get_authoritative_response(
209212 links = rdap_json .get ("links" )
210213 if links :
211214 next_href = self ._check_next_href (href , links )
212- if next_href :
213- resp = self ._get_authoritative_response (next_href , depth + 1 ) or resp
215+ if next_href and next_href not in seen :
216+ seen .append (next_href )
217+ resp = (
218+ self ._get_authoritative_response (next_href , seen , depth + 1 ) or resp
219+ )
214220 # return authoritative response
215221 return resp
216222
217223 async def _aio_get_authoritative_response (
218- self , href : str , depth : int = 0
224+ self , href : str , seen : List [ str ], depth : int = 0
219225 ) -> Optional [httpx .Response ]:
220226 """
221227 Makes HTTP calls to RDAP servers until it finds
@@ -236,8 +242,6 @@ async def _aio_get_authoritative_response(
236242 return None
237243 else :
238244 raise
239- # save href chain
240- self .rdap_hrefs .append (href )
241245 try :
242246 # If for some reason the response is invalid json, then just return None.
243247 # This may happen if we request an authoritative href that is not actually
@@ -248,9 +252,12 @@ async def _aio_get_authoritative_response(
248252 links = rdap_json .get ("links" )
249253 if links :
250254 next_href = self ._check_next_href (href , links )
251- if next_href :
255+ if next_href and next_href not in seen :
256+ seen .append (next_href )
252257 resp = (
253- await self ._aio_get_authoritative_response (next_href , depth + 1 )
258+ await self ._aio_get_authoritative_response (
259+ next_href , seen , depth + 1
260+ )
254261 or resp
255262 )
256263 return resp
@@ -341,7 +348,7 @@ def lookup(self, domain: str, tld: str, auth_href: str = None) -> DomainResponse
341348 # build query href
342349 href = self ._build_query_href (base_href , self ._target )
343350 # get response
344- rdap_resp = self ._get_authoritative_response (href )
351+ rdap_resp = self ._get_authoritative_response (href , [ href ] )
345352 # construct and return domain response
346353 domain_response = DomainResponse .from_json (rdap_resp .read ())
347354 return domain_response
@@ -373,7 +380,7 @@ async def aio_lookup(
373380 # build query href
374381 href = self ._build_query_href (base_href , self ._target )
375382 # get response
376- rdap_resp = await self ._aio_get_authoritative_response (href )
383+ rdap_resp = await self ._aio_get_authoritative_response (href , [ href ] )
377384 # construct and return domain response
378385 domain_response = DomainResponse .from_json (rdap_resp .read ())
379386 return domain_response
@@ -430,7 +437,7 @@ def lookup(
430437 self ._target = ipv4
431438 server = self ._get_rdap_server (self ._target )
432439 href = self ._build_query_href (server , str (self ._target ))
433- rdap_resp = self ._get_authoritative_response (href )
440+ rdap_resp = self ._get_authoritative_response (href , [ href ] )
434441 ipv4_response = IPv4Response .from_json (rdap_resp .read ())
435442 return ipv4_response
436443
@@ -453,7 +460,7 @@ async def aio_lookup(
453460 self ._target = ipv4
454461 server = self ._get_rdap_server (self ._target )
455462 href = self ._build_query_href (server , str (self ._target ))
456- rdap_resp = await self ._aio_get_authoritative_response (href )
463+ rdap_resp = await self ._aio_get_authoritative_response (href , [ href ] )
457464 ipv4_response = IPv4Response .from_json (rdap_resp .read ())
458465 return ipv4_response
459466
@@ -507,8 +514,10 @@ def lookup(
507514 else :
508515 self ._target = ipv6
509516 server = self ._get_rdap_server (self ._target )
517+ if server is None :
518+ raise WhodapError (f"No RDAP server found for IPv6={ ipv6 } " )
510519 href = self ._build_query_href (server , str (self ._target ))
511- rdap_resp = self ._get_authoritative_response (href )
520+ rdap_resp = self ._get_authoritative_response (href , [ href ] )
512521 ipv6_response = IPv6Response .from_json (rdap_resp .read ())
513522 return ipv6_response
514523
@@ -530,8 +539,10 @@ async def aio_lookup(
530539 else :
531540 self ._target = ipv6
532541 server = self ._get_rdap_server (self ._target )
542+ if server is None :
543+ raise WhodapError (f"No RDAP server found for IPv6={ ipv6 } " )
533544 href = self ._build_query_href (server , str (self ._target ))
534- rdap_resp = await self ._aio_get_authoritative_response (href )
545+ rdap_resp = await self ._aio_get_authoritative_response (href , [ href ] )
535546 ipv6_response = IPv6Response .from_json (rdap_resp .read ())
536547 return ipv6_response
537548
@@ -575,7 +586,7 @@ def lookup(self, asn: int, auth_href: str = None) -> ASNResponse:
575586 self ._target = asn
576587 server = self ._get_rdap_server (asn )
577588 href = self ._build_query_href (server , str (asn ))
578- rdap_resp = self ._get_authoritative_response (href )
589+ rdap_resp = self ._get_authoritative_response (href , [ href ] )
579590 asn_response = ASNResponse .from_json (rdap_resp .read ())
580591 return asn_response
581592
@@ -590,7 +601,7 @@ async def aio_lookup(self, asn: int, auth_href: str = None) -> ASNResponse:
590601 self ._target = asn
591602 server = self ._get_rdap_server (asn )
592603 href = self ._build_query_href (server , str (asn ))
593- rdap_resp = await self ._aio_get_authoritative_response (href )
604+ rdap_resp = await self ._aio_get_authoritative_response (href , [ href ] )
594605 asn_response = ASNResponse .from_json (rdap_resp .read ())
595606 return asn_response
596607
0 commit comments