Skip to content

Commit 6e63e83

Browse files
authored
Merge pull request #38 from pogzyb/bugfix/infinite-looping
Bugfix/infinite looping
2 parents c9a1492 + f272d91 commit 6e63e83

File tree

3 files changed

+42
-25
lines changed

3 files changed

+42
-25
lines changed

whodap/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def lookup_domain(
3131
submits an RDAP query for the given domain, and returns
3232
the result as a DomainResponse.
3333
34-
:param domain: the domain name to lookup
34+
:param domain: the domain name to query
3535
:param tld: the top level domain (e.g. "com", "net", "buzz")
3636
:param httpx_client: Optional preconfigured instance of `httpx.Client`
3737
:return: an instance of DomainResponse
@@ -55,7 +55,7 @@ async def aio_lookup_domain(
5555
a DNSClient, submits an RDAP query for the given domain,
5656
and returns the result as a DomainResponse.
5757
58-
:param domain: the domain name to lookup
58+
:param domain: the domain name to query
5959
:param tld: the top level domain (e.g. "com", "net", "buzz")
6060
:param httpx_client: Optional preconfigured instance of `httpx.AsyncClient`
6161
:return: an instance of DomainResponse

whodap/client.py

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,13 @@
1414
import httpx
1515

1616
from .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+
)
1824
from .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

whodap/response.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def to_whois_dict(
173173
does not modify the original DomainResponse object.
174174
175175
:param strict: If True, raises an RDAPConformanceException if
176-
the given RDAP response is incorrectly formatted. Otherwise
176+
the given RDAP response is incorrectly formatted. Otherwise,
177177
if False, the method will attempt to parse the RDAP response
178178
without raising any exception.
179179
:return: dict with WHOIS keys
@@ -184,7 +184,13 @@ def to_whois_dict(
184184
if getattr(self, "nameservers", None):
185185
flat_nameservers = {"nameservers": []}
186186
for obj in self.nameservers:
187-
flat_nameservers["nameservers"].append(obj.ldhName)
187+
if hasattr(obj, "ldhName"):
188+
flat_nameservers["nameservers"].append(obj.ldhName)
189+
# if hostnames are not given, try ipv4 addresses
190+
elif hasattr(obj, "ipAddresses"):
191+
if hasattr(obj.ipAddresses, "v4"):
192+
flat_nameservers["nameservers"].extend(obj.ipAddresses.v4)
193+
188194
flat.update(flat_nameservers)
189195

190196
if getattr(self, "status", None):

0 commit comments

Comments
 (0)