Skip to content
This repository was archived by the owner on Feb 3, 2024. It is now read-only.

Commit 481595a

Browse files
authored
Merge pull request #325 from maarten-boot/master
sync with mboot-github/WhoisDomain
2 parents 81810ca + d6be7c6 commit 481595a

File tree

7 files changed

+84
-50
lines changed

7 files changed

+84
-50
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ This package will not support querying ip CIDR ranges or AS information
3232

3333
## Docker
3434
* docker pull mbootgithub/whoisdomain:latest
35-
35+
3636
## Help Wanted
3737
Your contributions are welcome, look for the Help wanted tag https://github.com/DannyCork/python-whois/labels/help%20wanted
3838

@@ -113,6 +113,9 @@ Raise an issue https://github.com/DannyCork/python-whois/issues/new
113113
* whois.NoneStringsAdd(str) to add additional string for NoSuchDomainExists detection (whois.query() retuning None). whois.NoneStrings() lsts the current configured strings
114114
* suppress messages to stderr if not verbose=True
115115

116+
2023-07-20: maarten_boot
117+
* sync with https://github.com/mboot-github/WhoisDomain (gov.tr), (com.ru, msk.ru, spb.ru), (option to preserve partial output after timeout)
118+
116119
## Support
117120
* Python 3.x is supported for x >= 6
118121
* Python 2.x IS NOT supported.

whois/_1_query.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import os
55
import platform
66
import json
7+
import shutil
78
from .exceptions import WhoisCommandFailed, WhoisCommandTimeout
89

910
from typing import Dict, List, Optional, Tuple
@@ -13,6 +14,13 @@
1314
CACHE: Dict[str, Tuple[int, str]] = {}
1415
CACHE_MAX_AGE = 60 * 60 * 48 # 48h
1516

17+
IS_WINDOWS = platform.system() == "Windows"
18+
19+
if not IS_WINDOWS and shutil.which("stdbuf"):
20+
STDBUF_OFF_CMD = ["stdbuf", "-o0"]
21+
else:
22+
STDBUF_OFF_CMD = []
23+
1624

1725
def _cache_load(cf: str) -> None:
1826
if not os.path.isfile(cf):
@@ -88,7 +96,7 @@ def _makeWhoisCommandToRun(
8896
else:
8997
whList = [wh]
9098

91-
if platform.system() == "Windows":
99+
if IS_WINDOWS:
92100
if wh == "whois": # only if the use did not specify what whois to use
93101
if os.path.exists("whois.exe"):
94102
wh = r".\whois.exe"
@@ -122,6 +130,7 @@ def _do_whois_query(
122130
server: Optional[str] = None,
123131
verbose: bool = False,
124132
timeout: Optional[float] = None,
133+
parse_partial_response: bool = False,
125134
wh: str = "whois",
126135
simplistic: bool = False,
127136
) -> str:
@@ -141,7 +150,8 @@ def _do_whois_query(
141150

142151
# LANG=en is added to make the ".jp" output consist across all environments
143152
p = subprocess.Popen(
144-
cmd,
153+
# STDBUF_OFF_CMD needed to not lose data on kill
154+
STDBUF_OFF_CMD + cmd,
145155
stdout=subprocess.PIPE,
146156
stderr=subprocess.STDOUT,
147157
env={"LANG": "en"} if dl[-1] in ".jp" else None,
@@ -152,8 +162,12 @@ def _do_whois_query(
152162
except subprocess.TimeoutExpired:
153163
# Kill the child process & flush any output buffers
154164
p.kill()
155-
p.communicate()
156-
raise WhoisCommandTimeout(f"timeout: query took more then {timeout} seconds")
165+
r = p.communicate()[0].decode(errors="ignore")
166+
# In most cases whois servers returns partial domain data really fast
167+
# after that delay occurs (probably intentional) before returning contact data.
168+
# Add this option to cover those cases
169+
if not parse_partial_response or not r:
170+
raise WhoisCommandTimeout(f"timeout: query took more then {timeout} seconds")
157171

158172
if verbose:
159173
print(r, file=sys.stderr)
@@ -187,6 +201,7 @@ def do_query(
187201
server: Optional[str] = None,
188202
verbose: bool = False,
189203
timeout: Optional[float] = None,
204+
parse_partial_response: bool = False,
190205
wh: str = "whois",
191206
simplistic: bool = False,
192207
) -> str:
@@ -216,6 +231,7 @@ def do_query(
216231
server=server,
217232
verbose=verbose,
218233
timeout=timeout,
234+
parse_partial_response=parse_partial_response,
219235
wh=wh,
220236
simplistic=simplistic,
221237
),

whois/_2_parse.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,18 @@
2727
"the domain has not been registered",
2828
"no match found for",
2929
"no matching record",
30+
"no match",
3031
"not found",
3132
"no data found",
3233
"no entries found",
33-
"status: free",
34+
# "status: free", # we should not interprete the result if there is a result
3435
"no such domain",
3536
"the queried object does not exist",
3637
"domain you requested is not known",
37-
"status: available",
38+
# "status: available", # we should not interprete the result if there is a result
3839
"no whois server is known for this kind of object",
3940
"nameserver not found",
4041
"malformed request", # this means this domain is not in whois as it is on top of a registered domain
41-
"no match",
4242
"registration of this domain is restricted",
4343
"restricted",
4444
"this domain is currently available",
@@ -288,7 +288,7 @@ def cleanupWhoisResponse(
288288

289289

290290
def NoneStrings() -> List[str]:
291-
return sorted(NONESTRINGS)
291+
return NONESTRINGS
292292

293293

294294
def NoneStringsAdd(aString: str) -> None:
@@ -297,7 +297,7 @@ def NoneStringsAdd(aString: str) -> None:
297297

298298

299299
def QuotaStrings() -> List[str]:
300-
return sorted(QUOTASTRINGS)
300+
return QUOTASTRINGS
301301

302302

303303
def QuotaStringsAdd(aString: str) -> None:

whois/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ def _doOneLookup(
258258
with_cleanup_results: bool = False,
259259
include_raw_whois_text: bool = False,
260260
timeout: Optional[float] = None,
261+
parse_partial_response: bool = False,
261262
wh: str = "whois",
262263
simplistic: bool = False,
263264
withRedacted: bool = False,
@@ -274,6 +275,7 @@ def _doOneLookup(
274275
server=server,
275276
verbose=verbose,
276277
timeout=timeout,
278+
parse_partial_response=parse_partial_response,
277279
wh=wh,
278280
simplistic=simplistic,
279281
)
@@ -344,6 +346,7 @@ def query(
344346
include_raw_whois_text: bool = False,
345347
return_raw_text_for_unsupported_tld: bool = False,
346348
timeout: Optional[float] = None,
349+
parse_partial_response: bool = False,
347350
cmd: str = "whois",
348351
simplistic: bool = False,
349352
withRedacted: bool = False,
@@ -366,6 +369,8 @@ def query(
366369
return_raw_text_for_unsupported_tld:
367370
if the tld is unsupported, just try it anyway but return only the raw text.
368371
timeout: timeout in seconds for the whois command to return a result.
372+
parse_partial_response:
373+
try to parse partial response when cmd timed out (stdbuf should be in PATH for best results)
369374
cmd: explicitly specify the path to the whois you want to use.
370375
simplistic: when simplistic is True we return None for most exceptions and dont pass info why we have no data.
371376
withRedacted: show redacted output , default no redacted data is shown
@@ -464,6 +469,7 @@ def query(
464469
with_cleanup_results=with_cleanup_results,
465470
include_raw_whois_text=include_raw_whois_text,
466471
timeout=timeout,
472+
parse_partial_response=parse_partial_response,
467473
wh=wh,
468474
simplistic=simplistic,
469475
withRedacted=withRedacted,

whois/main.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,8 @@
1414
Dict,
1515
)
1616

17-
import whois # to be compatible with dannycork
18-
1917
# import whoisdomain as whois # to be compatible with dannycork
18+
import whois
2019

2120
# if we are not running as test2.py run in a simplistic way
2221
SIMPLISTIC: bool = False

whois/tld_regexpr.py

Lines changed: 47 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"extend": None,
2525
"domain_name": r"Domain Name\s*:\s*(.+)",
2626
"registrar": r"Registrar:\s?(.+)",
27-
"registrant": r"Registrant\s*Organi(?:s|z)ation:\s?(.+)",
27+
"registrant": r"Registrant\s*Organi(?:s|z)ation:([^\n]*)", # this seems to match Registrant Street: if Registrant Organization: is empty
2828
"registrant_country": r"Registrant Country:\s?(.+)",
2929
"creation_date": r"Creation Date:[ \t]*([^\n]*)",
3030
"expiration_date": r"(?:Expiry|Expiration) Date:[ \t]*([^\n]*)", # Expiration Date
@@ -287,6 +287,8 @@
287287
"extend": "au",
288288
}
289289

290+
ZZ["tr"] = {"extend": "_privateReg"} # whois.nic.tr is an alias for whois.trabis.gov.tr.
291+
290292
ZZ["com.tr"] = {
291293
"extend": "com",
292294
"domain_name": r"\*\* Domain Name:\s?(.+)",
@@ -296,16 +298,24 @@
296298
"creation_date": r"Created on\.+:\s?(.+).",
297299
"expiration_date": r"Expires on\.+:\s?(.+).", # note the trailing . on both dates fields
298300
"updated_date": "",
299-
"name_servers": r"\*\* Domain Servers:\n(?:(\S+)\n)(?:(\S+)\n)?(?:(\S+)\n)?(?:(\S+)\n)?(?:(\S+)\n)?(?:(\S+)\n)\n?",
301+
# "name_servers": r"\*\* Domain Servers:\n(?:(\S+)\n)(?:(\S+)\n)?(?:(\S+)\n)?(?:(\S+)\n)?(?:(\S+)\n)?(?:(\S+)\n)\n?",
302+
"name_servers": r"\*\* Domain Servers:\n(?:(\S+).*\n)?(?:(\S+).*\n)?(?:(\S+).*\n)?(?:(\S+).*\n)?", # allow for ip addresses after the name server
300303
"status": None,
304+
"_server": "whois.trabis.gov.tr",
305+
# "_test": "googl.com.tr"
301306
}
302307

303-
ZZ["edu.tr"] = {"extend": "com.tr"}
308+
ZZ["gov.tr"] = {
309+
"extend": "com.tr",
310+
# "name_servers": r"\*\* Domain Servers:\n(?:(\S+).*\n)?(?:(\S+).*\n)?(?:(\S+).*\n)?(?:(\S+).*\n)?", # this has ip addresses after the nameserver
311+
# "_test": "www.turkiye.gov.tr",
312+
}
304313

314+
ZZ["edu.tr"] = {"extend": "com.tr"}
305315
ZZ["org.tr"] = {"extend": "com.tr"}
306-
307316
ZZ["net.tr"] = {"extend": "com.tr"}
308317

318+
309319
ZZ["co.il"] = {
310320
"extend": "com",
311321
"domain_name": r"domain:\s*(.+)",
@@ -664,10 +674,14 @@
664674
}
665675

666676
ZZ["lv"] = {
667-
"extend": "ru",
668-
"creation_date": r"Registered:\s*(.+)\n",
677+
"extend": "com",
678+
"domain_name": r"domain:\s*(.+)",
679+
"creation_date": r"Registered:\s*(.+)\n", # actually there seem to be no dates
669680
"updated_date": r"Changed:\s*(.+)\n",
681+
"expiration_date": r"paid-till:\s*(.+)",
682+
"name_servers": r"nserver:\s*(.+)",
670683
"status": r"Status:\s?(.+)",
684+
"_server": "whois.nic.lv",
671685
}
672686

673687
ZZ["me"] = {
@@ -893,28 +907,25 @@
893907

894908
ZZ["ru"] = {
895909
"extend": "com",
896-
"domain_name": r"\ndomain:\s*(.+)",
897-
"creation_date": r"\ncreated:\s*(.+)",
898-
"expiration_date": r"\npaid-till:\s*(.+)",
899-
"name_servers": r"\nnserver:\s*(.+)",
900-
"status": r"\nstate:\s*(.+)",
910+
"domain_name": r"domain:\s*(.+)",
911+
"creation_date": r"created:\s*(.+)",
912+
"expiration_date": r"paid-till:\s*(.+)",
913+
"name_servers": r"nserver:\s*(.+)",
914+
"status": r"state:\s*(.+)",
915+
"_server": "whois.tcinet.ru",
901916
}
917+
ZZ["com.ru"] = {"extend": "ru", "_server": "whois.nic.ru"} # test: mining.com.ru
918+
919+
# Russian city sub-domains
920+
ZZ["msk.ru"] = {"extend": "com.ru"} # test with: mining.msk.ru
921+
ZZ["spb.ru"] = {"extend": "com.ru"} # test with iac.spb.ru
902922

903923
# Rossíyskaya Federátsiya) is the Cyrillic country code top-level domain for the Russian Federation,
904924
# In the Domain Name System it has the ASCII DNS name xn--p1ai.
905925

906-
ZZ["ru.rf"] = {
907-
"extend": "ru",
908-
"_server": "whois.tcinet.ru",
909-
}
910-
ZZ["рф"] = {
911-
"extend": "ru",
912-
"_server": "whois.tcinet.ru",
913-
}
914-
ZZ["xn--p1ai"] = {
915-
"extend": "ru",
916-
"_server": "whois.tcinet.ru",
917-
}
926+
ZZ["ru.rf"] = {"extend": "ru"}
927+
ZZ["рф"] = {"extend": "ru"}
928+
ZZ["xn--p1ai"] = {"extend": "ru"}
918929

919930
ZZ["sa"] = {
920931
"extend": "com",
@@ -1161,23 +1172,10 @@
11611172
ZZ["net.za"] = {"extend": "za", "_server": "net-whois.registry.net.za"}
11621173
ZZ["co.za"] = {"extend": "za", "_server": "coza-whois.registry.net.za"}
11631174

1164-
# Kenia
1165-
# ke : http://www.kenic.or.ke/index.php/en/ke-domains/ke-domains
1166-
ZZ["ke"] = {"extend": "com", "_server": "whois.kenic.or.ke"}
1167-
ZZ["ac.ke"] = {"extend": "ke"}
1168-
ZZ["co.ke"] = {"extend": "ke"}
1169-
ZZ["go.ke"] = {"extend": "ke"}
1170-
ZZ["info.ke"] = {"extend": "ke"}
1171-
ZZ["me.ke"] = {"extend": "ke"}
1172-
ZZ["mobi.ke"] = {"extend": "ke"}
1173-
ZZ["ne.ke"] = {"extend": "ke"}
1174-
ZZ["or.ke"] = {"extend": "ke"}
1175-
ZZ["sc.ke"] = {"extend": "ke"}
11761175
ZZ["gy"] = {"extend": "com"}
11771176

11781177
# Multiple initialization
11791178
ZZ["ca"] = {"extend": "bank"}
1180-
11811179
# Rwanda: https://en.wikipedia.org/wiki/.rw
11821180
ZZ["rw"] = {"extend": "com", "_server": "whois.ricta.org.rw"}
11831181
ZZ[".co.rw"] = {"extend": "rw"}
@@ -1957,6 +1955,19 @@
19571955
ZZ["com.py"] = {"extend": "_privateReg"}
19581956
ZZ["sr"] = {"extend": "_privateReg"}
19591957

1958+
# Kenia
1959+
# ke : http://www.kenic.or.ke/index.php/en/ke-domains/ke-domains
1960+
ZZ["ke"] = {"extend": "com", "_server": "whois.kenic.or.ke"}
1961+
ZZ["ac.ke"] = {"extend": "ke"}
1962+
ZZ["co.ke"] = {"extend": "ke"}
1963+
ZZ["go.ke"] = {"extend": "ke"}
1964+
ZZ["info.ke"] = {"extend": "ke"}
1965+
ZZ["me.ke"] = {"extend": "ke"}
1966+
ZZ["mobi.ke"] = {"extend": "ke"}
1967+
ZZ["ne.ke"] = {"extend": "ke"}
1968+
ZZ["or.ke"] = {"extend": "ke"}
1969+
ZZ["sc.ke"] = {"extend": "ke"}
1970+
19601971
# https://www.iana.org/domains/root/db/td.html
19611972
# td = {"extend": "_privateReg"} # Chad (French: Tchad) made available for use in 1997.
19621973

@@ -2073,7 +2084,6 @@
20732084
ZZ["net.ph"] = {"extend": "ph"}
20742085
ZZ["zm"] = {"extend": "com"}
20752086
ZZ["sy"] = {"extend": "_privateReg", "_server": "whois.tld.sy"}
2076-
ZZ["tr"] = {"extend": "_privateReg"}
20772087
ZZ["onl"] = {"extend": "com"}
20782088
ZZ["blue"] = {"extend": "com"}
20792089
ZZ["garden"] = {"extend": "com", "_server": "whois.nic.garden"}

whois/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
VERSION = "1.20230525.9"
1+
VERSION = "1.20230718.3"

0 commit comments

Comments
 (0)