Skip to content

Commit

Permalink
Merge pull request #38 from pogzyb/bugfix/infinite-looping
Browse files Browse the repository at this point in the history
Bugfix/infinite looping
  • Loading branch information
pogzyb committed Feb 10, 2024
2 parents c9a1492 + f272d91 commit 6e63e83
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 25 deletions.
4 changes: 2 additions & 2 deletions whodap/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def lookup_domain(
submits an RDAP query for the given domain, and returns
the result as a DomainResponse.
:param domain: the domain name to lookup
:param domain: the domain name to query
:param tld: the top level domain (e.g. "com", "net", "buzz")
:param httpx_client: Optional preconfigured instance of `httpx.Client`
:return: an instance of DomainResponse
Expand All @@ -55,7 +55,7 @@ async def aio_lookup_domain(
a DNSClient, submits an RDAP query for the given domain,
and returns the result as a DomainResponse.
:param domain: the domain name to lookup
:param domain: the domain name to query
:param tld: the top level domain (e.g. "com", "net", "buzz")
:param httpx_client: Optional preconfigured instance of `httpx.AsyncClient`
:return: an instance of DomainResponse
Expand Down
53 changes: 32 additions & 21 deletions whodap/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@
import httpx

from .codes import RDAPStatusCodes
from .errors import RateLimitError, NotFoundError, MalformedQueryError, BadStatusCode
from .errors import (
RateLimitError,
NotFoundError,
MalformedQueryError,
BadStatusCode,
WhodapError,
)
from .response import DomainResponse, IPv4Response, IPv6Response, ASNResponse


Expand All @@ -33,7 +39,6 @@ def __init__(self, httpx_client: Union[httpx.Client, httpx.AsyncClient]):
self.httpx_client = httpx_client
self.version: str = ""
self.publication: str = ""
self.rdap_hrefs: List[str] = []
self._target: Union[str, int, ipaddress.IPv4Address, ipaddress.IPv6Address] = ""

def lookup(self, *args, **kwargs):
Expand Down Expand Up @@ -97,7 +102,7 @@ def new_client_context(cls, httpx_client: Optional[httpx.Client] = None):
@classmethod
def new_client(cls, httpx_client: Optional[httpx.Client] = None):
"""
Classmethod for instantiating an synchronous instance of Client
Classmethod for instantiating a synchronous instance of Client
:httpx_client: pre-configured instance of `httpx.Client`
:return: DNSClient with a sync httpx_client
Expand Down Expand Up @@ -175,7 +180,7 @@ async def _aio_get_request(self, uri: str) -> httpx.Response:
return await self.httpx_client.get(uri)

def _get_authoritative_response(
self, href: str, depth: int = 0
self, href: str, seen: List[str], depth: int = 0
) -> Optional[httpx.Response]:
"""
Makes HTTP calls to RDAP servers until it finds
Expand All @@ -196,8 +201,6 @@ def _get_authoritative_response(
return None
else:
raise
# save href chain
self.rdap_hrefs.append(href)
# check for more authoritative source
try:
# If for some reason the response is invalid json, then just return None.
Expand All @@ -209,13 +212,16 @@ def _get_authoritative_response(
links = rdap_json.get("links")
if links:
next_href = self._check_next_href(href, links)
if next_href:
resp = self._get_authoritative_response(next_href, depth + 1) or resp
if next_href and next_href not in seen:
seen.append(next_href)
resp = (
self._get_authoritative_response(next_href, seen, depth + 1) or resp
)
# return authoritative response
return resp

async def _aio_get_authoritative_response(
self, href: str, depth: int = 0
self, href: str, seen: List[str], depth: int = 0
) -> Optional[httpx.Response]:
"""
Makes HTTP calls to RDAP servers until it finds
Expand All @@ -236,8 +242,6 @@ async def _aio_get_authoritative_response(
return None
else:
raise
# save href chain
self.rdap_hrefs.append(href)
try:
# If for some reason the response is invalid json, then just return None.
# This may happen if we request an authoritative href that is not actually
Expand All @@ -248,9 +252,12 @@ async def _aio_get_authoritative_response(
links = rdap_json.get("links")
if links:
next_href = self._check_next_href(href, links)
if next_href:
if next_href and next_href not in seen:
seen.append(next_href)
resp = (
await self._aio_get_authoritative_response(next_href, depth + 1)
await self._aio_get_authoritative_response(
next_href, seen, depth + 1
)
or resp
)
return resp
Expand Down Expand Up @@ -341,7 +348,7 @@ def lookup(self, domain: str, tld: str, auth_href: str = None) -> DomainResponse
# build query href
href = self._build_query_href(base_href, self._target)
# get response
rdap_resp = self._get_authoritative_response(href)
rdap_resp = self._get_authoritative_response(href, [href])
# construct and return domain response
domain_response = DomainResponse.from_json(rdap_resp.read())
return domain_response
Expand Down Expand Up @@ -373,7 +380,7 @@ async def aio_lookup(
# build query href
href = self._build_query_href(base_href, self._target)
# get response
rdap_resp = await self._aio_get_authoritative_response(href)
rdap_resp = await self._aio_get_authoritative_response(href, [href])
# construct and return domain response
domain_response = DomainResponse.from_json(rdap_resp.read())
return domain_response
Expand Down Expand Up @@ -430,7 +437,7 @@ def lookup(
self._target = ipv4
server = self._get_rdap_server(self._target)
href = self._build_query_href(server, str(self._target))
rdap_resp = self._get_authoritative_response(href)
rdap_resp = self._get_authoritative_response(href, [href])
ipv4_response = IPv4Response.from_json(rdap_resp.read())
return ipv4_response

Expand All @@ -453,7 +460,7 @@ async def aio_lookup(
self._target = ipv4
server = self._get_rdap_server(self._target)
href = self._build_query_href(server, str(self._target))
rdap_resp = await self._aio_get_authoritative_response(href)
rdap_resp = await self._aio_get_authoritative_response(href, [href])
ipv4_response = IPv4Response.from_json(rdap_resp.read())
return ipv4_response

Expand Down Expand Up @@ -507,8 +514,10 @@ def lookup(
else:
self._target = ipv6
server = self._get_rdap_server(self._target)
if server is None:
raise WhodapError(f"No RDAP server found for IPv6={ipv6}")
href = self._build_query_href(server, str(self._target))
rdap_resp = self._get_authoritative_response(href)
rdap_resp = self._get_authoritative_response(href, [href])
ipv6_response = IPv6Response.from_json(rdap_resp.read())
return ipv6_response

Expand All @@ -530,8 +539,10 @@ async def aio_lookup(
else:
self._target = ipv6
server = self._get_rdap_server(self._target)
if server is None:
raise WhodapError(f"No RDAP server found for IPv6={ipv6}")
href = self._build_query_href(server, str(self._target))
rdap_resp = await self._aio_get_authoritative_response(href)
rdap_resp = await self._aio_get_authoritative_response(href, [href])
ipv6_response = IPv6Response.from_json(rdap_resp.read())
return ipv6_response

Expand Down Expand Up @@ -575,7 +586,7 @@ def lookup(self, asn: int, auth_href: str = None) -> ASNResponse:
self._target = asn
server = self._get_rdap_server(asn)
href = self._build_query_href(server, str(asn))
rdap_resp = self._get_authoritative_response(href)
rdap_resp = self._get_authoritative_response(href, [href])
asn_response = ASNResponse.from_json(rdap_resp.read())
return asn_response

Expand All @@ -590,7 +601,7 @@ async def aio_lookup(self, asn: int, auth_href: str = None) -> ASNResponse:
self._target = asn
server = self._get_rdap_server(asn)
href = self._build_query_href(server, str(asn))
rdap_resp = await self._aio_get_authoritative_response(href)
rdap_resp = await self._aio_get_authoritative_response(href, [href])
asn_response = ASNResponse.from_json(rdap_resp.read())
return asn_response

Expand Down
10 changes: 8 additions & 2 deletions whodap/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def to_whois_dict(
does not modify the original DomainResponse object.
:param strict: If True, raises an RDAPConformanceException if
the given RDAP response is incorrectly formatted. Otherwise
the given RDAP response is incorrectly formatted. Otherwise,
if False, the method will attempt to parse the RDAP response
without raising any exception.
:return: dict with WHOIS keys
Expand All @@ -184,7 +184,13 @@ def to_whois_dict(
if getattr(self, "nameservers", None):
flat_nameservers = {"nameservers": []}
for obj in self.nameservers:
flat_nameservers["nameservers"].append(obj.ldhName)
if hasattr(obj, "ldhName"):
flat_nameservers["nameservers"].append(obj.ldhName)
# if hostnames are not given, try ipv4 addresses
elif hasattr(obj, "ipAddresses"):
if hasattr(obj.ipAddresses, "v4"):
flat_nameservers["nameservers"].extend(obj.ipAddresses.v4)

flat.update(flat_nameservers)

if getattr(self, "status", None):
Expand Down

0 comments on commit 6e63e83

Please sign in to comment.