Skip to content

Commit

Permalink
Add hosts deserializer
Browse files Browse the repository at this point in the history
  • Loading branch information
jayvdb committed Sep 7, 2020
1 parent a8ed923 commit fc9ec20
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 0 deletions.
125 changes: 125 additions & 0 deletions dns_cache/hosts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
from __future__ import absolute_import

import os.path
import sys
from datetime import timedelta

from dns.name import from_text
from dns.rdataclass import IN
from dns.rdatatype import A, AAAA

from dns.resolver import Cache

from reconfigure.configs import HostsConfig

from .dnspython import create_answer, create_simple_rrset
from .expiration import NoExpirationCacheBase
from .persistence import _DeserializeOnGetCacheBase

_year_in_seconds = timedelta(days=365).total_seconds()

# References to https://en.wikipedia.org/wiki/Hosts_(file)
_WINDOWS_PATHS = [
# Microsoft Windows NT, 2000, XP,[5] 2003, Vista, 2008, 7, 2012, 8, 10
r"${SystemRoot}\System32\drivers\etc\hosts",
# Microsoft Windows 95, 98, ME
r"${WinDir}\hosts",
# Microsoft Windows 3.1
r"${WinDir}\HOSTS",
# Symbian OS 6.1-9.0
r"C:\system\data\hosts",
# Symbian OS 9.1+
r"C:\private\10000882\hosts",
]
_UNIX_PATHS = [
# Unix, Unix-like, POSIX, Apple Macintosh Mac OS X 10.2 and newer,
# Android, iOS 2.0 and newer
"/etc/hosts",
# openSUSE
"/usr/etc/hosts",
]
_OTHER_PARTS = [
# Plan 9
"/lib/ndb/hosts",
# BeOS
"/boot/beos/etc/hosts",
# Haiku
"/boot/common/settings/network/hosts",
]


def guess_hosts_path():
if sys.platform == "win32":
possible_paths = _WINDOWS_PATHS + _UNIX_PATHS + _OTHER_PARTS
else:
possible_paths = _UNIX_PATHS + _OTHER_PARTS + _WINDOWS_PATHS

for path in possible_paths:
path = os.path.expandvars(os.path.expanduser(path))
if os.path.exists(path):
return path

raise RuntimeError()


def _convert_entries(entries, expiration=None):
out_data = []

for entry in entries:
if ":" in entry.address:
rdtype = AAAA
elif "." in entry.address:
rdtype = A
else:
continue

names = [entry.name] + [
alias.name for alias in entry.aliases
]
for name in names:
name = from_text(name)

ip = entry.address
rrset = create_simple_rrset(name, ip, rdtype, rdclass=IN)
rrset.ttl = _year_in_seconds
out_entry = create_answer(name, rrset)
if expiration:
out_entry.expiration = expiration

out_data.append(out_entry)

return out_data


def loads(filename=None):
if not filename:
filename = guess_hosts_path()

mtime = os.path.getmtime(filename)

config = HostsConfig(path=filename)
config.load()

expiration = mtime + _year_in_seconds
dnspython_data = _convert_entries(config.tree.hosts, expiration)

return dnspython_data


class HostsCacheBase(_DeserializeOnGetCacheBase, NoExpirationCacheBase):
def __init__(
self,
filename,
*args,
**kwargs
):
super(HostsCacheBase, self).__init__(
*args,
filename=filename,
deserializer=loads,
**kwargs)


class HostsCache(HostsCacheBase, Cache):
def __init__(self, *args, **kwargs):
super(HostsCache, self).__init__(*args, **kwargs)
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
install_requires=[
"dnspython",
"ProxyTypes",
"reconfigure",
],
classifiers=classifiers.splitlines(),
)
9 changes: 9 additions & 0 deletions tests/test_hosts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import unittest

from dns_cache.hosts import loads


class TestHostsSerializer(unittest.TestCase):
def test_loads(self):
data = loads()
assert data
16 changes: 16 additions & 0 deletions tests/test_persistence.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
except ImportError:
apsw = None

from dns_cache.hosts import HostsCache
from dns_cache.diskcache import DiskCache, DiskLRUCache
from dns_cache.pickle import PickableCache, PickableCacheBase, PickableLRUCache
from dns_cache.sqlitedict import SqliteDictCache, SqliteDictLRUCache
Expand Down Expand Up @@ -241,6 +242,12 @@ def test_reload(self):
q2.response = None
q2.rrset = None

if isinstance(resolver.cache, HostsCache):
q1.expiration = None
q2.expiration = None

q1.response.id = q2.response.id

compare_response(q1, q2)

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


class TestHosts(TestPickling):

cache_cls = HostsCache
kwargs = {"filename": None}
query_name = "localhost."
load_on_get = True
seed_cache = lambda self, resolver: None


class TestStashMemory(TestPickling):

cache_cls = StashCache
Expand Down

0 comments on commit fc9ec20

Please sign in to comment.