Skip to content

Commit fc9ec20

Browse files
committed
Add hosts deserializer
1 parent a8ed923 commit fc9ec20

File tree

4 files changed

+151
-0
lines changed

4 files changed

+151
-0
lines changed

dns_cache/hosts.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
from __future__ import absolute_import
2+
3+
import os.path
4+
import sys
5+
from datetime import timedelta
6+
7+
from dns.name import from_text
8+
from dns.rdataclass import IN
9+
from dns.rdatatype import A, AAAA
10+
11+
from dns.resolver import Cache
12+
13+
from reconfigure.configs import HostsConfig
14+
15+
from .dnspython import create_answer, create_simple_rrset
16+
from .expiration import NoExpirationCacheBase
17+
from .persistence import _DeserializeOnGetCacheBase
18+
19+
_year_in_seconds = timedelta(days=365).total_seconds()
20+
21+
# References to https://en.wikipedia.org/wiki/Hosts_(file)
22+
_WINDOWS_PATHS = [
23+
# Microsoft Windows NT, 2000, XP,[5] 2003, Vista, 2008, 7, 2012, 8, 10
24+
r"${SystemRoot}\System32\drivers\etc\hosts",
25+
# Microsoft Windows 95, 98, ME
26+
r"${WinDir}\hosts",
27+
# Microsoft Windows 3.1
28+
r"${WinDir}\HOSTS",
29+
# Symbian OS 6.1-9.0
30+
r"C:\system\data\hosts",
31+
# Symbian OS 9.1+
32+
r"C:\private\10000882\hosts",
33+
]
34+
_UNIX_PATHS = [
35+
# Unix, Unix-like, POSIX, Apple Macintosh Mac OS X 10.2 and newer,
36+
# Android, iOS 2.0 and newer
37+
"/etc/hosts",
38+
# openSUSE
39+
"/usr/etc/hosts",
40+
]
41+
_OTHER_PARTS = [
42+
# Plan 9
43+
"/lib/ndb/hosts",
44+
# BeOS
45+
"/boot/beos/etc/hosts",
46+
# Haiku
47+
"/boot/common/settings/network/hosts",
48+
]
49+
50+
51+
def guess_hosts_path():
52+
if sys.platform == "win32":
53+
possible_paths = _WINDOWS_PATHS + _UNIX_PATHS + _OTHER_PARTS
54+
else:
55+
possible_paths = _UNIX_PATHS + _OTHER_PARTS + _WINDOWS_PATHS
56+
57+
for path in possible_paths:
58+
path = os.path.expandvars(os.path.expanduser(path))
59+
if os.path.exists(path):
60+
return path
61+
62+
raise RuntimeError()
63+
64+
65+
def _convert_entries(entries, expiration=None):
66+
out_data = []
67+
68+
for entry in entries:
69+
if ":" in entry.address:
70+
rdtype = AAAA
71+
elif "." in entry.address:
72+
rdtype = A
73+
else:
74+
continue
75+
76+
names = [entry.name] + [
77+
alias.name for alias in entry.aliases
78+
]
79+
for name in names:
80+
name = from_text(name)
81+
82+
ip = entry.address
83+
rrset = create_simple_rrset(name, ip, rdtype, rdclass=IN)
84+
rrset.ttl = _year_in_seconds
85+
out_entry = create_answer(name, rrset)
86+
if expiration:
87+
out_entry.expiration = expiration
88+
89+
out_data.append(out_entry)
90+
91+
return out_data
92+
93+
94+
def loads(filename=None):
95+
if not filename:
96+
filename = guess_hosts_path()
97+
98+
mtime = os.path.getmtime(filename)
99+
100+
config = HostsConfig(path=filename)
101+
config.load()
102+
103+
expiration = mtime + _year_in_seconds
104+
dnspython_data = _convert_entries(config.tree.hosts, expiration)
105+
106+
return dnspython_data
107+
108+
109+
class HostsCacheBase(_DeserializeOnGetCacheBase, NoExpirationCacheBase):
110+
def __init__(
111+
self,
112+
filename,
113+
*args,
114+
**kwargs
115+
):
116+
super(HostsCacheBase, self).__init__(
117+
*args,
118+
filename=filename,
119+
deserializer=loads,
120+
**kwargs)
121+
122+
123+
class HostsCache(HostsCacheBase, Cache):
124+
def __init__(self, *args, **kwargs):
125+
super(HostsCache, self).__init__(*args, **kwargs)

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
install_requires=[
3737
"dnspython",
3838
"ProxyTypes",
39+
"reconfigure",
3940
],
4041
classifiers=classifiers.splitlines(),
4142
)

tests/test_hosts.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import unittest
2+
3+
from dns_cache.hosts import loads
4+
5+
6+
class TestHostsSerializer(unittest.TestCase):
7+
def test_loads(self):
8+
data = loads()
9+
assert data

tests/test_persistence.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
except ImportError:
1616
apsw = None
1717

18+
from dns_cache.hosts import HostsCache
1819
from dns_cache.diskcache import DiskCache, DiskLRUCache
1920
from dns_cache.pickle import PickableCache, PickableCacheBase, PickableLRUCache
2021
from dns_cache.sqlitedict import SqliteDictCache, SqliteDictLRUCache
@@ -241,6 +242,12 @@ def test_reload(self):
241242
q2.response = None
242243
q2.rrset = None
243244

245+
if isinstance(resolver.cache, HostsCache):
246+
q1.expiration = None
247+
q2.expiration = None
248+
249+
q1.response.id = q2.response.id
250+
244251
compare_response(q1, q2)
245252

246253
self.remove_cache()
@@ -252,6 +259,15 @@ class TestLRUPickling(TestPickling):
252259
kwargs = {"filename": os.path.abspath("dns-lru.pickle")}
253260

254261

262+
class TestHosts(TestPickling):
263+
264+
cache_cls = HostsCache
265+
kwargs = {"filename": None}
266+
query_name = "localhost."
267+
load_on_get = True
268+
seed_cache = lambda self, resolver: None
269+
270+
255271
class TestStashMemory(TestPickling):
256272

257273
cache_cls = StashCache

0 commit comments

Comments
 (0)