From 5f59c1f3071e2ba6aad4f163ec7884ca8e4f1cc4 Mon Sep 17 00:00:00 2001 From: Arthur Gautier Date: Tue, 29 Mar 2016 17:51:13 +0000 Subject: [PATCH] python3 support Signed-off-by: Arthur Gautier --- dns/_compat.py | 19 +++ dns/dnssec.py | 153 +++++++++++++---------- dns/e164.py | 13 +- dns/edns.py | 30 +++-- dns/entropy.py | 32 ++--- dns/exception.py | 10 ++ dns/flags.py | 30 +++-- dns/grange.py | 2 +- dns/hash.py | 53 ++------ dns/inet.py | 11 +- dns/ipv4.py | 11 +- dns/ipv6.py | 54 +++++---- dns/message.py | 187 ++++++++++++++++------------- dns/name.py | 220 ++++++++++++++++++++-------------- dns/namedict.py | 12 +- dns/node.py | 15 ++- dns/opcode.py | 28 +++-- dns/query.py | 67 +++++++---- dns/rcode.py | 33 +++-- dns/rdata.py | 158 ++++++++++++------------ dns/rdataclass.py | 43 ++++--- dns/rdataset.py | 45 ++++--- dns/rdatatype.py | 164 +++++++++++++------------ dns/rdtypes/ANY/AFSDB.py | 2 + dns/rdtypes/ANY/CAA.py | 27 +++-- dns/rdtypes/ANY/CDNSKEY.py | 7 +- dns/rdtypes/ANY/CDS.py | 2 + dns/rdtypes/ANY/CERT.py | 55 +++++---- dns/rdtypes/ANY/CNAME.py | 2 + dns/rdtypes/ANY/DLV.py | 2 + dns/rdtypes/ANY/DNAME.py | 5 +- dns/rdtypes/ANY/DNSKEY.py | 7 +- dns/rdtypes/ANY/DS.py | 2 + dns/rdtypes/ANY/EUI48.py | 1 + dns/rdtypes/ANY/EUI64.py | 1 + dns/rdtypes/ANY/GPOS.py | 62 ++++++---- dns/rdtypes/ANY/HINFO.py | 40 ++++--- dns/rdtypes/ANY/HIP.py | 44 +++---- dns/rdtypes/ANY/ISDN.py | 40 ++++--- dns/rdtypes/ANY/LOC.py | 76 +++++++----- dns/rdtypes/ANY/MX.py | 2 + dns/rdtypes/ANY/NS.py | 2 + dns/rdtypes/ANY/NSEC.py | 45 +++---- dns/rdtypes/ANY/NSEC3.py | 97 ++++++++------- dns/rdtypes/ANY/NSEC3PARAM.py | 37 +++--- dns/rdtypes/ANY/PTR.py | 2 + dns/rdtypes/ANY/RP.py | 20 ++-- dns/rdtypes/ANY/RRSIG.py | 35 +++--- dns/rdtypes/ANY/RT.py | 2 + dns/rdtypes/ANY/SOA.py | 26 ++-- dns/rdtypes/ANY/SPF.py | 2 + dns/rdtypes/ANY/SSHFP.py | 24 ++-- dns/rdtypes/ANY/TLSA.py | 26 ++-- dns/rdtypes/ANY/TXT.py | 2 + dns/rdtypes/ANY/URI.py | 26 ++-- dns/rdtypes/ANY/X25.py | 28 +++-- dns/rdtypes/IN/A.py | 17 +-- dns/rdtypes/IN/AAAA.py | 17 +-- dns/rdtypes/IN/APL.py | 26 ++-- dns/rdtypes/IN/DHCID.py | 23 ++-- dns/rdtypes/IN/IPSECKEY.py | 38 +++--- dns/rdtypes/IN/KX.py | 2 + dns/rdtypes/IN/NAPTR.py | 43 ++++--- dns/rdtypes/IN/NSAP.py | 23 ++-- dns/rdtypes/IN/NSAP_PTR.py | 2 + dns/rdtypes/IN/PX.py | 24 ++-- dns/rdtypes/IN/SRV.py | 20 ++-- dns/rdtypes/IN/WKS.py | 33 ++--- dns/rdtypes/dnskeybase.py | 32 ++--- dns/rdtypes/dsbase.py | 24 ++-- dns/rdtypes/euibase.py | 8 +- dns/rdtypes/mxbase.py | 36 +++--- dns/rdtypes/nsbase.py | 30 ++--- dns/rdtypes/txtbase.py | 31 +++-- dns/renderer.py | 19 +-- dns/resolver.py | 212 +++++++++++++++++++------------- dns/reversename.py | 21 +++- dns/rrset.py | 18 ++- dns/set.py | 14 ++- dns/tokenizer.py | 61 ++++++---- dns/tsig.py | 63 +++++----- dns/tsigkeyring.py | 6 +- dns/ttl.py | 18 +-- dns/update.py | 78 ++++++------ dns/version.py | 2 +- dns/wiredata.py | 27 +++-- dns/zone.py | 157 +++++++++++++----------- setup.py | 5 +- tests/test_bugs.py | 16 +-- tests/test_dnssec.py | 2 +- tests/test_generate.py | 150 ++++++++++++----------- tests/test_grange.py | 1 - tests/test_message.py | 23 ++-- tests/test_name.py | 156 ++++++++++++------------ tests/test_ntoaaton.py | 86 ++++++------- tests/test_rdtypeanyeui.py | 10 +- tests/test_resolver.py | 7 +- tests/test_tokenizer.py | 4 + tests/test_update.py | 3 +- tests/test_zone.py | 63 ++++++---- 100 files changed, 2144 insertions(+), 1648 deletions(-) create mode 100644 dns/_compat.py diff --git a/dns/_compat.py b/dns/_compat.py new file mode 100644 index 00000000..88416ee0 --- /dev/null +++ b/dns/_compat.py @@ -0,0 +1,19 @@ +import sys + + +if sys.version_info > (3,): + long = int + xrange = range +else: + long = long + xrange = xrange + +# unicode / binary types +if sys.version_info > (3,): + text_type = str + binary_type = bytes + string_types = (str,) +else: + text_type = unicode + binary_type = str + string_types = (basestring,) diff --git a/dns/dnssec.py b/dns/dnssec.py index a4546450..fec12082 100644 --- a/dns/dnssec.py +++ b/dns/dnssec.py @@ -15,7 +15,7 @@ """Common DNSSEC-related functions and constants.""" -import cStringIO +from io import BytesIO import struct import time @@ -27,11 +27,16 @@ import dns.rdataset import dns.rdata import dns.rdatatype import dns.rdataclass +from ._compat import string_types + class UnsupportedAlgorithm(dns.exception.DNSException): + """The DNSSEC algorithm is not supported.""" + class ValidationFailure(dns.exception.DNSException): + """The DNSSEC signature is invalid.""" RSAMD5 = 1 @@ -50,27 +55,28 @@ PRIVATEDNS = 253 PRIVATEOID = 254 _algorithm_by_text = { - 'RSAMD5' : RSAMD5, - 'DH' : DH, - 'DSA' : DSA, - 'ECC' : ECC, - 'RSASHA1' : RSASHA1, - 'DSANSEC3SHA1' : DSANSEC3SHA1, - 'RSASHA1NSEC3SHA1' : RSASHA1NSEC3SHA1, - 'RSASHA256' : RSASHA256, - 'RSASHA512' : RSASHA512, - 'INDIRECT' : INDIRECT, - 'ECDSAP256SHA256' : ECDSAP256SHA256, - 'ECDSAP384SHA384' : ECDSAP384SHA384, - 'PRIVATEDNS' : PRIVATEDNS, - 'PRIVATEOID' : PRIVATEOID, - } + 'RSAMD5': RSAMD5, + 'DH': DH, + 'DSA': DSA, + 'ECC': ECC, + 'RSASHA1': RSASHA1, + 'DSANSEC3SHA1': DSANSEC3SHA1, + 'RSASHA1NSEC3SHA1': RSASHA1NSEC3SHA1, + 'RSASHA256': RSASHA256, + 'RSASHA512': RSASHA512, + 'INDIRECT': INDIRECT, + 'ECDSAP256SHA256': ECDSAP256SHA256, + 'ECDSAP384SHA384': ECDSAP384SHA384, + 'PRIVATEDNS': PRIVATEDNS, + 'PRIVATEOID': PRIVATEOID, +} # We construct the inverse mapping programmatically to ensure that we # cannot make any mistakes (e.g. omissions, cut-and-paste errors) that # would cause the mapping not to be true inverse. -_algorithm_by_value = dict([(y, x) for x, y in _algorithm_by_text.iteritems()]) +_algorithm_by_value = dict((y, x) for x, y in _algorithm_by_text.items()) + def algorithm_from_text(text): """Convert text into a DNSSEC algorithm value @@ -81,6 +87,7 @@ def algorithm_from_text(text): value = int(text) return value + def algorithm_to_text(value): """Convert a DNSSEC algorithm value to text @rtype: string""" @@ -90,35 +97,40 @@ def algorithm_to_text(value): text = str(value) return text + def _to_rdata(record, origin): - s = cStringIO.StringIO() + s = BytesIO() record.to_wire(s, origin=origin) return s.getvalue() + def key_id(key, origin=None): rdata = _to_rdata(key, origin) + rdata = bytearray(rdata) if key.algorithm == RSAMD5: - return (ord(rdata[-3]) << 8) + ord(rdata[-2]) + return (rdata[-3] << 8) + rdata[-2] else: total = 0 for i in range(len(rdata) // 2): - total += (ord(rdata[2 * i]) << 8) + ord(rdata[2 * i + 1]) + total += (rdata[2 * i] << 8) + \ + rdata[2 * i + 1] if len(rdata) % 2 != 0: - total += ord(rdata[len(rdata) - 1]) << 8 - total += ((total >> 16) & 0xffff); + total += rdata[len(rdata) - 1] << 8 + total += ((total >> 16) & 0xffff) return total & 0xffff + def make_ds(name, key, algorithm, origin=None): if algorithm.upper() == 'SHA1': dsalg = 1 - hash = dns.hash.get('SHA1')() + hash = dns.hash.hashes['SHA1']() elif algorithm.upper() == 'SHA256': dsalg = 2 - hash = dns.hash.get('SHA256')() + hash = dns.hash.hashes['SHA256']() else: - raise UnsupportedAlgorithm, 'unsupported algorithm "%s"' % algorithm + raise UnsupportedAlgorithm('unsupported algorithm "%s"' % algorithm) - if isinstance(name, (str, unicode)): + if isinstance(name, string_types): name = dns.name.from_text(name, origin) hash.update(name.canonicalize().to_wire()) hash.update(_to_rdata(key, origin)) @@ -128,8 +140,9 @@ def make_ds(name, key, algorithm, origin=None): return dns.rdata.from_wire(dns.rdataclass.IN, dns.rdatatype.DS, dsrdata, 0, len(dsrdata)) + def _find_candidate_keys(keys, rrsig): - candidate_keys=[] + candidate_keys = [] value = keys.get(rrsig.signer) if value is None: return None @@ -143,49 +156,59 @@ def _find_candidate_keys(keys, rrsig): rdataset = value for rdata in rdataset: if rdata.algorithm == rrsig.algorithm and \ - key_id(rdata) == rrsig.key_tag: + key_id(rdata) == rrsig.key_tag: candidate_keys.append(rdata) return candidate_keys + def _is_rsa(algorithm): return algorithm in (RSAMD5, RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512) + def _is_dsa(algorithm): return algorithm in (DSA, DSANSEC3SHA1) + def _is_ecdsa(algorithm): return _have_ecdsa and (algorithm in (ECDSAP256SHA256, ECDSAP384SHA384)) + def _is_md5(algorithm): return algorithm == RSAMD5 + def _is_sha1(algorithm): return algorithm in (DSA, RSASHA1, DSANSEC3SHA1, RSASHA1NSEC3SHA1) + def _is_sha256(algorithm): return algorithm in (RSASHA256, ECDSAP256SHA256) + def _is_sha384(algorithm): return algorithm == ECDSAP384SHA384 + def _is_sha512(algorithm): return algorithm == RSASHA512 + def _make_hash(algorithm): if _is_md5(algorithm): - return dns.hash.get('MD5')() + return dns.hash.hashes['MD5']() if _is_sha1(algorithm): - return dns.hash.get('SHA1')() + return dns.hash.hashes['SHA1']() if _is_sha256(algorithm): - return dns.hash.get('SHA256')() + return dns.hash.hashes['SHA256']() if _is_sha384(algorithm): - return dns.hash.get('SHA384')() + return dns.hash.hashes['SHA384']() if _is_sha512(algorithm): - return dns.hash.get('SHA512')() - raise ValidationFailure, 'unknown hash for algorithm %u' % algorithm + return dns.hash.hashes['SHA512']() + raise ValidationFailure('unknown hash for algorithm %u' % algorithm) + def _make_algorithm_id(algorithm): if _is_md5(algorithm): @@ -197,13 +220,14 @@ def _make_algorithm_id(algorithm): elif _is_sha512(algorithm): oid = [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03] else: - raise ValidationFailure, 'unknown algorithm %u' % algorithm + raise ValidationFailure('unknown algorithm %u' % algorithm) olen = len(oid) dlen = _make_hash(algorithm).digest_size idbytes = [0x30] + [8 + olen + dlen] + \ [0x30, olen + 4] + [0x06, olen] + oid + \ [0x05, 0x00] + [0x04, dlen] - return ''.join(map(chr, idbytes)) + return struct.pack('!%dB' % len(idbytes), *idbytes) + def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None): """Validate an RRset against a single signature rdata @@ -217,7 +241,8 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None): @param rrsig: The signature rdata @type rrsig: dns.rrset.Rdata @param keys: The key dictionary. - @type keys: a dictionary keyed by dns.name.Name with node or rdataset values + @type keys: a dictionary keyed by dns.name.Name with node or rdataset + values @param origin: The origin to use for relative names @type origin: dns.name.Name or None @param now: The time to use when validating the signatures. The default @@ -225,15 +250,15 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None): @type now: int """ - if isinstance(origin, (str, unicode)): + if isinstance(origin, string_types): origin = dns.name.from_text(origin, dns.name.root) for candidate_key in _find_candidate_keys(keys, rrsig): if not candidate_key: - raise ValidationFailure, 'unknown key' + raise ValidationFailure('unknown key') - # For convenience, allow the rrset to be specified as a (name, rdataset) - # tuple as well as a proper rrset + # For convenience, allow the rrset to be specified as a (name, + # rdataset) tuple as well as a proper rrset if isinstance(rrset, tuple): rrname = rrset[0] rdataset = rrset[1] @@ -244,21 +269,21 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None): if now is None: now = time.time() if rrsig.expiration < now: - raise ValidationFailure, 'expired' + raise ValidationFailure('expired') if rrsig.inception > now: - raise ValidationFailure, 'not yet valid' + raise ValidationFailure('not yet valid') hash = _make_hash(rrsig.algorithm) if _is_rsa(rrsig.algorithm): keyptr = candidate_key.key - (bytes,) = struct.unpack('!B', keyptr[0:1]) + (bytes_,) = struct.unpack('!B', keyptr[0:1]) keyptr = keyptr[1:] - if bytes == 0: - (bytes,) = struct.unpack('!H', keyptr[0:2]) + if bytes_ == 0: + (bytes_,) = struct.unpack('!H', keyptr[0:2]) keyptr = keyptr[2:] - rsa_e = keyptr[0:bytes] - rsa_n = keyptr[bytes:] + rsa_e = keyptr[0:bytes_] + rsa_n = keyptr[bytes_:] keylen = len(rsa_n) * 8 pubkey = Crypto.PublicKey.RSA.construct( (Crypto.Util.number.bytes_to_long(rsa_n), @@ -288,14 +313,12 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None): if rrsig.algorithm == ECDSAP256SHA256: curve = ecdsa.curves.NIST256p key_len = 32 - digest_len = 32 elif rrsig.algorithm == ECDSAP384SHA384: curve = ecdsa.curves.NIST384p key_len = 48 - digest_len = 48 else: # shouldn't happen - raise ValidationFailure, 'unknown ECDSA curve' + raise ValidationFailure('unknown ECDSA curve') keyptr = candidate_key.key x = Crypto.Util.number.bytes_to_long(keyptr[0:key_len]) y = Crypto.Util.number.bytes_to_long(keyptr[key_len:key_len * 2]) @@ -309,7 +332,7 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None): sig = ecdsa.ecdsa.Signature(Crypto.Util.number.bytes_to_long(r), Crypto.Util.number.bytes_to_long(s)) else: - raise ValidationFailure, 'unknown algorithm %u' % rrsig.algorithm + raise ValidationFailure('unknown algorithm %u' % rrsig.algorithm) hash.update(_to_rdata(rrsig, origin)[:18]) hash.update(rrsig.signer.to_digestable(origin)) @@ -320,7 +343,7 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None): rrnamebuf = rrname.to_digestable(origin) rrfixed = struct.pack('!HHI', rdataset.rdtype, rdataset.rdclass, rrsig.original_ttl) - rrlist = sorted(rdataset); + rrlist = sorted(rdataset) for rr in rrlist: hash.update(rrnamebuf) hash.update(rrfixed) @@ -335,18 +358,20 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None): # PKCS1 algorithm identifier goop digest = _make_algorithm_id(rrsig.algorithm) + digest padlen = keylen // 8 - len(digest) - 3 - digest = chr(0) + chr(1) + chr(0xFF) * padlen + chr(0) + digest + digest = struct.pack('!%dB' % (2 + padlen + 1), + *([0, 1] + [0xFF] * padlen + [0])) + digest elif _is_dsa(rrsig.algorithm) or _is_ecdsa(rrsig.algorithm): pass else: # Raise here for code clarity; this won't actually ever happen # since if the algorithm is really unknown we'd already have # raised an exception above - raise ValidationFailure, 'unknown algorithm %u' % rrsig.algorithm + raise ValidationFailure('unknown algorithm %u' % rrsig.algorithm) if pubkey.verify(digest, sig): return - raise ValidationFailure, 'verify failure' + raise ValidationFailure('verify failure') + def _validate(rrset, rrsigset, keys, origin=None, now=None): """Validate an RRset @@ -358,7 +383,8 @@ def _validate(rrset, rrsigset, keys, origin=None, now=None): @type rrsigset: dns.rrset.RRset or (dns.name.Name, dns.rdataset.Rdataset) tuple @param keys: The key dictionary. - @type keys: a dictionary keyed by dns.name.Name with node or rdataset values + @type keys: a dictionary keyed by dns.name.Name with node or rdataset + values @param origin: The origin to use for relative names @type origin: dns.name.Name or None @param now: The time to use when validating the signatures. The default @@ -366,7 +392,7 @@ def _validate(rrset, rrsigset, keys, origin=None, now=None): @type now: int """ - if isinstance(origin, (str, unicode)): + if isinstance(origin, string_types): origin = dns.name.from_text(origin, dns.name.root) if isinstance(rrset, tuple): @@ -384,18 +410,19 @@ def _validate(rrset, rrsigset, keys, origin=None, now=None): rrname = rrname.choose_relativity(origin) rrsigname = rrname.choose_relativity(origin) if rrname != rrsigname: - raise ValidationFailure, "owner names do not match" + raise ValidationFailure("owner names do not match") for rrsig in rrsigrdataset: try: _validate_rrsig(rrset, rrsig, keys, origin, now) return - except ValidationFailure, e: + except ValidationFailure: pass - raise ValidationFailure, "no RRSIGs validated" + raise ValidationFailure("no RRSIGs validated") + def _need_pycrypto(*args, **kwargs): - raise NotImplementedError, "DNSSEC validation requires pycrypto" + raise NotImplementedError("DNSSEC validation requires pycrypto") try: import Crypto.PublicKey.RSA @@ -417,9 +444,11 @@ try: _have_ecdsa = True class ECKeyWrapper(object): + def __init__(self, key, key_len): self.key = key self.key_len = key_len + def verify(self, digest, sig): diglong = Crypto.Util.number.bytes_to_long(digest) return self.key.pubkey.verifies(diglong, sig) diff --git a/dns/e164.py b/dns/e164.py index 68f0a316..2cc911cd 100644 --- a/dns/e164.py +++ b/dns/e164.py @@ -19,12 +19,15 @@ @type public_enum_domain: dns.name.Name object """ + import dns.exception import dns.name import dns.resolver +from ._compat import string_types public_enum_domain = dns.name.from_text('e164.arpa.') + def from_e164(text, origin=public_enum_domain): """Convert an E.164 number in textual form into a Name object whose value is the ENUM domain name for that number. @@ -39,6 +42,7 @@ def from_e164(text, origin=public_enum_domain): parts.reverse() return dns.name.from_text('.'.join(parts), origin=origin) + def to_e164(name, origin=public_enum_domain, want_plus_prefix=True): """Convert an ENUM domain name into an E.164 number. @param name: the ENUM domain name. @@ -50,17 +54,18 @@ def to_e164(name, origin=public_enum_domain, want_plus_prefix=True): returned number. @rtype: str """ - if not origin is None: + if origin is not None: name = name.relativize(origin) dlabels = [d for d in name.labels if (d.isdigit() and len(d) == 1)] if len(dlabels) != len(name.labels): raise dns.exception.SyntaxError('non-digit labels in ENUM domain name') dlabels.reverse() - text = ''.join(dlabels) + text = b''.join(dlabels) if want_plus_prefix: - text = '+' + text + text = b'+' + text return text + def query(number, domains, resolver=None): """Look for NAPTR RRs for the specified number in the specified domains. @@ -69,7 +74,7 @@ def query(number, domains, resolver=None): if resolver is None: resolver = dns.resolver.get_default_resolver() for domain in domains: - if isinstance(domain, (str, unicode)): + if isinstance(domain, string_types): domain = dns.name.from_text(domain) qname = dns.e164.from_e164(number, domain) try: diff --git a/dns/edns.py b/dns/edns.py index cf062d06..c6102fd7 100644 --- a/dns/edns.py +++ b/dns/edns.py @@ -17,7 +17,9 @@ NSID = 3 + class Option(object): + """Base class for all EDNS option types. """ @@ -33,6 +35,7 @@ class Option(object): """ raise NotImplementedError + @classmethod def from_wire(cls, otype, wire, current, olen): """Build an EDNS option object from wire format @@ -47,11 +50,10 @@ class Option(object): @rtype: dns.edns.Option instance""" raise NotImplementedError - from_wire = classmethod(from_wire) - def _cmp(self, other): """Compare an EDNS option with another option of the same type. - Return < 0 if self < other, 0 if self == other, and > 0 if self > other. + Return < 0 if self < other, 0 if self == other, + and > 0 if self > other. """ raise NotImplementedError @@ -71,30 +73,31 @@ class Option(object): def __lt__(self, other): if not isinstance(other, Option) or \ - self.otype != other.otype: + self.otype != other.otype: return NotImplemented return self._cmp(other) < 0 def __le__(self, other): if not isinstance(other, Option) or \ - self.otype != other.otype: + self.otype != other.otype: return NotImplemented return self._cmp(other) <= 0 def __ge__(self, other): if not isinstance(other, Option) or \ - self.otype != other.otype: + self.otype != other.otype: return NotImplemented return self._cmp(other) >= 0 def __gt__(self, other): if not isinstance(other, Option) or \ - self.otype != other.otype: + self.otype != other.otype: return NotImplemented return self._cmp(other) > 0 class GenericOption(Option): + """Generate Rdata Class This class is used for EDNS option types for which we have no better @@ -108,23 +111,28 @@ class GenericOption(Option): def to_wire(self, file): file.write(self.data) + @classmethod def from_wire(cls, otype, wire, current, olen): - return cls(otype, wire[current : current + olen]) - - from_wire = classmethod(from_wire) + return cls(otype, wire[current: current + olen]) def _cmp(self, other): - return cmp(self.data, other.data) + if self.data == other.data: + return 0 + if self.data > other.data: + return 1 + return -1 _type_to_class = { } + def get_option_class(otype): cls = _type_to_class.get(otype) if cls is None: cls = GenericOption return cls + def option_from_wire(otype, wire, current, olen): """Build an EDNS option object from wire format diff --git a/dns/entropy.py b/dns/entropy.py index 1ffbc7b9..43841a7a 100644 --- a/dns/entropy.py +++ b/dns/entropy.py @@ -15,12 +15,15 @@ import os import time +from ._compat import long, binary_type try: import threading as _threading except ImportError: import dummy_threading as _threading + class EntropyPool(object): + def __init__(self, seed=None): self.pool_index = 0 self.digest = None @@ -39,9 +42,9 @@ class EntropyPool(object): import md5 self.hash = md5.new() self.hash_len = 16 - self.pool = '\0' * self.hash_len - if not seed is None: - self.stir(seed) + self.pool = bytearray(b'\0' * self.hash_len) + if seed is not None: + self.stir(bytearray(seed)) self.seeded = True else: self.seeded = False @@ -50,14 +53,12 @@ class EntropyPool(object): if not already_locked: self.lock.acquire() try: - bytes = [ord(c) for c in self.pool] for c in entropy: if self.pool_index == self.hash_len: self.pool_index = 0 - b = ord(c) & 0xff - bytes[self.pool_index] ^= b + b = c & 0xff + self.pool[self.pool_index] ^= b self.pool_index += 1 - self.pool = ''.join([chr(c) for c in bytes]) finally: if not already_locked: self.lock.release() @@ -68,7 +69,7 @@ class EntropyPool(object): seed = os.urandom(16) except: try: - r = file('/dev/urandom', 'r', 0) + r = open('/dev/urandom', 'rb', 0) try: seed = r.read(16) finally: @@ -76,18 +77,19 @@ class EntropyPool(object): except: seed = str(time.time()) self.seeded = True + seed = bytearray(seed) self.stir(seed, True) def random_8(self): self.lock.acquire() - self._maybe_seed() try: + self._maybe_seed() if self.digest is None or self.next_byte == self.hash_len: - self.hash.update(self.pool) - self.digest = self.hash.digest() + self.hash.update(binary_type(self.pool)) + self.digest = bytearray(self.hash.digest()) self.stir(self.digest, True) self.next_byte = 0 - value = ord(self.digest[self.next_byte]) + value = self.digest[self.next_byte] self.next_byte += 1 finally: self.lock.release() @@ -101,11 +103,11 @@ class EntropyPool(object): def random_between(self, first, last): size = last - first + 1 - if size > 4294967296L: + if size > long(4294967296): raise ValueError('too big') if size > 65536: rand = self.random_32 - max = 4294967295L + max = long(4294967295) elif size > 256: rand = self.random_16 max = 65535 @@ -116,8 +118,10 @@ class EntropyPool(object): pool = EntropyPool() + def random_16(): return pool.random_16() + def between(first, last): return pool.random_between(first, last) diff --git a/dns/exception.py b/dns/exception.py index cbcdb57c..81c8daab 100644 --- a/dns/exception.py +++ b/dns/exception.py @@ -17,6 +17,7 @@ class DNSException(Exception): + """Abstract base class shared by all dnspython exceptions. It supports two basic modes of operation: @@ -97,18 +98,27 @@ class DNSException(Exception): class FormError(DNSException): + """DNS message is malformed.""" + class SyntaxError(DNSException): + """Text input is malformed.""" + class UnexpectedEnd(SyntaxError): + """Text input ended unexpectedly.""" + class TooBig(DNSException): + """The DNS message is too big.""" + class Timeout(DNSException): + """The DNS operation timed out.""" supp_kwargs = set(['timeout']) fmt = "%s after {timeout} seconds" % __doc__[:-1] diff --git a/dns/flags.py b/dns/flags.py index 35a8305e..388d6aaa 100644 --- a/dns/flags.py +++ b/dns/flags.py @@ -30,17 +30,17 @@ CD = 0x0010 DO = 0x8000 _by_text = { - 'QR' : QR, - 'AA' : AA, - 'TC' : TC, - 'RD' : RD, - 'RA' : RA, - 'AD' : AD, - 'CD' : CD + 'QR': QR, + 'AA': AA, + 'TC': TC, + 'RD': RD, + 'RA': RA, + 'AD': AD, + 'CD': CD } _edns_by_text = { - 'DO' : DO + 'DO': DO } @@ -48,12 +48,13 @@ _edns_by_text = { # cannot make any mistakes (e.g. omissions, cut-and-paste errors) that # would cause the mappings not to be true inverses. -_by_value = dict([(y, x) for x, y in _by_text.iteritems()]) +_by_value = dict((y, x) for x, y in _by_text.items()) + +_edns_by_value = dict((y, x) for x, y in _edns_by_text.items()) -_edns_by_value = dict([(y, x) for x, y in _edns_by_text.iteritems()]) def _order_flags(table): - order = list(table.iteritems()) + order = list(table.items()) order.sort() order.reverse() return order @@ -62,6 +63,7 @@ _flags_order = _order_flags(_by_value) _edns_flags_order = _order_flags(_edns_by_value) + def _from_text(text, table): flags = 0 tokens = text.split() @@ -69,6 +71,7 @@ def _from_text(text, table): flags = flags | table[t.upper()] return flags + def _to_text(flags, table, order): text_flags = [] for k, v in order: @@ -76,6 +79,7 @@ def _to_text(flags, table, order): text_flags.append(v) return ' '.join(text_flags) + def from_text(text): """Convert a space-separated list of flag text values into a flags value. @@ -83,13 +87,14 @@ def from_text(text): return _from_text(text, _by_text) + def to_text(flags): """Convert a flags value into a space-separated list of flag text values. @rtype: string""" return _to_text(flags, _by_value, _flags_order) - + def edns_from_text(text): """Convert a space-separated list of EDNS flag text values into a EDNS @@ -98,6 +103,7 @@ def edns_from_text(text): return _from_text(text, _edns_by_text) + def edns_to_text(flags): """Convert an EDNS flags value into a space-separated list of EDNS flag text values. diff --git a/dns/grange.py b/dns/grange.py index c0607d60..01a3257b 100644 --- a/dns/grange.py +++ b/dns/grange.py @@ -17,6 +17,7 @@ import dns + def from_text(text): """Convert the text form of a range in a GENERATE statement to an integer. @@ -28,7 +29,6 @@ def from_text(text): """ # TODO, figure out the bounds on start, stop and step. - import pdb step = 1 cur = '' state = 0 diff --git a/dns/hash.py b/dns/hash.py index 0c708036..27f7a7e2 100644 --- a/dns/hash.py +++ b/dns/hash.py @@ -16,52 +16,17 @@ """Hashing backwards compatibility wrapper""" import sys +import hashlib -_hashes = None -def _need_later_python(alg): - def func(*args, **kwargs): - raise NotImplementedError("TSIG algorithm " + alg + - " requires Python 2.5.2 or later") - return func +hashes = {} +hashes['MD5'] = hashlib.md5 +hashes['SHA1'] = hashlib.sha1 +hashes['SHA224'] = hashlib.sha224 +hashes['SHA256'] = hashlib.sha256 +hashes['SHA384'] = hashlib.sha384 +hashes['SHA512'] = hashlib.sha512 -def _setup(): - global _hashes - _hashes = {} - try: - import hashlib - _hashes['MD5'] = hashlib.md5 - _hashes['SHA1'] = hashlib.sha1 - _hashes['SHA224'] = hashlib.sha224 - _hashes['SHA256'] = hashlib.sha256 - if sys.hexversion >= 0x02050200: - _hashes['SHA384'] = hashlib.sha384 - _hashes['SHA512'] = hashlib.sha512 - else: - _hashes['SHA384'] = _need_later_python('SHA384') - _hashes['SHA512'] = _need_later_python('SHA512') - - if sys.hexversion < 0x02050000: - # hashlib doesn't conform to PEP 247: API for - # Cryptographic Hash Functions, which hmac before python - # 2.5 requires, so add the necessary items. - class HashlibWrapper: - def __init__(self, basehash): - self.basehash = basehash - self.digest_size = self.basehash().digest_size - - def new(self, *args, **kwargs): - return self.basehash(*args, **kwargs) - - for name in _hashes: - _hashes[name] = HashlibWrapper(_hashes[name]) - - except ImportError: - import md5, sha - _hashes['MD5'] = md5 - _hashes['SHA1'] = sha def get(algorithm): - if _hashes is None: - _setup() - return _hashes[algorithm.upper()] + return hashes[algorithm.upper()] diff --git a/dns/inet.py b/dns/inet.py index 3b7e88f7..966285e7 100644 --- a/dns/inet.py +++ b/dns/inet.py @@ -34,6 +34,7 @@ try: except AttributeError: AF_INET6 = 9999 + def inet_pton(family, text): """Convert the textual form of a network address into its binary form. @@ -45,7 +46,7 @@ def inet_pton(family, text): implemented. @rtype: string """ - + if family == AF_INET: return dns.ipv4.inet_aton(text) elif family == AF_INET6: @@ -53,6 +54,7 @@ def inet_pton(family, text): else: raise NotImplementedError + def inet_ntop(family, address): """Convert the binary form of a network address into its textual form. @@ -71,6 +73,7 @@ def inet_ntop(family, address): else: raise NotImplementedError + def af_for_address(text): """Determine the address family of a textual-form network address. @@ -80,15 +83,16 @@ def af_for_address(text): @rtype: int """ try: - junk = dns.ipv4.inet_aton(text) + dns.ipv4.inet_aton(text) return AF_INET except: try: - junk = dns.ipv6.inet_aton(text) + dns.ipv6.inet_aton(text) return AF_INET6 except: raise ValueError + def is_multicast(text): """Is the textual-form network address a multicast address? @@ -105,4 +109,3 @@ def is_multicast(text): return (first == 255) except: raise ValueError - diff --git a/dns/ipv4.py b/dns/ipv4.py index 85ab48f7..3fef282b 100644 --- a/dns/ipv4.py +++ b/dns/ipv4.py @@ -18,6 +18,7 @@ import struct import dns.exception +from ._compat import binary_type def inet_ntoa(address): """Convert an IPv4 address in network form to text form. @@ -28,8 +29,10 @@ def inet_ntoa(address): """ if len(address) != 4: raise dns.exception.SyntaxError - return '%u.%u.%u.%u' % (ord(address[0]), ord(address[1]), - ord(address[2]), ord(address[3])) + if not isinstance(address, bytearray): + address = bytearray(address) + return (u'%u.%u.%u.%u' % (address[0], address[1], + address[2], address[3])).encode() def inet_aton(text): """Convert an IPv4 address in text form to network form. @@ -38,7 +41,9 @@ def inet_aton(text): @type text: string @returns: string """ - parts = text.split('.') + if not isinstance(text, binary_type): + text = text.encode() + parts = text.split(b'.') if len(parts) != 4: raise dns.exception.SyntaxError for part in parts: diff --git a/dns/ipv6.py b/dns/ipv6.py index bf658af1..ee991e85 100644 --- a/dns/ipv6.py +++ b/dns/ipv6.py @@ -16,11 +16,13 @@ """IPv6 helper functions.""" import re +import binascii import dns.exception import dns.ipv4 +from ._compat import xrange, binary_type -_leading_zero = re.compile(r'0+([0-9a-f]+)') +_leading_zero = re.compile(b'0+([0-9a-f]+)') def inet_ntoa(address): """Convert a network format IPv6 address into text. @@ -33,7 +35,7 @@ def inet_ntoa(address): if len(address) != 16: raise ValueError("IPv6 addresses are 16 bytes long") - hex = address.encode('hex_codec') + hex = binascii.hexlify(address) chunks = [] i = 0 l = len(hex) @@ -55,7 +57,7 @@ def inet_ntoa(address): start = -1 last_was_zero = False for i in xrange(8): - if chunks[i] != '0': + if chunks[i] != b'0': if last_was_zero: end = i current_len = end - start @@ -75,23 +77,23 @@ def inet_ntoa(address): if best_len > 1: if best_start == 0 and \ (best_len == 6 or - best_len == 5 and chunks[5] == 'ffff'): + best_len == 5 and chunks[5] == b'ffff'): # We have an embedded IPv4 address if best_len == 6: - prefix = '::' + prefix = b'::' else: - prefix = '::ffff:' + prefix = b'::ffff:' hex = prefix + dns.ipv4.inet_ntoa(address[12:]) else: - hex = ':'.join(chunks[:best_start]) + '::' + \ - ':'.join(chunks[best_start + best_len:]) + hex = b':'.join(chunks[:best_start]) + b'::' + \ + b':'.join(chunks[best_start + best_len:]) else: - hex = ':'.join(chunks) + hex = b':'.join(chunks) return hex -_v4_ending = re.compile(r'(.*):(\d+\.\d+\.\d+\.\d+)$') -_colon_colon_start = re.compile(r'::.*') -_colon_colon_end = re.compile(r'.*::$') +_v4_ending = re.compile(b'(.*):(\d+\.\d+\.\d+\.\d+)$') +_colon_colon_start = re.compile(b'::.*') +_colon_colon_end = re.compile(b'.*::$') def inet_aton(text): """Convert a text format IPv6 address into network format. @@ -105,17 +107,19 @@ def inet_aton(text): # # Our aim here is not something fast; we just want something that works. # + if not isinstance(text, binary_type): + text = text.encode() - if text == '::': - text = '0::' + if text == b'::': + text = b'0::' # # Get rid of the icky dot-quad syntax if we have it. # m = _v4_ending.match(text) if not m is None: - b = dns.ipv4.inet_aton(m.group(2)) - text = "%s:%02x%02x:%02x%02x" % (m.group(1), ord(b[0]), ord(b[1]), - ord(b[2]), ord(b[3])) + b = bytearray(dns.ipv4.inet_aton(m.group(2))) + text = (u"%s:%02x%02x:%02x%02x" % (m.group(1).decode(), b[0], b[1], + b[2], b[3])).encode() # # Try to turn '::' into ':'; if no match try to # turn '::' into ':' @@ -130,39 +134,39 @@ def inet_aton(text): # # Now canonicalize into 8 chunks of 4 hex digits each # - chunks = text.split(':') + chunks = text.split(b':') l = len(chunks) if l > 8: raise dns.exception.SyntaxError seen_empty = False canonical = [] for c in chunks: - if c == '': + if c == b'': if seen_empty: raise dns.exception.SyntaxError seen_empty = True for i in xrange(0, 8 - l + 1): - canonical.append('0000') + canonical.append(b'0000') else: lc = len(c) if lc > 4: raise dns.exception.SyntaxError if lc != 4: - c = ('0' * (4 - lc)) + c + c = (b'0' * (4 - lc)) + c canonical.append(c) if l < 8 and not seen_empty: raise dns.exception.SyntaxError - text = ''.join(canonical) + text = b''.join(canonical) # # Finally we can go to binary. # try: - return text.decode('hex_codec') - except TypeError: + return binascii.unhexlify(text) + except (binascii.Error, TypeError): raise dns.exception.SyntaxError -_mapped_prefix = '\x00' * 10 + '\xff\xff' +_mapped_prefix = b'\x00' * 10 + b'\xff\xff' def is_mapped(address): return address.startswith(_mapped_prefix) diff --git a/dns/message.py b/dns/message.py index 820376d7..5f82b45a 100644 --- a/dns/message.py +++ b/dns/message.py @@ -15,8 +15,9 @@ """DNS Messages""" -import cStringIO -import random +from __future__ import absolute_import + +from io import StringIO import struct import sys import time @@ -36,28 +37,44 @@ import dns.renderer import dns.tsig import dns.wiredata +from ._compat import long, xrange, string_types + + class ShortHeader(dns.exception.FormError): + """The DNS packet passed to from_wire() is too short.""" + class TrailingJunk(dns.exception.FormError): + """The DNS packet passed to from_wire() has extra junk at the end of it.""" + class UnknownHeaderField(dns.exception.DNSException): + """The header field name was not recognized when converting from text into a message.""" + class BadEDNS(dns.exception.FormError): + """OPT record occured somewhere other than the start of the additional data section.""" + class BadTSIG(dns.exception.FormError): + """A TSIG record occured somewhere other than the end of the additional data section.""" + class UnknownTSIGKey(dns.exception.DNSException): + """A TSIG with an unknown key was received.""" + class Message(object): + """A DNS message. @ivar id: The query id; the default is a randomly chosen id. @@ -166,7 +183,7 @@ class Message(object): self.index = {} def __repr__(self): - return '' + return '' def __str__(self): return self.to_text() @@ -180,41 +197,45 @@ class Message(object): @rtype: string """ - s = cStringIO.StringIO() - print >> s, 'id %d' % self.id - print >> s, 'opcode %s' % \ - dns.opcode.to_text(dns.opcode.from_flags(self.flags)) + s = StringIO() + s.write(u'id %d\n' % self.id) + s.write(u'opcode %s\n' % + dns.opcode.to_text(dns.opcode.from_flags(self.flags))) rc = dns.rcode.from_flags(self.flags, self.ednsflags) - print >> s, 'rcode %s' % dns.rcode.to_text(rc) - print >> s, 'flags %s' % dns.flags.to_text(self.flags) + s.write(u'rcode %s\n' % dns.rcode.to_text(rc)) + s.write(u'flags %s\n' % dns.flags.to_text(self.flags)) if self.edns >= 0: - print >> s, 'edns %s' % self.edns + s.write(u'edns %s\n' % self.edns) if self.ednsflags != 0: - print >> s, 'eflags %s' % \ - dns.flags.edns_to_text(self.ednsflags) - print >> s, 'payload', self.payload + s.write(u'eflags %s\n' % + dns.flags.edns_to_text(self.ednsflags)) + s.write(u'payload %d\n' % self.payload) is_update = dns.opcode.is_update(self.flags) if is_update: - print >> s, ';ZONE' + s.write(u';ZONE\n') else: - print >> s, ';QUESTION' + s.write(u';QUESTION\n') for rrset in self.question: - print >> s, rrset.to_text(origin, relativize, **kw) + s.write(rrset.to_text(origin, relativize, **kw)) + s.write(u'\n') if is_update: - print >> s, ';PREREQ' + s.write(u';PREREQ\n') else: - print >> s, ';ANSWER' + s.write(u';ANSWER\n') for rrset in self.answer: - print >> s, rrset.to_text(origin, relativize, **kw) + s.write(rrset.to_text(origin, relativize, **kw)) + s.write(u'\n') if is_update: - print >> s, ';UPDATE' + s.write(u';UPDATE\n') else: - print >> s, ';AUTHORITY' + s.write(u';AUTHORITY\n') for rrset in self.authority: - print >> s, rrset.to_text(origin, relativize, **kw) - print >> s, ';ADDITIONAL' + s.write(rrset.to_text(origin, relativize, **kw)) + s.write(u'\n') + s.write(u';ADDITIONAL\n') for rrset in self.additional: - print >> s, rrset.to_text(origin, relativize, **kw) + s.write(rrset.to_text(origin, relativize, **kw)) + s.write(u'\n') # # We strip off the final \n so the caller can print the result without # doing weird things to get around eccentricities in Python print @@ -266,7 +287,7 @@ class Message(object): dns.opcode.from_flags(other.flags): return False if dns.rcode.from_flags(other.flags, other.ednsflags) != \ - dns.rcode.NOERROR: + dns.rcode.NOERROR: return True if dns.opcode.is_update(self.flags): return True @@ -320,9 +341,9 @@ class Message(object): key = (self.section_number(section), name, rdclass, rdtype, covers, deleting) if not force_unique: - if not self.index is None: + if self.index is not None: rrset = self.index.get(key) - if not rrset is None: + if rrset is not None: return rrset else: for rrset in section: @@ -332,7 +353,7 @@ class Message(object): raise KeyError rrset = dns.rrset.RRset(name, rdclass, rdtype, covers, deleting) section.append(rrset) - if not self.index is None: + if self.index is not None: self.index[key] = rrset return rrset @@ -409,7 +430,7 @@ class Message(object): for rrset in self.additional: r.add_rrset(dns.renderer.ADDITIONAL, rrset, **kw) r.write_header() - if not self.keyname is None: + if self.keyname is not None: r.add_tsig(self.keyname, self.keyring[self.keyname], self.fudge, self.original_id, self.tsig_error, self.other_data, self.request_mac, @@ -448,7 +469,7 @@ class Message(object): if keyname is None: self.keyname = self.keyring.keys()[0] else: - if isinstance(keyname, (str, unicode)): + if isinstance(keyname, string_types): keyname = dns.name.from_text(keyname) self.keyname = keyname self.keyalgorithm = algorithm @@ -460,7 +481,8 @@ class Message(object): self.tsig_error = tsig_error self.other_data = other_data - def use_edns(self, edns=0, ednsflags=0, payload=1280, request_payload=None, options=None): + def use_edns(self, edns=0, ednsflags=0, payload=1280, request_payload=None, + options=None): """Configure EDNS behavior. @param edns: The EDNS level to use. Specifying None, False, or -1 means 'do not use EDNS', and in this case the other parameters are @@ -492,7 +514,7 @@ class Message(object): options = [] else: # make sure the EDNS version in ednsflags agrees with edns - ednsflags &= 0xFF00FFFFL + ednsflags &= long(0xFF00FFFF) ednsflags |= (edns << 16) if options is None: options = [] @@ -530,7 +552,7 @@ class Message(object): (value, evalue) = dns.rcode.to_flags(rcode) self.flags &= 0xFFF0 self.flags |= value - self.ednsflags &= 0x00FFFFFFL + self.ednsflags &= long(0x00FFFFFF) self.ednsflags |= evalue if self.ednsflags != 0 and self.edns < 0: self.edns = 0 @@ -549,7 +571,9 @@ class Message(object): self.flags &= 0x87FF self.flags |= dns.opcode.to_flags(opcode) + class _WireReader(object): + """Wire format reader. @ivar wire: the wire-format message. @@ -593,12 +617,12 @@ class _WireReader(object): for i in xrange(0, qcount): (qname, used) = dns.name.from_wire(self.wire, self.current) - if not self.message.origin is None: + if self.message.origin is not None: qname = qname.relativize(self.message.origin) self.current = self.current + used (rdtype, rdclass) = \ - struct.unpack('!HH', - self.wire[self.current:self.current + 4]) + struct.unpack('!HH', + self.wire[self.current:self.current + 4]) self.current = self.current + 4 self.message.find_rrset(self.message.question, qname, rdclass, rdtype, create=True, @@ -623,15 +647,15 @@ class _WireReader(object): rr_start = self.current (name, used) = dns.name.from_wire(self.wire, self.current) absolute_name = name - if not self.message.origin is None: + if self.message.origin is not None: name = name.relativize(self.message.origin) self.current = self.current + used (rdtype, rdclass, ttl, rdlen) = \ - struct.unpack('!HHIH', - self.wire[self.current:self.current + 10]) + struct.unpack('!HHIH', + self.wire[self.current:self.current + 10]) self.current = self.current + 10 if rdtype == dns.rdatatype.OPT: - if not section is self.message.additional or seen_opt: + if section is not self.message.additional or seen_opt: raise BadEDNS self.message.payload = rdclass self.message.ednsflags = ttl @@ -641,10 +665,11 @@ class _WireReader(object): optslen = rdlen while optslen > 0: (otype, olen) = \ - struct.unpack('!HH', - self.wire[current:current + 4]) + struct.unpack('!HH', + self.wire[current:current + 4]) current = current + 4 - opt = dns.edns.option_from_wire(otype, self.wire, current, olen) + opt = dns.edns.option_from_wire( + otype, self.wire, current, olen) self.message.options.append(opt) current = current + olen optslen = optslen - 4 - olen @@ -663,31 +688,31 @@ class _WireReader(object): dns.tsig.get_algorithm_and_mac(self.wire, self.current, rdlen) self.message.tsig_ctx = \ - dns.tsig.validate(self.wire, - absolute_name, - secret, - int(time.time()), - self.message.request_mac, - rr_start, - self.current, - rdlen, - self.message.tsig_ctx, - self.message.multi, - self.message.first) + dns.tsig.validate(self.wire, + absolute_name, + secret, + int(time.time()), + self.message.request_mac, + rr_start, + self.current, + rdlen, + self.message.tsig_ctx, + self.message.multi, + self.message.first) self.message.had_tsig = True else: if ttl < 0: ttl = 0 if self.updating and \ (rdclass == dns.rdataclass.ANY or - rdclass == dns.rdataclass.NONE): + rdclass == dns.rdataclass.NONE): deleting = rdclass rdclass = self.zone_rdclass else: deleting = None if deleting == dns.rdataclass.ANY or \ - (deleting == dns.rdataclass.NONE and \ - section is self.message.answer): + (deleting == dns.rdataclass.NONE and + section is self.message.answer): covers = dns.rdatatype.NONE rd = None else: @@ -700,7 +725,7 @@ class _WireReader(object): rrset = self.message.find_rrset(section, name, rdclass, rdtype, covers, deleting, True, force_unique) - if not rd is None: + if rd is not None: rrset.add(rd, ttl) self.current = self.current + rdlen @@ -725,14 +750,14 @@ class _WireReader(object): if not self.ignore_trailing and self.current != l: raise TrailingJunk if self.message.multi and self.message.tsig_ctx and \ - not self.message.had_tsig: + not self.message.had_tsig: self.message.tsig_ctx.update(self.wire) def from_wire(wire, keyring=None, request_mac='', xfr=False, origin=None, - tsig_ctx = None, multi = False, first = True, - question_only = False, one_rr_per_rrset = False, - ignore_trailing = False): + tsig_ctx=None, multi=False, first=True, + question_only=False, one_rr_per_rrset=False, + ignore_trailing=False): """Convert a DNS wire format message into a message object. @@ -786,6 +811,7 @@ def from_wire(wire, keyring=None, request_mac='', xfr=False, origin=None, class _TextReader(object): + """Text format reader. @ivar tok: the tokenizer @@ -823,13 +849,13 @@ class _TextReader(object): self.tok.unget(token) break self.message.flags = self.message.flags | \ - dns.flags.from_text(token.value) + dns.flags.from_text(token.value) if dns.opcode.is_update(self.message.flags): self.updating = True elif what == 'edns': self.message.edns = self.tok.get_int() self.message.ednsflags = self.message.ednsflags | \ - (self.message.edns << 16) + (self.message.edns << 16) elif what == 'eflags': if self.message.edns < 0: self.message.edns = 0 @@ -839,7 +865,7 @@ class _TextReader(object): self.tok.unget(token) break self.message.ednsflags = self.message.ednsflags | \ - dns.flags.edns_from_text(token.value) + dns.flags.edns_from_text(token.value) elif what == 'payload': self.message.payload = self.tok.get_int() if self.message.edns < 0: @@ -847,7 +873,7 @@ class _TextReader(object): elif what == 'opcode': text = self.tok.get_string() self.message.flags = self.message.flags | \ - dns.opcode.to_flags(dns.opcode.from_text(text)) + dns.opcode.to_flags(dns.opcode.from_text(text)) elif what == 'rcode': text = self.tok.get_string() self.message.set_rcode(dns.rcode.from_text(text)) @@ -858,7 +884,7 @@ class _TextReader(object): def _question_line(self, section): """Process one line from the text format question section.""" - token = self.tok.get(want_leading = True) + token = self.tok.get(want_leading=True) if not token.is_whitespace(): self.last_name = dns.name.from_text(token.value, None) name = self.last_name @@ -891,7 +917,7 @@ class _TextReader(object): deleting = None # Name - token = self.tok.get(want_leading = True) + token = self.tok.get(want_leading=True) if not token.is_whitespace(): self.last_name = dns.name.from_text(token.value, None) name = self.last_name @@ -934,7 +960,7 @@ class _TextReader(object): rrset = self.message.find_rrset(section, name, rdclass, rdtype, covers, deleting, True, self.updating) - if not rd is None: + if rd is not None: rrset.add(rd, ttl) def read(self): @@ -989,6 +1015,7 @@ def from_text(text): return m + def from_file(f): """Read the next text format message from the specified file. @@ -998,15 +1025,11 @@ def from_file(f): @raises dns.exception.SyntaxError: @rtype: dns.message.Message object""" - if sys.hexversion >= 0x02030000: - # allow Unicode filenames; turn on universal newline support - str_type = basestring - opts = 'rU' - else: - str_type = str - opts = 'r' + str_type = string_types + opts = 'rU' + if isinstance(f, str_type): - f = file(f, opts) + f = open(f, opts) want_close = True else: want_close = False @@ -1018,7 +1041,8 @@ def from_file(f): f.close() return m -def make_query(qname, rdtype, rdclass = dns.rdataclass.IN, use_edns=None, + +def make_query(qname, rdtype, rdclass=dns.rdataclass.IN, use_edns=None, want_dnssec=False, ednsflags=0, payload=1280, request_payload=None, options=None): """Make a query message. @@ -1054,11 +1078,11 @@ def make_query(qname, rdtype, rdclass = dns.rdataclass.IN, use_edns=None, @see: RFC 2671 @rtype: dns.message.Message object""" - if isinstance(qname, (str, unicode)): + if isinstance(qname, string_types): qname = dns.name.from_text(qname) - if isinstance(rdtype, (str, unicode)): + if isinstance(rdtype, string_types): rdtype = dns.rdatatype.from_text(rdtype) - if isinstance(rdclass, (str, unicode)): + if isinstance(rdclass, string_types): rdclass = dns.rdataclass.from_text(rdclass) m = Message() m.flags |= dns.flags.RD @@ -1068,6 +1092,7 @@ def make_query(qname, rdtype, rdclass = dns.rdataclass.IN, use_edns=None, m.want_dnssec(want_dnssec) return m + def make_response(query, recursion_available=False, our_payload=8192, fudge=300): """Make a message which is a response for the specified query. diff --git a/dns/name.py b/dns/name.py index f8faf455..1fc56ed7 100644 --- a/dns/name.py +++ b/dns/name.py @@ -21,63 +21,79 @@ @type empty: dns.name.Name object """ -import cStringIO +from io import BytesIO import struct import sys import copy - -if sys.hexversion >= 0x02030000: - import encodings.idna +import encodings.idna import dns.exception import dns.wiredata +from ._compat import long, binary_type, text_type + + +try: + maxint = sys.maxint +except: + maxint = (1 << (8 * struct.calcsize("P"))) / 2 - 1 + NAMERELN_NONE = 0 NAMERELN_SUPERDOMAIN = 1 NAMERELN_SUBDOMAIN = 2 NAMERELN_EQUAL = 3 NAMERELN_COMMONANCESTOR = 4 + class EmptyLabel(dns.exception.SyntaxError): + """A DNS label is empty.""" + class BadEscape(dns.exception.SyntaxError): + """An escaped code in a text format of DNS name is invalid.""" + class BadPointer(dns.exception.FormError): + """A DNS compression pointer points forward instead of backward.""" + class BadLabelType(dns.exception.FormError): + """The label type in DNS name wire format is unknown.""" + class NeedAbsoluteNameOrOrigin(dns.exception.DNSException): + """An attempt was made to convert a non-absolute name to wire when there was also a non-absolute (or missing) origin.""" + class NameTooLong(dns.exception.FormError): + """A DNS name is > 255 octets long.""" + class LabelTooLong(dns.exception.SyntaxError): + """A DNS label is > 63 octets long.""" + class AbsoluteConcatenation(dns.exception.DNSException): + """An attempt was made to append anything other than the empty name to an absolute DNS name.""" + class NoParent(dns.exception.DNSException): + """An attempt was made to get the parent of the root name or the empty name.""" -_escaped = { - '"' : True, - '(' : True, - ')' : True, - '.' : True, - ';' : True, - '\\' : True, - '@' : True, - '$' : True - } +_escaped = bytearray(b'"().;\\@$') + def _escapify(label, unicode_mode=False): """Escape the characters in label which need it. @@ -85,24 +101,38 @@ def _escapify(label, unicode_mode=False): characters @returns: the escaped string @rtype: string""" - text = '' + if not unicode_mode: + text = '' + if isinstance(label, text_type): + label = label.encode() + for c in bytearray(label): + packed = struct.pack('!B', c).decode() + if c in _escaped: + text += '\\' + packed + elif c > 0x20 and c < 0x7F: + text += packed + else: + text += '\\%03d' % c + return text.encode() + + text = u'' + if isinstance(label, binary_type): + label = label.decode() for c in label: - if c in _escaped: - text += '\\' + c - elif ord(c) > 0x20 and ord(c) < 0x7F: + if c > u'\x20' and c < u'\x7f': text += c else: - if unicode_mode and ord(c) >= 0x7F: + if c >= u'\x7f': text += c else: - text += '\\%03d' % ord(c) + text += u'\\%03d' % c return text + def _validate_labels(labels): """Check for empty labels in the middle of a label sequence, labels that are too long, and for too many labels. @raises NameTooLong: the name as a whole is too long - @raises LabelTooLong: an individual label is too long @raises EmptyLabel: a label is empty (i.e. the root label) and appears in a position other than the end of the label sequence""" @@ -115,7 +145,7 @@ def _validate_labels(labels): total += ll + 1 if ll > 63: raise LabelTooLong - if i < 0 and label == '': + if i < 0 and label == b'': i = j j += 1 if total > 255: @@ -123,7 +153,17 @@ def _validate_labels(labels): if i >= 0 and i != l - 1: raise EmptyLabel + +def _ensure_bytes(label): + if isinstance(label, binary_type): + return label + if isinstance(label, text_type): + return label.encode() + raise ValueError + + class Name(object): + """A DNS name. The dns.name.Name class represents a DNS name as a tuple of labels. @@ -139,7 +179,7 @@ class Name(object): @param labels: the labels @type labels: any iterable whose values are strings """ - + labels = [_ensure_bytes(x) for x in labels] super(Name, self).__setattr__('labels', tuple(labels)) _validate_labels(self.labels) @@ -153,7 +193,7 @@ class Name(object): return Name(copy.deepcopy(self.labels, memo)) def __getstate__(self): - return { 'labels' : self.labels } + return {'labels': self.labels} def __setstate__(self, state): super(Name, self).__setattr__('labels', state['labels']) @@ -164,25 +204,25 @@ class Name(object): @rtype: bool """ - return len(self.labels) > 0 and self.labels[-1] == '' + return len(self.labels) > 0 and self.labels[-1] == b'' def is_wild(self): """Is this name wild? (I.e. Is the least significant label '*'?) @rtype: bool """ - return len(self.labels) > 0 and self.labels[0] == '*' + return len(self.labels) > 0 and self.labels[0] == b'*' def __hash__(self): """Return a case-insensitive hash of the name. @rtype: int """ - h = 0L + h = long(0) for label in self.labels: - for c in label: - h += ( h << 3 ) + ord(c.lower()) - return int(h % sys.maxint) + for c in bytearray(label.lower()): + h += (h << 3) + c + return int(h % maxint) def fullcompare(self, other): """Compare two names, returning a 3-tuple (relation, order, nlabels). @@ -317,9 +357,9 @@ class Name(object): return '' def __str__(self): - return self.to_text(False) + return self.to_text(False).decode() - def to_text(self, omit_final_dot = False): + def to_text(self, omit_final_dot=False): """Convert name to text format. @param omit_final_dot: If True, don't emit the final dot (denoting the root label) for absolute names. The default is False. @@ -327,17 +367,17 @@ class Name(object): """ if len(self.labels) == 0: - return '@' - if len(self.labels) == 1 and self.labels[0] == '': - return '.' + return b'@' + if len(self.labels) == 1 and self.labels[0] == b'': + return b'.' if omit_final_dot and self.is_absolute(): l = self.labels[:-1] else: l = self.labels - s = '.'.join(map(_escapify, l)) + s = b'.'.join(map(_escapify, l)) return s - def to_unicode(self, omit_final_dot = False): + def to_unicode(self, omit_final_dot=False): """Convert name to Unicode text format. IDN ACE lables are converted to Unicode. @@ -355,7 +395,8 @@ class Name(object): l = self.labels[:-1] else: l = self.labels - s = u'.'.join([_escapify(encodings.idna.ToUnicode(x), True) for x in l]) + s = u'.'.join([_escapify(encodings.idna.ToUnicode(x), True) + for x in l]) return s def to_digestable(self, origin=None): @@ -379,14 +420,15 @@ class Name(object): labels.extend(list(origin.labels)) else: labels = self.labels - dlabels = ["%s%s" % (chr(len(x)), x.lower()) for x in labels] - return ''.join(dlabels) + dlabels = [struct.pack('!B%ds' % len(x), len(x), x.lower()) + for x in labels] + return b''.join(dlabels) - def to_wire(self, file = None, compress = None, origin = None): + def to_wire(self, file=None, compress=None, origin=None): """Convert name to wire format, possibly compressing it. @param file: the file where the name is emitted (typically - a cStringIO file). If None, a string containing the wire name + a BytesIO file). If None, a string containing the wire name will be returned. @type file: file or None @param compress: The compression table. If None (the default) names @@ -401,7 +443,7 @@ class Name(object): """ if file is None: - file = cStringIO.StringIO() + file = BytesIO() want_return = True else: want_return = False @@ -417,22 +459,22 @@ class Name(object): for label in labels: n = Name(labels[i:]) i += 1 - if not compress is None: + if compress is not None: pos = compress.get(n) else: pos = None - if not pos is None: + if pos is not None: value = 0xc000 + pos s = struct.pack('!H', value) file.write(s) break else: - if not compress is None and len(n) > 1: + if compress is not None and len(n) > 1: pos = file.tell() if pos <= 0x3fff: compress[n] = pos l = len(label) - file.write(chr(l)) + file.write(struct.pack('!B', l)) if l > 0: file.write(label) if want_return: @@ -474,8 +516,9 @@ class Name(object): elif depth == l: return (dns.name.empty, self) elif depth < 0 or depth > l: - raise ValueError('depth must be >= 0 and <= the length of the name') - return (Name(self[: -depth]), Name(self[-depth :])) + raise ValueError( + 'depth must be >= 0 and <= the length of the name') + return (Name(self[: -depth]), Name(self[-depth:])) def concatenate(self, other): """Return a new name which is the concatenation of self and other. @@ -496,7 +539,7 @@ class Name(object): @rtype: dns.name.Name object """ - if not origin is None and self.is_subdomain(origin): + if origin is not None and self.is_subdomain(origin): return Name(self[: -len(origin)]) else: return self @@ -538,10 +581,11 @@ class Name(object): raise NoParent return Name(self.labels[1:]) -root = Name(['']) +root = Name([b'']) empty = Name([]) -def from_unicode(text, origin = root): + +def from_unicode(text, origin=root): """Convert unicode text into a Name object. Lables are encoded in IDN ACE form. @@ -549,7 +593,7 @@ def from_unicode(text, origin = root): @rtype: dns.name.Name object """ - if not isinstance(text, unicode): + if not isinstance(text, text_type): raise ValueError("input to from_unicode() must be a unicode string") if not (origin is None or isinstance(origin, Name)): raise ValueError("origin must be a Name or None") @@ -562,7 +606,7 @@ def from_unicode(text, origin = root): text = u'' if text: if text == u'.': - return Name(['']) # no Unicode "u" on this constant! + return Name([b'']) # no Unicode "u" on this constant! for c in text: if escaping: if edigits == 0: @@ -581,8 +625,7 @@ def from_unicode(text, origin = root): if edigits == 3: escaping = False label += chr(total) - elif c == u'.' or c == u'\u3002' or \ - c == u'\uff0e' or c == u'\uff61': + elif c in [u'.', u'\u3002', u'\uff0e', u'\uff61']: if len(label) == 0: raise EmptyLabel if len(label) > 63: @@ -604,72 +647,75 @@ def from_unicode(text, origin = root): if len(label) > 0: labels.append(encodings.idna.ToASCII(label)) else: - labels.append('') - if (len(labels) == 0 or labels[-1] != '') and not origin is None: + labels.append(b'') + + if (len(labels) == 0 or labels[-1] != b'') and origin is not None: labels.extend(list(origin.labels)) return Name(labels) -def from_text(text, origin = root): + +def from_text(text, origin=root): """Convert text into a Name object. @rtype: dns.name.Name object """ - if not isinstance(text, str): - if isinstance(text, unicode) and sys.hexversion >= 0x02030000: - return from_unicode(text, origin) - else: - raise ValueError("input to from_text() must be a string") + if isinstance(text, text_type): + return from_unicode(text, origin) + if not isinstance(text, binary_type): + raise ValueError("input to from_text() must be a string") if not (origin is None or isinstance(origin, Name)): raise ValueError("origin must be a Name or None") labels = [] - label = '' + label = b'' escaping = False edigits = 0 total = 0 - if text == '@': - text = '' + if text == b'@': + text = b'' if text: - if text == '.': - return Name(['']) - for c in text: + if text == b'.': + return Name([b'']) + for c in bytearray(text): + byte_ = struct.pack('!B', c) if escaping: if edigits == 0: - if c.isdigit(): - total = int(c) + if byte_.isdigit(): + total = int(byte_) edigits += 1 else: - label += c + label += byte_ escaping = False else: - if not c.isdigit(): + if not byte_.isdigit(): raise BadEscape total *= 10 - total += int(c) + total += int(byte_) edigits += 1 if edigits == 3: escaping = False - label += chr(total) - elif c == '.': + label += struct.pack('!B', total) + elif byte_ == b'.': if len(label) == 0: raise EmptyLabel labels.append(label) - label = '' - elif c == '\\': + label = b'' + elif byte_ == b'\\': escaping = True edigits = 0 total = 0 else: - label += c + label += byte_ if escaping: raise BadEscape if len(label) > 0: labels.append(label) else: - labels.append('') - if (len(labels) == 0 or labels[-1] != '') and not origin is None: + labels.append(b'') + if (len(labels) == 0 or labels[-1] != b'') and origin is not None: labels.extend(list(origin.labels)) return Name(labels) + def from_wire(message, current): """Convert possibly compressed wire format into a Name. @param message: the entire DNS message @@ -685,23 +731,23 @@ def from_wire(message, current): @rtype: (dns.name.Name object, int) tuple """ - if not isinstance(message, str): + if not isinstance(message, binary_type): raise ValueError("input to from_wire() must be a byte string") message = dns.wiredata.maybe_wrap(message) labels = [] biggest_pointer = current hops = 0 - count = ord(message[current]) + count = message[current] current += 1 cused = 1 while count != 0: if count < 64: - labels.append(message[current : current + count].unwrap()) + labels.append(message[current: current + count].unwrap()) current += count if hops == 0: cused += count elif count >= 192: - current = (count & 0x3f) * 256 + ord(message[current]) + current = (count & 0x3f) * 256 + message[current] if hops == 0: cused += 1 if current >= biggest_pointer: @@ -710,7 +756,7 @@ def from_wire(message, current): hops += 1 else: raise BadLabelType - count = ord(message[current]) + count = message[current] current += 1 if hops == 0: cused += 1 diff --git a/dns/namedict.py b/dns/namedict.py index b9024a77..58e40344 100644 --- a/dns/namedict.py +++ b/dns/namedict.py @@ -25,8 +25,10 @@ """DNS name dictionary""" -import dns.name import collections +import dns.name +from ._compat import xrange + class NameDict(collections.MutableMapping): @@ -46,7 +48,7 @@ class NameDict(collections.MutableMapping): self.max_depth_items = 0 self.update(dict(*args, **kwargs)) - def __update_max_depth(self,key): + def __update_max_depth(self, key): if len(key) == self.max_depth: self.max_depth_items = self.max_depth_items + 1 elif len(key) > self.max_depth: @@ -77,8 +79,8 @@ class NameDict(collections.MutableMapping): def __len__(self): return len(self.__store) - def has_key(self,key): - return self.__store.has_key(key) + def has_key(self, key): + return key in self.__store def get_deepest_match(self, name): """Find the deepest match to I{name} in the dictionary. @@ -96,7 +98,7 @@ class NameDict(collections.MutableMapping): depth = self.max_depth for i in xrange(-depth, 0): n = dns.name.Name(name[i:]) - if self.has_key(n): + if n in self: return (n, self[n]) v = self[dns.name.empty] return (dns.name.empty, v) diff --git a/dns/node.py b/dns/node.py index e74004f6..7c25060e 100644 --- a/dns/node.py +++ b/dns/node.py @@ -15,13 +15,15 @@ """DNS nodes. A node is a set of rdatasets.""" -import StringIO +from io import StringIO import dns.rdataset import dns.rdatatype import dns.renderer + class Node(object): + """A DNS node. A node is a set of rdatasets @@ -35,7 +37,7 @@ class Node(object): """Initialize a DNS node. """ - self.rdatasets = []; + self.rdatasets = [] def to_text(self, name, **kw): """Convert a node to text format. @@ -47,10 +49,11 @@ class Node(object): @rtype: string """ - s = StringIO.StringIO() + s = StringIO() for rds in self.rdatasets: if len(rds) > 0: - print >> s, rds.to_text(name, **kw) + s.write(rds.to_text(name, **kw)) + s.write(u'\n') return s.getvalue()[:-1] def __repr__(self): @@ -155,7 +158,7 @@ class Node(object): """ rds = self.get_rdataset(rdclass, rdtype, covers) - if not rds is None: + if rds is not None: self.rdatasets.remove(rds) def replace_rdataset(self, replacement): @@ -169,7 +172,7 @@ class Node(object): """ if not isinstance(replacement, dns.rdataset.Rdataset): - raise ValueError, 'replacement is not an rdataset' + raise ValueError('replacement is not an rdataset') self.delete_rdataset(replacement.rdclass, replacement.rdtype, replacement.covers) self.rdatasets.append(replacement) diff --git a/dns/opcode.py b/dns/opcode.py index 40fe8d1a..2b2918e9 100644 --- a/dns/opcode.py +++ b/dns/opcode.py @@ -24,23 +24,25 @@ NOTIFY = 4 UPDATE = 5 _by_text = { - 'QUERY' : QUERY, - 'IQUERY' : IQUERY, - 'STATUS' : STATUS, - 'NOTIFY' : NOTIFY, - 'UPDATE' : UPDATE + 'QUERY': QUERY, + 'IQUERY': IQUERY, + 'STATUS': STATUS, + 'NOTIFY': NOTIFY, + 'UPDATE': UPDATE } # We construct the inverse mapping programmatically to ensure that we # cannot make any mistakes (e.g. omissions, cut-and-paste errors) that # would cause the mapping not to be true inverse. -_by_value = dict([(y, x) for x, y in _by_text.iteritems()]) +_by_value = dict((y, x) for x, y in _by_text.items()) class UnknownOpcode(dns.exception.DNSException): + """An DNS opcode is unknown.""" + def from_text(text): """Convert text into an opcode. @@ -59,23 +61,26 @@ def from_text(text): raise UnknownOpcode return value + def from_flags(flags): """Extract an opcode from DNS message flags. @param flags: int @rtype: int """ - + return (flags & 0x7800) >> 11 + def to_flags(value): """Convert an opcode to a value suitable for ORing into DNS message flags. @rtype: int """ - + return (value << 11) & 0x7800 - + + def to_text(value): """Convert an opcode to text. @@ -84,12 +89,13 @@ def to_text(value): @raises UnknownOpcode: the opcode is unknown @rtype: string """ - + text = _by_value.get(value) if text is None: text = str(value) return text + def is_update(flags): """True if the opcode in flags is UPDATE. @@ -97,7 +103,7 @@ def is_update(flags): @type flags: int @rtype: bool """ - + if (from_flags(flags) == UPDATE): return True return False diff --git a/dns/query.py b/dns/query.py index 4e8089f5..cfa3f172 100644 --- a/dns/query.py +++ b/dns/query.py @@ -30,19 +30,31 @@ import dns.name import dns.message import dns.rdataclass import dns.rdatatype +from ._compat import long, string_types + +if sys.version_info > (3,): + select_error = OSError +else: + select_error = select.error + class UnexpectedSource(dns.exception.DNSException): + """A DNS query response came from an unexpected address or port.""" + class BadResponse(dns.exception.FormError): + """A DNS query response does not respond to the question asked.""" + def _compute_expiration(timeout): if timeout is None: return None else: return time.time() + timeout + def _poll_for(fd, readable, writable, error, timeout): """Poll polling backend. @param fd: File descriptor @@ -73,6 +85,7 @@ def _poll_for(fd, readable, writable, error, timeout): return bool(event_list) + def _select_for(fd, readable, writable, error, timeout): """Select polling backend. @param fd: File descriptor @@ -101,6 +114,7 @@ def _select_for(fd, readable, writable, error, timeout): return bool((rcount or wcount or xcount)) + def _wait_for(fd, readable, writable, error, expiration): done = False while not done: @@ -113,11 +127,12 @@ def _wait_for(fd, readable, writable, error, expiration): try: if not _polling_backend(fd, readable, writable, error, timeout): raise dns.exception.Timeout - except select.error, e: + except select_error as e: if e.args[0] != errno.EINTR: raise e done = True + def _set_polling_backend(fn): """ Internal API. Do not use. @@ -134,12 +149,15 @@ if hasattr(select, 'poll'): else: _polling_backend = _select_for + def _wait_for_readable(s, expiration): _wait_for(s, True, False, True, expiration) + def _wait_for_writable(s, expiration): _wait_for(s, False, True, True, expiration) + def _addresses_equal(af, a1, a2): # Convert the first value of the tuple, which is a textual format # address into binary form, so that we are not confused by different @@ -148,6 +166,7 @@ def _addresses_equal(af, a1, a2): n2 = dns.inet.inet_pton(af, a2[0]) return n1 == n2 and a1[1:] == a2[1:] + def _destination_and_source(af, where, port, source, source_port): # Apply defaults and compute destination and source tuples # suitable for use in connect(), sendto(), or bind(). @@ -170,6 +189,7 @@ def _destination_and_source(af, where, port, source, source_port): source = (source, source_port, 0, 0) return (af, destination, source) + def udp(q, where, timeout=None, port=53, af=None, source=None, source_port=0, ignore_unexpected=False, one_rr_per_rrset=False): """Return the response obtained after sending a query via UDP. @@ -201,8 +221,8 @@ def udp(q, where, timeout=None, port=53, af=None, source=None, source_port=0, """ wire = q.to_wire() - (af, destination, source) = _destination_and_source(af, where, port, source, - source_port) + (af, destination, source) = _destination_and_source(af, where, port, + source, source_port) s = socket.socket(af, socket.SOCK_DGRAM, 0) begin_time = None try: @@ -217,8 +237,8 @@ def udp(q, where, timeout=None, port=53, af=None, source=None, source_port=0, _wait_for_readable(s, expiration) (wire, from_address) = s.recvfrom(65535) if _addresses_equal(af, from_address, destination) or \ - (dns.inet.is_multicast(where) and \ - from_address[1:] == destination[1:]): + (dns.inet.is_multicast(where) and + from_address[1:] == destination[1:]): break if not ignore_unexpected: raise UnexpectedSource('got a response from ' @@ -237,6 +257,7 @@ def udp(q, where, timeout=None, port=53, af=None, source=None, source_port=0, raise BadResponse return r + def _net_read(sock, count, expiration): """Read the specified number of bytes from sock. Keep trying until we either get the desired amount, or we hit EOF. @@ -253,6 +274,7 @@ def _net_read(sock, count, expiration): s = s + n return s + def _net_write(sock, data, expiration): """Write the specified data to the socket. A Timeout exception will be raised if the operation is not completed @@ -264,16 +286,18 @@ def _net_write(sock, data, expiration): _wait_for_writable(sock, expiration) current += sock.send(data[current:]) + def _connect(s, address): try: s.connect(address) except socket.error: (ty, v) = sys.exc_info()[:2] if v[0] != errno.EINPROGRESS and \ - v[0] != errno.EWOULDBLOCK and \ - v[0] != errno.EALREADY: + v[0] != errno.EWOULDBLOCK and \ + v[0] != errno.EALREADY: raise v + def tcp(q, where, timeout=None, port=53, af=None, source=None, source_port=0, one_rr_per_rrset=False): """Return the response obtained after sending a query via TCP. @@ -302,8 +326,8 @@ def tcp(q, where, timeout=None, port=53, af=None, source=None, source_port=0, """ wire = q.to_wire() - (af, destination, source) = _destination_and_source(af, where, port, source, - source_port) + (af, destination, source) = _destination_and_source(af, where, port, + source, source_port) s = socket.socket(af, socket.SOCK_STREAM, 0) begin_time = None try: @@ -337,6 +361,7 @@ def tcp(q, where, timeout=None, port=53, af=None, source=None, source_port=0, raise BadResponse return r + def xfr(where, zone, rdtype=dns.rdatatype.AXFR, rdclass=dns.rdataclass.IN, timeout=None, port=53, keyring=None, keyname=None, relativize=True, af=None, lifetime=None, source=None, source_port=0, serial=0, @@ -390,20 +415,20 @@ def xfr(where, zone, rdtype=dns.rdatatype.AXFR, rdclass=dns.rdataclass.IN, @type keyalgorithm: string """ - if isinstance(zone, (str, unicode)): + if isinstance(zone, string_types): zone = dns.name.from_text(zone) - if isinstance(rdtype, (str, unicode)): + if isinstance(rdtype, string_types): rdtype = dns.rdatatype.from_text(rdtype) q = dns.message.make_query(zone, rdtype, rdclass) if rdtype == dns.rdatatype.IXFR: rrset = dns.rrset.from_text(zone, 0, 'IN', 'SOA', '. . %u 0 0 0 0' % serial) q.authority.append(rrset) - if not keyring is None: + if keyring is not None: q.use_tsig(keyring, keyname, algorithm=keyalgorithm) wire = q.to_wire() - (af, destination, source) = _destination_and_source(af, where, port, source, - source_port) + (af, destination, source) = _destination_and_source(af, where, port, + source, source_port) if use_udp: if rdtype != dns.rdatatype.IXFR: raise ValueError('cannot do a UDP AXFR') @@ -426,7 +451,6 @@ def xfr(where, zone, rdtype=dns.rdatatype.AXFR, rdclass=dns.rdataclass.IN, delete_mode = True expecting_SOA = False soa_rrset = None - soa_count = 0 if relativize: origin = zone oname = dns.name.empty @@ -446,16 +470,18 @@ def xfr(where, zone, rdtype=dns.rdatatype.AXFR, rdclass=dns.rdataclass.IN, ldata = _net_read(s, 2, mexpiration) (l,) = struct.unpack("!H", ldata) wire = _net_read(s, l, mexpiration) + is_ixfr = (rdtype == dns.rdatatype.IXFR) r = dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac, xfr=True, origin=origin, tsig_ctx=tsig_ctx, multi=True, first=first, - one_rr_per_rrset=(rdtype==dns.rdatatype.IXFR)) + one_rr_per_rrset=is_ixfr) tsig_ctx = r.tsig_ctx first = False answer_index = 0 if soa_rrset is None: if not r.answer or r.answer[0].name != oname: - raise dns.exception.FormError("No answer or RRset not for qname") + raise dns.exception.FormError( + "No answer or RRset not for qname") rrset = r.answer[0] if rrset.rdtype != dns.rdatatype.SOA: raise dns.exception.FormError("first RRset is not an SOA") @@ -479,7 +505,8 @@ def xfr(where, zone, rdtype=dns.rdatatype.AXFR, rdclass=dns.rdataclass.IN, if rrset.rdtype == dns.rdatatype.SOA and rrset.name == oname: if expecting_SOA: if rrset[0].serial != serial: - raise dns.exception.FormError("IXFR base serial mismatch") + raise dns.exception.FormError( + "IXFR base serial mismatch") expecting_SOA = False elif rdtype == dns.rdatatype.IXFR: delete_mode = not delete_mode @@ -489,8 +516,8 @@ def xfr(where, zone, rdtype=dns.rdatatype.AXFR, rdclass=dns.rdataclass.IN, # the record in the expected part of the response. # if rrset == soa_rrset and \ - (rdtype == dns.rdatatype.AXFR or \ - (rdtype == dns.rdatatype.IXFR and delete_mode)): + (rdtype == dns.rdatatype.AXFR or + (rdtype == dns.rdatatype.IXFR and delete_mode)): done = True elif expecting_SOA: # diff --git a/dns/rcode.py b/dns/rcode.py index 372d724f..698ad25c 100644 --- a/dns/rcode.py +++ b/dns/rcode.py @@ -16,6 +16,8 @@ """DNS Result Codes.""" import dns.exception +from ._compat import long + NOERROR = 0 FORMERR = 1 @@ -31,30 +33,32 @@ NOTZONE = 10 BADVERS = 16 _by_text = { - 'NOERROR' : NOERROR, - 'FORMERR' : FORMERR, - 'SERVFAIL' : SERVFAIL, - 'NXDOMAIN' : NXDOMAIN, - 'NOTIMP' : NOTIMP, - 'REFUSED' : REFUSED, - 'YXDOMAIN' : YXDOMAIN, - 'YXRRSET' : YXRRSET, - 'NXRRSET' : NXRRSET, - 'NOTAUTH' : NOTAUTH, - 'NOTZONE' : NOTZONE, - 'BADVERS' : BADVERS + 'NOERROR': NOERROR, + 'FORMERR': FORMERR, + 'SERVFAIL': SERVFAIL, + 'NXDOMAIN': NXDOMAIN, + 'NOTIMP': NOTIMP, + 'REFUSED': REFUSED, + 'YXDOMAIN': YXDOMAIN, + 'YXRRSET': YXRRSET, + 'NXRRSET': NXRRSET, + 'NOTAUTH': NOTAUTH, + 'NOTZONE': NOTZONE, + 'BADVERS': BADVERS } # We construct the inverse mapping programmatically to ensure that we # cannot make any mistakes (e.g. omissions, cut-and-paste errors) that # would cause the mapping not to be a true inverse. -_by_value = dict([(y, x) for x, y in _by_text.iteritems()]) +_by_value = dict((y, x) for x, y in _by_text.items()) class UnknownRcode(dns.exception.DNSException): + """A DNS rcode is unknown.""" + def from_text(text): """Convert text into an rcode. @@ -73,6 +77,7 @@ def from_text(text): raise UnknownRcode return v + def from_flags(flags, ednsflags): """Return the rcode value encoded by flags and ednsflags. @@ -89,6 +94,7 @@ def from_flags(flags, ednsflags): raise ValueError('rcode must be >= 0 and <= 4095') return value + def to_flags(value): """Return a (flags, ednsflags) tuple which encodes the rcode. @@ -104,6 +110,7 @@ def to_flags(value): ev = long(value & 0xff0) << 20 return (v, ev) + def to_text(value): """Convert rcode into text. diff --git a/dns/rdata.py b/dns/rdata.py index ef0dddef..cb8c2743 100644 --- a/dns/rdata.py +++ b/dns/rdata.py @@ -25,7 +25,10 @@ default is 'dns.rdtypes'. Changing this value will break the library. chunk of hexstring that _hexify() produces before whitespace occurs. @type _hex_chunk: int""" -import cStringIO +from io import BytesIO +import base64 +import binascii +import struct import dns.exception import dns.name @@ -33,10 +36,12 @@ import dns.rdataclass import dns.rdatatype import dns.tokenizer import dns.wiredata +from ._compat import xrange, string_types, text_type _hex_chunksize = 32 -def _hexify(data, chunksize=None): + +def _hexify(data, chunksize=_hex_chunksize): """Convert a binary string into its hex encoding, broken up into chunks of I{chunksize} characters separated by a space. @@ -46,22 +51,15 @@ def _hexify(data, chunksize=None): @rtype: string """ - if chunksize is None: - chunksize = _hex_chunksize - hex = data.encode('hex_codec') - l = len(hex) - if l > chunksize: - chunks = [] - i = 0 - while i < l: - chunks.append(hex[i : i + chunksize]) - i += chunksize - hex = ' '.join(chunks) - return hex + line = binascii.hexlify(data) + return b' '.join([line[i:i + chunksize] + for i + in range(0, len(line), chunksize)]).decode() _base64_chunksize = 32 -def _base64ify(data, chunksize=None): + +def _base64ify(data, chunksize=_base64_chunksize): """Convert a binary string into its base64 encoding, broken up into chunks of I{chunksize} characters separated by a space. @@ -72,24 +70,16 @@ def _base64ify(data, chunksize=None): @rtype: string """ - if chunksize is None: - chunksize = _base64_chunksize - b64 = data.encode('base64_codec') - b64 = b64.replace('\n', '') - l = len(b64) - if l > chunksize: - chunks = [] - i = 0 - while i < l: - chunks.append(b64[i : i + chunksize]) - i += chunksize - b64 = ' '.join(chunks) - return b64 + line = base64.b64encode(data) + return b' '.join([line[i:i + chunksize] + for i + in range(0, len(line), chunksize)]).decode() __escaped = { - '"' : True, - '\\' : True, - } + '"': True, + '\\': True, +} + def _escapify(qstring): """Escape the characters in a quoted string which need it. @@ -100,16 +90,23 @@ def _escapify(qstring): @rtype: string """ + if isinstance(qstring, text_type): + qstring = qstring.encode() + if not isinstance(qstring, bytearray): + qstring = bytearray(qstring) + text = '' for c in qstring: - if c in __escaped: - text += '\\' + c - elif ord(c) >= 0x20 and ord(c) < 0x7F: - text += c + packed = struct.pack('!B', c).decode() + if packed in __escaped: + text += '\\' + packed + elif c >= 0x20 and c < 0x7F: + text += packed else: - text += '\\%03d' % ord(c) + text += '\\%03d' % c return text + def _truncate_bitmap(what): """Determine the index of greatest byte that isn't all zeros, and return the bitmap that contains all the bytes less than that index. @@ -120,11 +117,13 @@ def _truncate_bitmap(what): """ for i in xrange(len(what) - 1, -1, -1): - if what[i] != '\x00': + if what[i] != 0: break - return ''.join(what[0 : i + 1]) + return what[0: i + 1] + class Rdata(object): + """Base class for all DNS rdata types. """ @@ -167,17 +166,17 @@ class Rdata(object): """ raise NotImplementedError - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): """Convert an rdata to wire format. @rtype: string """ raise NotImplementedError - def to_digestable(self, origin = None): + def to_digestable(self, origin=None): """Convert rdata to a format suitable for digesting in hashes. This is also the DNSSEC canonical form.""" - f = cStringIO.StringIO() + f = BytesIO() self.to_wire(f, None, origin) return f.getvalue() @@ -207,57 +206,59 @@ class Rdata(object): rdclass. Return < 0 if self < other in the DNSSEC ordering, 0 if self == other, and > 0 if self > other. """ - return cmp(self.to_digestable(dns.name.root), - other.to_digestable(dns.name.root)) + our = self.to_digestable(dns.name.root) + their = other.to_digestable(dns.name.root) + if our == their: + return 0 + if our > their: + return 1 + + return -1 def __eq__(self, other): if not isinstance(other, Rdata): return False - if self.rdclass != other.rdclass or \ - self.rdtype != other.rdtype: + if self.rdclass != other.rdclass or self.rdtype != other.rdtype: return False return self._cmp(other) == 0 def __ne__(self, other): if not isinstance(other, Rdata): return True - if self.rdclass != other.rdclass or \ - self.rdtype != other.rdtype: + if self.rdclass != other.rdclass or self.rdtype != other.rdtype: return True return self._cmp(other) != 0 def __lt__(self, other): if not isinstance(other, Rdata) or \ - self.rdclass != other.rdclass or \ - self.rdtype != other.rdtype: + self.rdclass != other.rdclass or self.rdtype != other.rdtype: + return NotImplemented return self._cmp(other) < 0 def __le__(self, other): if not isinstance(other, Rdata) or \ - self.rdclass != other.rdclass or \ - self.rdtype != other.rdtype: + self.rdclass != other.rdclass or self.rdtype != other.rdtype: return NotImplemented return self._cmp(other) <= 0 def __ge__(self, other): if not isinstance(other, Rdata) or \ - self.rdclass != other.rdclass or \ - self.rdtype != other.rdtype: + self.rdclass != other.rdclass or self.rdtype != other.rdtype: return NotImplemented return self._cmp(other) >= 0 def __gt__(self, other): if not isinstance(other, Rdata) or \ - self.rdclass != other.rdclass or \ - self.rdtype != other.rdtype: + self.rdclass != other.rdclass or self.rdtype != other.rdtype: return NotImplemented return self._cmp(other) > 0 def __hash__(self): return hash(self.to_digestable(dns.name.root)) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): """Build an rdata object from text format. @param rdclass: The rdata class @@ -275,9 +276,8 @@ class Rdata(object): raise NotImplementedError - from_text = classmethod(from_text) - - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): """Build an rdata object from wire format @param rdclass: The rdata class @@ -297,9 +297,7 @@ class Rdata(object): raise NotImplementedError - from_wire = classmethod(from_wire) - - def choose_relativity(self, origin = None, relativize = True): + def choose_relativity(self, origin=None, relativize=True): """Convert any domain names in the rdata to the specified relativization. """ @@ -308,6 +306,7 @@ class Rdata(object): class GenericRdata(Rdata): + """Generate Rdata Class This class is used for rdata types for which we have no better @@ -323,36 +322,37 @@ class GenericRdata(Rdata): def to_text(self, origin=None, relativize=True, **kw): return r'\# %d ' % len(self.data) + _hexify(self.data) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): token = tok.get() if not token.is_identifier() or token.value != '\#': - raise dns.exception.SyntaxError(r'generic rdata does not start with \#') + raise dns.exception.SyntaxError( + r'generic rdata does not start with \#') length = tok.get_int() chunks = [] while 1: token = tok.get() if token.is_eol_or_eof(): break - chunks.append(token.value) - hex = ''.join(chunks) - data = hex.decode('hex_codec') + chunks.append(token.value.encode()) + hex = b''.join(chunks) + data = binascii.unhexlify(hex) if len(data) != length: - raise dns.exception.SyntaxError('generic rdata hex data has wrong length') + raise dns.exception.SyntaxError( + 'generic rdata hex data has wrong length') return cls(rdclass, rdtype, data) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): file.write(self.data) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): - return cls(rdclass, rdtype, wire[current : current + rdlen]) - - from_wire = classmethod(from_wire) + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): + return cls(rdclass, rdtype, wire[current: current + rdlen]) _rdata_modules = {} _module_prefix = 'dns.rdtypes' + def get_rdata_class(rdclass, rdtype): def import_module(name): @@ -386,7 +386,8 @@ def get_rdata_class(rdclass, rdtype): cls = GenericRdata return cls -def from_text(rdclass, rdtype, tok, origin = None, relativize = True): + +def from_text(rdclass, rdtype, tok, origin=None, relativize=True): """Build an rdata object from text format. This function attempts to dynamically load a class which @@ -412,7 +413,7 @@ def from_text(rdclass, rdtype, tok, origin = None, relativize = True): @type relativize: bool @rtype: dns.rdata.Rdata instance""" - if isinstance(tok, (str, unicode)): + if isinstance(tok, string_types): tok = dns.tokenizer.Tokenizer(tok) cls = get_rdata_class(rdclass, rdtype) if cls != GenericRdata: @@ -432,7 +433,8 @@ def from_text(rdclass, rdtype, tok, origin = None, relativize = True): origin) return cls.from_text(rdclass, rdtype, tok, origin, relativize) -def from_wire(rdclass, rdtype, wire, current, rdlen, origin = None): + +def from_wire(rdclass, rdtype, wire, current, rdlen, origin=None): """Build an rdata object from wire format This function attempts to dynamically load a class which diff --git a/dns/rdataclass.py b/dns/rdataclass.py index 06afa867..17a4810d 100644 --- a/dns/rdataclass.py +++ b/dns/rdataclass.py @@ -35,39 +35,42 @@ NONE = 254 ANY = 255 _by_text = { - 'RESERVED0' : RESERVED0, - 'IN' : IN, - 'CH' : CH, - 'HS' : HS, - 'NONE' : NONE, - 'ANY' : ANY - } + 'RESERVED0': RESERVED0, + 'IN': IN, + 'CH': CH, + 'HS': HS, + 'NONE': NONE, + 'ANY': ANY +} # We construct the inverse mapping programmatically to ensure that we # cannot make any mistakes (e.g. omissions, cut-and-paste errors) that # would cause the mapping not to be true inverse. -_by_value = dict([(y, x) for x, y in _by_text.iteritems()]) +_by_value = dict((y, x) for x, y in _by_text.items()) # Now that we've built the inverse map, we can add class aliases to # the _by_text mapping. _by_text.update({ - 'INTERNET' : IN, - 'CHAOS' : CH, - 'HESIOD' : HS - }) + 'INTERNET': IN, + 'CHAOS': CH, + 'HESIOD': HS +}) _metaclasses = { - NONE : True, - ANY : True - } + NONE: True, + ANY: True +} + +_unknown_class_pattern = re.compile('CLASS([0-9]+)$', re.I) -_unknown_class_pattern = re.compile('CLASS([0-9]+)$', re.I); class UnknownRdataclass(dns.exception.DNSException): + """A DNS class is unknown.""" + def from_text(text): """Convert text into a DNS rdata class value. @param text: the text @@ -80,13 +83,14 @@ def from_text(text): value = _by_text.get(text.upper()) if value is None: match = _unknown_class_pattern.match(text) - if match == None: + if match is None: raise UnknownRdataclass value = int(match.group(1)) if value < 0 or value > 65535: raise ValueError("class must be between >= 0 and <= 65535") return value + def to_text(value): """Convert a DNS rdata class to text. @param value: the rdata class value @@ -99,15 +103,16 @@ def to_text(value): raise ValueError("class must be between >= 0 and <= 65535") text = _by_value.get(value) if text is None: - text = 'CLASS' + `value` + text = 'CLASS' + repr(value) return text + def is_metaclass(rdclass): """True if the class is a metaclass. @param rdclass: the rdata class @type rdclass: int @rtype: bool""" - if _metaclasses.has_key(rdclass): + if rdclass in _metaclasses: return True return False diff --git a/dns/rdataset.py b/dns/rdataset.py index 9524979f..db266f2f 100644 --- a/dns/rdataset.py +++ b/dns/rdataset.py @@ -16,7 +16,7 @@ """DNS rdatasets (an rdataset is a set of rdatas of a given type and class)""" import random -import StringIO +from io import StringIO import struct import dns.exception @@ -24,18 +24,25 @@ import dns.rdatatype import dns.rdataclass import dns.rdata import dns.set +from ._compat import string_types # define SimpleSet here for backwards compatibility SimpleSet = dns.set.Set + class DifferingCovers(dns.exception.DNSException): + """An attempt was made to add a DNS SIG/RRSIG whose covered type is not the same as that of the other rdatas in the rdataset.""" + class IncompatibleTypes(dns.exception.DNSException): + """An attempt was made to add DNS RR data of an incompatible type.""" + class Rdataset(dns.set.Set): + """A DNS rdataset. @ivar rdclass: The class of the rdataset @@ -108,7 +115,7 @@ class Rdataset(dns.set.Set): # if self.rdclass != rd.rdclass or self.rdtype != rd.rdtype: raise IncompatibleTypes - if not ttl is None: + if ttl is not None: self.update_ttl(ttl) if self.rdtype == dns.rdatatype.RRSIG or \ self.rdtype == dns.rdatatype.SIG: @@ -183,15 +190,15 @@ class Rdataset(dns.set.Set): @type origin: dns.name.Name object @param relativize: True if names should names be relativized @type relativize: bool""" - if not name is None: + if name is not None: name = name.choose_relativity(origin, relativize) ntext = str(name) pad = ' ' else: ntext = '' pad = '' - s = StringIO.StringIO() - if not override_rdclass is None: + s = StringIO() + if override_rdclass is not None: rdclass = override_rdclass else: rdclass = self.rdclass @@ -201,15 +208,16 @@ class Rdataset(dns.set.Set): # some dynamic updates, so we don't need to print out the TTL # (which is meaningless anyway). # - print >> s, '%s%s%s %s' % (ntext, pad, - dns.rdataclass.to_text(rdclass), - dns.rdatatype.to_text(self.rdtype)) + s.write(u'%s%s%s %s\n' % (ntext, pad, + dns.rdataclass.to_text(rdclass), + dns.rdatatype.to_text(self.rdtype))) else: for rd in self: - print >> s, '%s%s%d %s %s %s' % \ - (ntext, pad, self.ttl, dns.rdataclass.to_text(rdclass), - dns.rdatatype.to_text(self.rdtype), - rd.to_text(origin=origin, relativize=relativize, **kw)) + s.write(u'%s%s%d %s %s %s\n' % + (ntext, pad, self.ttl, dns.rdataclass.to_text(rdclass), + dns.rdatatype.to_text(self.rdtype), + rd.to_text(origin=origin, relativize=relativize, + **kw))) # # We strip off the final \n for the caller's convenience in printing # @@ -231,8 +239,8 @@ class Rdataset(dns.set.Set): @rtype: int """ - if not override_rdclass is None: - rdclass = override_rdclass + if override_rdclass is not None: + rdclass = override_rdclass want_shuffle = False else: rdclass = self.rdclass @@ -272,6 +280,7 @@ class Rdataset(dns.set.Set): return True return False + def from_text_list(rdclass, rdtype, ttl, text_rdatas): """Create an rdataset with the specified class, type, and TTL, and with the specified list of rdatas in text format. @@ -279,9 +288,9 @@ def from_text_list(rdclass, rdtype, ttl, text_rdatas): @rtype: dns.rdataset.Rdataset object """ - if isinstance(rdclass, (str, unicode)): + if isinstance(rdclass, string_types): rdclass = dns.rdataclass.from_text(rdclass) - if isinstance(rdtype, (str, unicode)): + if isinstance(rdtype, string_types): rdtype = dns.rdatatype.from_text(rdtype) r = Rdataset(rdclass, rdtype) r.update_ttl(ttl) @@ -290,6 +299,7 @@ def from_text_list(rdclass, rdtype, ttl, text_rdatas): r.add(rd) return r + def from_text(rdclass, rdtype, ttl, *text_rdatas): """Create an rdataset with the specified class, type, and TTL, and with the specified rdatas in text format. @@ -299,6 +309,7 @@ def from_text(rdclass, rdtype, ttl, *text_rdatas): return from_text_list(rdclass, rdtype, ttl, text_rdatas) + def from_rdata_list(ttl, rdatas): """Create an rdataset with the specified TTL, and with the specified list of rdata objects. @@ -313,10 +324,10 @@ def from_rdata_list(ttl, rdatas): if r is None: r = Rdataset(rd.rdclass, rd.rdtype) r.update_ttl(ttl) - first_time = False r.add(rd) return r + def from_rdata(ttl, *rdatas): """Create an rdataset with the specified TTL, and with the specified rdata objects. diff --git a/dns/rdatatype.py b/dns/rdatatype.py index 8e98f492..f4336fca 100644 --- a/dns/rdatatype.py +++ b/dns/rdatatype.py @@ -99,99 +99,102 @@ TA = 32768 DLV = 32769 _by_text = { - 'NONE' : NONE, - 'A' : A, - 'NS' : NS, - 'MD' : MD, - 'MF' : MF, - 'CNAME' : CNAME, - 'SOA' : SOA, - 'MB' : MB, - 'MG' : MG, - 'MR' : MR, - 'NULL' : NULL, - 'WKS' : WKS, - 'PTR' : PTR, - 'HINFO' : HINFO, - 'MINFO' : MINFO, - 'MX' : MX, - 'TXT' : TXT, - 'RP' : RP, - 'AFSDB' : AFSDB, - 'X25' : X25, - 'ISDN' : ISDN, - 'RT' : RT, - 'NSAP' : NSAP, - 'NSAP-PTR' : NSAP_PTR, - 'SIG' : SIG, - 'KEY' : KEY, - 'PX' : PX, - 'GPOS' : GPOS, - 'AAAA' : AAAA, - 'LOC' : LOC, - 'NXT' : NXT, - 'SRV' : SRV, - 'NAPTR' : NAPTR, - 'KX' : KX, - 'CERT' : CERT, - 'A6' : A6, - 'DNAME' : DNAME, - 'OPT' : OPT, - 'APL' : APL, - 'DS' : DS, - 'SSHFP' : SSHFP, - 'IPSECKEY' : IPSECKEY, - 'RRSIG' : RRSIG, - 'NSEC' : NSEC, - 'DNSKEY' : DNSKEY, - 'DHCID' : DHCID, - 'NSEC3' : NSEC3, - 'NSEC3PARAM' : NSEC3PARAM, - 'TLSA' : TLSA, - 'HIP' : HIP, - 'CDS' : CDS, - 'CDNSKEY' : CDNSKEY, - 'SPF' : SPF, - 'UNSPEC' : UNSPEC, + 'NONE': NONE, + 'A': A, + 'NS': NS, + 'MD': MD, + 'MF': MF, + 'CNAME': CNAME, + 'SOA': SOA, + 'MB': MB, + 'MG': MG, + 'MR': MR, + 'NULL': NULL, + 'WKS': WKS, + 'PTR': PTR, + 'HINFO': HINFO, + 'MINFO': MINFO, + 'MX': MX, + 'TXT': TXT, + 'RP': RP, + 'AFSDB': AFSDB, + 'X25': X25, + 'ISDN': ISDN, + 'RT': RT, + 'NSAP': NSAP, + 'NSAP-PTR': NSAP_PTR, + 'SIG': SIG, + 'KEY': KEY, + 'PX': PX, + 'GPOS': GPOS, + 'AAAA': AAAA, + 'LOC': LOC, + 'NXT': NXT, + 'SRV': SRV, + 'NAPTR': NAPTR, + 'KX': KX, + 'CERT': CERT, + 'A6': A6, + 'DNAME': DNAME, + 'OPT': OPT, + 'APL': APL, + 'DS': DS, + 'SSHFP': SSHFP, + 'IPSECKEY': IPSECKEY, + 'RRSIG': RRSIG, + 'NSEC': NSEC, + 'DNSKEY': DNSKEY, + 'DHCID': DHCID, + 'NSEC3': NSEC3, + 'NSEC3PARAM': NSEC3PARAM, + 'TLSA': TLSA, + 'HIP': HIP, + 'CDS': CDS, + 'CDNSKEY': CDNSKEY, + 'SPF': SPF, + 'UNSPEC': UNSPEC, 'EUI48': EUI48, 'EUI64': EUI64, - 'TKEY' : TKEY, - 'TSIG' : TSIG, - 'IXFR' : IXFR, - 'AXFR' : AXFR, - 'MAILB' : MAILB, - 'MAILA' : MAILA, - 'ANY' : ANY, - 'URI' : URI, - 'CAA' : CAA, - 'TA' : TA, - 'DLV' : DLV, - } + 'TKEY': TKEY, + 'TSIG': TSIG, + 'IXFR': IXFR, + 'AXFR': AXFR, + 'MAILB': MAILB, + 'MAILA': MAILA, + 'ANY': ANY, + 'URI': URI, + 'CAA': CAA, + 'TA': TA, + 'DLV': DLV, +} # We construct the inverse mapping programmatically to ensure that we # cannot make any mistakes (e.g. omissions, cut-and-paste errors) that # would cause the mapping not to be true inverse. -_by_value = dict([(y, x) for x, y in _by_text.iteritems()]) +_by_value = dict((y, x) for x, y in _by_text.items()) _metatypes = { - OPT : True - } + OPT: True +} _singletons = { - SOA : True, - NXT : True, - DNAME : True, - NSEC : True, + SOA: True, + NXT: True, + DNAME: True, + NSEC: True, # CNAME is technically a singleton, but we allow multiple CNAMEs. - } +} + +_unknown_type_pattern = re.compile('TYPE([0-9]+)$', re.I) -_unknown_type_pattern = re.compile('TYPE([0-9]+)$', re.I); class UnknownRdatatype(dns.exception.DNSException): + """DNS resource record type is unknown.""" + def from_text(text): """Convert text into a DNS rdata type value. @param text: the text @@ -203,13 +206,14 @@ def from_text(text): value = _by_text.get(text.upper()) if value is None: match = _unknown_type_pattern.match(text) - if match == None: + if match is None: raise UnknownRdatatype value = int(match.group(1)) if value < 0 or value > 65535: raise ValueError("type must be between >= 0 and <= 65535") return value + def to_text(value): """Convert a DNS rdata type to text. @param value: the rdata type value @@ -221,25 +225,27 @@ def to_text(value): raise ValueError("type must be between >= 0 and <= 65535") text = _by_value.get(value) if text is None: - text = 'TYPE' + `value` + text = 'TYPE' + repr(value) return text + def is_metatype(rdtype): """True if the type is a metatype. @param rdtype: the type @type rdtype: int @rtype: bool""" - if rdtype >= TKEY and rdtype <= ANY or _metatypes.has_key(rdtype): + if rdtype >= TKEY and rdtype <= ANY or rdtype in _metatypes: return True return False + def is_singleton(rdtype): """True if the type is a singleton. @param rdtype: the type @type rdtype: int @rtype: bool""" - if _singletons.has_key(rdtype): + if rdtype in _singletons: return True return False diff --git a/dns/rdtypes/ANY/AFSDB.py b/dns/rdtypes/ANY/AFSDB.py index c729789a..f3d51540 100644 --- a/dns/rdtypes/ANY/AFSDB.py +++ b/dns/rdtypes/ANY/AFSDB.py @@ -15,7 +15,9 @@ import dns.rdtypes.mxbase + class AFSDB(dns.rdtypes.mxbase.UncompressedDowncasingMX): + """AFSDB record @ivar subtype: the subtype value diff --git a/dns/rdtypes/ANY/CAA.py b/dns/rdtypes/ANY/CAA.py index 15e0f622..e80d4693 100644 --- a/dns/rdtypes/ANY/CAA.py +++ b/dns/rdtypes/ANY/CAA.py @@ -19,7 +19,9 @@ import dns.exception import dns.rdata import dns.tokenizer + class CAA(dns.rdata.Rdata): + """CAA (Certification Authority Authorization) record @ivar flags: the flags @@ -43,31 +45,30 @@ class CAA(dns.rdata.Rdata): dns.rdata._escapify(self.tag), dns.rdata._escapify(self.value)) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): flags = tok.get_uint8() - tag = tok.get_string() + tag = tok.get_string().encode() if len(tag) > 255: raise dns.exception.SyntaxError("tag too long") if not tag.isalnum(): raise dns.exception.SyntaxError("tag is not alphanumeric") - value = tok.get_string() + value = tok.get_string().encode() return cls(rdclass, rdtype, flags, tag, value) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): - file.write(chr(self.flags)) + def to_wire(self, file, compress=None, origin=None): + file.write(struct.pack('!B', self.flags)) l = len(self.tag) assert l < 256 - file.write(chr(l)) + file.write(struct.pack('!B', l)) file.write(self.tag) file.write(self.value) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): - (flags, l) = struct.unpack('!BB', wire[current : current + 2]) + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): + (flags, l) = struct.unpack('!BB', wire[current: current + 2]) current += 2 - tag = wire[current : current + l].unwrap() - value = wire[current + l:current + rdlen - 2].unwrap() + tag = wire[current: current + l] + value = wire[current + l:current + rdlen - 2] return cls(rdclass, rdtype, flags, tag, value) - from_wire = classmethod(from_wire) diff --git a/dns/rdtypes/ANY/CDNSKEY.py b/dns/rdtypes/ANY/CDNSKEY.py index 25cb3d8c..83f3d51f 100644 --- a/dns/rdtypes/ANY/CDNSKEY.py +++ b/dns/rdtypes/ANY/CDNSKEY.py @@ -14,7 +14,12 @@ # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. import dns.rdtypes.dnskeybase -from dns.rdtypes.dnskeybase import * +from dns.rdtypes.dnskeybase import flags_to_text_set, flags_from_text_set + + +__all__ = ['flags_to_text_set', 'flags_from_text_set'] + class CDNSKEY(dns.rdtypes.dnskeybase.DNSKEYBase): + """CDNSKEY record""" diff --git a/dns/rdtypes/ANY/CDS.py b/dns/rdtypes/ANY/CDS.py index 15fe012f..e1abfc36 100644 --- a/dns/rdtypes/ANY/CDS.py +++ b/dns/rdtypes/ANY/CDS.py @@ -15,5 +15,7 @@ import dns.rdtypes.dsbase + class CDS(dns.rdtypes.dsbase.DSBase): + """CDS record""" diff --git a/dns/rdtypes/ANY/CERT.py b/dns/rdtypes/ANY/CERT.py index df2ab07b..b7454409 100644 --- a/dns/rdtypes/ANY/CERT.py +++ b/dns/rdtypes/ANY/CERT.py @@ -13,8 +13,8 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -import cStringIO import struct +import base64 import dns.exception import dns.dnssec @@ -22,34 +22,38 @@ import dns.rdata import dns.tokenizer _ctype_by_value = { - 1 : 'PKIX', - 2 : 'SPKI', - 3 : 'PGP', - 253 : 'URI', - 254 : 'OID', - } + 1: 'PKIX', + 2: 'SPKI', + 3: 'PGP', + 253: 'URI', + 254: 'OID', +} _ctype_by_name = { - 'PKIX' : 1, - 'SPKI' : 2, - 'PGP' : 3, - 'URI' : 253, - 'OID' : 254, - } + 'PKIX': 1, + 'SPKI': 2, + 'PGP': 3, + 'URI': 253, + 'OID': 254, +} + def _ctype_from_text(what): v = _ctype_by_name.get(what) - if not v is None: + if v is not None: return v return int(what) + def _ctype_to_text(what): v = _ctype_by_value.get(what) - if not v is None: + if v is not None: return v return str(what) + class CERT(dns.rdata.Rdata): + """CERT record @ivar certificate_type: certificate type @@ -78,7 +82,8 @@ class CERT(dns.rdata.Rdata): dns.dnssec.algorithm_to_text(self.algorithm), dns.rdata._base64ify(self.certificate)) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): certificate_type = _ctype_from_text(tok.get_string()) key_tag = tok.get_uint16() algorithm = dns.dnssec.algorithm_from_text(tok.get_string()) @@ -91,29 +96,27 @@ class CERT(dns.rdata.Rdata): break if not t.is_identifier(): raise dns.exception.SyntaxError - chunks.append(t.value) - b64 = ''.join(chunks) - certificate = b64.decode('base64_codec') + chunks.append(t.value.encode()) + b64 = b''.join(chunks) + certificate = base64.b64decode(b64) return cls(rdclass, rdtype, certificate_type, key_tag, algorithm, certificate) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): prefix = struct.pack("!HHB", self.certificate_type, self.key_tag, self.algorithm) file.write(prefix) file.write(self.certificate) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): - prefix = wire[current : current + 5].unwrap() + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): + prefix = wire[current: current + 5].unwrap() current += 5 rdlen -= 5 if rdlen < 0: raise dns.exception.FormError (certificate_type, key_tag, algorithm) = struct.unpack("!HHB", prefix) - certificate = wire[current : current + rdlen].unwrap() + certificate = wire[current: current + rdlen].unwrap() return cls(rdclass, rdtype, certificate_type, key_tag, algorithm, certificate) - from_wire = classmethod(from_wire) diff --git a/dns/rdtypes/ANY/CNAME.py b/dns/rdtypes/ANY/CNAME.py index 6c6ab35c..65cf570c 100644 --- a/dns/rdtypes/ANY/CNAME.py +++ b/dns/rdtypes/ANY/CNAME.py @@ -15,7 +15,9 @@ import dns.rdtypes.nsbase + class CNAME(dns.rdtypes.nsbase.NSBase): + """CNAME record Note: although CNAME is officially a singleton type, dnspython allows diff --git a/dns/rdtypes/ANY/DLV.py b/dns/rdtypes/ANY/DLV.py index 2a360b97..cd1244c1 100644 --- a/dns/rdtypes/ANY/DLV.py +++ b/dns/rdtypes/ANY/DLV.py @@ -15,5 +15,7 @@ import dns.rdtypes.dsbase + class DLV(dns.rdtypes.dsbase.DSBase): + """DLV record""" diff --git a/dns/rdtypes/ANY/DNAME.py b/dns/rdtypes/ANY/DNAME.py index d8640011..dac97214 100644 --- a/dns/rdtypes/ANY/DNAME.py +++ b/dns/rdtypes/ANY/DNAME.py @@ -15,7 +15,10 @@ import dns.rdtypes.nsbase + class DNAME(dns.rdtypes.nsbase.UncompressedNS): + """DNAME record""" - def to_digestable(self, origin = None): + + def to_digestable(self, origin=None): return self.target.to_digestable(origin) diff --git a/dns/rdtypes/ANY/DNSKEY.py b/dns/rdtypes/ANY/DNSKEY.py index cc64b6cc..e915e98b 100644 --- a/dns/rdtypes/ANY/DNSKEY.py +++ b/dns/rdtypes/ANY/DNSKEY.py @@ -14,7 +14,12 @@ # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. import dns.rdtypes.dnskeybase -from dns.rdtypes.dnskeybase import * +from dns.rdtypes.dnskeybase import flags_to_text_set, flags_from_text_set + + +__all__ = ['flags_to_text_set', 'flags_from_text_set'] + class DNSKEY(dns.rdtypes.dnskeybase.DNSKEYBase): + """DNSKEY record""" diff --git a/dns/rdtypes/ANY/DS.py b/dns/rdtypes/ANY/DS.py index 6c669ccc..577c8d84 100644 --- a/dns/rdtypes/ANY/DS.py +++ b/dns/rdtypes/ANY/DS.py @@ -15,5 +15,7 @@ import dns.rdtypes.dsbase + class DS(dns.rdtypes.dsbase.DSBase): + """DS record""" diff --git a/dns/rdtypes/ANY/EUI48.py b/dns/rdtypes/ANY/EUI48.py index 6ddfa6a4..aa260e20 100644 --- a/dns/rdtypes/ANY/EUI48.py +++ b/dns/rdtypes/ANY/EUI48.py @@ -18,6 +18,7 @@ import dns.rdtypes.euibase class EUI48(dns.rdtypes.euibase.EUIBase): + """EUI48 record @ivar fingerprint: 48-bit Extended Unique Identifier (EUI-48) diff --git a/dns/rdtypes/ANY/EUI64.py b/dns/rdtypes/ANY/EUI64.py index 41abd7b7..5eba350d 100644 --- a/dns/rdtypes/ANY/EUI64.py +++ b/dns/rdtypes/ANY/EUI64.py @@ -18,6 +18,7 @@ import dns.rdtypes.euibase class EUI64(dns.rdtypes.euibase.EUIBase): + """EUI64 record @ivar fingerprint: 64-bit Extended Unique Identifier (EUI-64) diff --git a/dns/rdtypes/ANY/GPOS.py b/dns/rdtypes/ANY/GPOS.py index 39994811..a359a771 100644 --- a/dns/rdtypes/ANY/GPOS.py +++ b/dns/rdtypes/ANY/GPOS.py @@ -13,24 +13,36 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +import struct + import dns.exception import dns.rdata import dns.tokenizer +from dns._compat import long, text_type + def _validate_float_string(what): - if what[0] == '-' or what[0] == '+': + if what[0] == b'-'[0] or what[0] == b'+'[0]: what = what[1:] if what.isdigit(): return - (left, right) = what.split('.') - if left == '' and right == '': + (left, right) = what.split(b'.') + if left == b'' and right == b'': raise dns.exception.FormError - if not left == '' and not left.isdigit(): + if not left == b'' and not left.decode().isdigit(): raise dns.exception.FormError - if not right == '' and not right.isdigit(): + if not right == b'' and not right.decode().isdigit(): raise dns.exception.FormError + +def _sanitize(value): + if isinstance(value, text_type): + return value.encode() + return value + + class GPOS(dns.rdata.Rdata): + """GPOS record @ivar latitude: latitude @@ -57,6 +69,9 @@ class GPOS(dns.rdata.Rdata): isinstance(altitude, int) or \ isinstance(altitude, long): altitude = str(altitude) + latitude = _sanitize(latitude) + longitude = _sanitize(longitude) + altitude = _sanitize(altitude) _validate_float_string(latitude) _validate_float_string(longitude) _validate_float_string(altitude) @@ -65,61 +80,58 @@ class GPOS(dns.rdata.Rdata): self.altitude = altitude def to_text(self, origin=None, relativize=True, **kw): - return '%s %s %s' % (self.latitude, self.longitude, self.altitude) + return '%s %s %s' % (self.latitude.decode(), + self.longitude.decode(), + self.altitude.decode()) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): latitude = tok.get_string() longitude = tok.get_string() altitude = tok.get_string() tok.get_eol() return cls(rdclass, rdtype, latitude, longitude, altitude) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): l = len(self.latitude) assert l < 256 - byte = chr(l) - file.write(byte) + file.write(struct.pack('!B', l)) file.write(self.latitude) l = len(self.longitude) assert l < 256 - byte = chr(l) - file.write(byte) + file.write(struct.pack('!B', l)) file.write(self.longitude) l = len(self.altitude) assert l < 256 - byte = chr(l) - file.write(byte) + file.write(struct.pack('!B', l)) file.write(self.altitude) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): - l = ord(wire[current]) + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): + l = wire[current] current += 1 rdlen -= 1 if l > rdlen: raise dns.exception.FormError - latitude = wire[current : current + l].unwrap() + latitude = wire[current: current + l].unwrap() current += l rdlen -= l - l = ord(wire[current]) + l = wire[current] current += 1 rdlen -= 1 if l > rdlen: raise dns.exception.FormError - longitude = wire[current : current + l].unwrap() + longitude = wire[current: current + l].unwrap() current += l rdlen -= l - l = ord(wire[current]) + l = wire[current] current += 1 rdlen -= 1 if l != rdlen: raise dns.exception.FormError - altitude = wire[current : current + l].unwrap() + altitude = wire[current: current + l].unwrap() return cls(rdclass, rdtype, latitude, longitude, altitude) - from_wire = classmethod(from_wire) - def _get_float_latitude(self): return float(self.latitude) diff --git a/dns/rdtypes/ANY/HINFO.py b/dns/rdtypes/ANY/HINFO.py index 4fe7a0f9..52298bc4 100644 --- a/dns/rdtypes/ANY/HINFO.py +++ b/dns/rdtypes/ANY/HINFO.py @@ -13,11 +13,16 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +import struct + import dns.exception import dns.rdata import dns.tokenizer +from dns._compat import text_type + class HINFO(dns.rdata.Rdata): + """HINFO record @ivar cpu: the CPU type @@ -30,48 +35,51 @@ class HINFO(dns.rdata.Rdata): def __init__(self, rdclass, rdtype, cpu, os): super(HINFO, self).__init__(rdclass, rdtype) - self.cpu = cpu - self.os = os + if isinstance(cpu, text_type): + self.cpu = cpu.encode() + else: + self.cpu = cpu + if isinstance(os, text_type): + self.os = os.encode() + else: + self.os = os def to_text(self, origin=None, relativize=True, **kw): return '"%s" "%s"' % (dns.rdata._escapify(self.cpu), dns.rdata._escapify(self.os)) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): cpu = tok.get_string() os = tok.get_string() tok.get_eol() return cls(rdclass, rdtype, cpu, os) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): l = len(self.cpu) assert l < 256 - byte = chr(l) - file.write(byte) + file.write(struct.pack('!B', l)) file.write(self.cpu) l = len(self.os) assert l < 256 - byte = chr(l) - file.write(byte) + file.write(struct.pack('!B', l)) file.write(self.os) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): - l = ord(wire[current]) + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): + l = wire[current] current += 1 rdlen -= 1 if l > rdlen: raise dns.exception.FormError - cpu = wire[current : current + l].unwrap() + cpu = wire[current:current + l].unwrap() current += l rdlen -= l - l = ord(wire[current]) + l = wire[current] current += 1 rdlen -= 1 if l != rdlen: raise dns.exception.FormError - os = wire[current : current + l].unwrap() + os = wire[current: current + l].unwrap() return cls(rdclass, rdtype, cpu, os) - from_wire = classmethod(from_wire) diff --git a/dns/rdtypes/ANY/HIP.py b/dns/rdtypes/ANY/HIP.py index 654d5c79..e0cd2755 100644 --- a/dns/rdtypes/ANY/HIP.py +++ b/dns/rdtypes/ANY/HIP.py @@ -13,15 +13,17 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -import cStringIO -import string import struct +import base64 +import binascii import dns.exception import dns.rdata import dns.rdatatype + class HIP(dns.rdata.Rdata): + """HIP record @ivar hit: the host identity tag @@ -44,22 +46,23 @@ class HIP(dns.rdata.Rdata): self.servers = servers def to_text(self, origin=None, relativize=True, **kw): - hit = self.hit.encode('hex-codec') - key = self.key.encode('base64-codec').replace('\n', '') - text = '' + hit = binascii.hexlify(self.hit).decode() + key = base64.b64encode(self.key).replace(b'\n', b'').decode() + text = u'' servers = [] for server in self.servers: - servers.append(str(server.choose_relativity(origin, relativize))) + servers.append(server.choose_relativity(origin, relativize)) if len(servers) > 0: - text += (' ' + ' '.join(servers)) - return '%u %s %s%s' % (self.algorithm, hit, key, text) + text += (u' ' + u' '.join(map(lambda x: x.to_unicode(), servers))) + return u'%u %s %s%s' % (self.algorithm, hit, key, text) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): algorithm = tok.get_uint8() - hit = tok.get_string().decode('hex-codec') + hit = binascii.unhexlify(tok.get_string().encode()) if len(hit) > 255: raise dns.exception.SyntaxError("HIT too long") - key = tok.get_string().decode('base64-codec') + key = base64.b64decode(tok.get_string().encode()) servers = [] while 1: token = tok.get() @@ -70,9 +73,7 @@ class HIP(dns.rdata.Rdata): servers.append(server) return cls(rdclass, rdtype, hit, algorithm, key, servers) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): lh = len(self.hit) lk = len(self.key) file.write(struct.pack("!BBH", lh, self.algorithm, lk)) @@ -81,15 +82,16 @@ class HIP(dns.rdata.Rdata): for server in self.servers: server.to_wire(file, None, origin) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): (lh, algorithm, lk) = struct.unpack('!BBH', - wire[current : current + 4]) + wire[current: current + 4]) current += 4 rdlen -= 4 - hit = wire[current : current + lh].unwrap() + hit = wire[current: current + lh].unwrap() current += lh rdlen -= lh - key = wire[current : current + lk].unwrap() + key = wire[current: current + lk].unwrap() current += lk rdlen -= lk servers = [] @@ -98,14 +100,12 @@ class HIP(dns.rdata.Rdata): current) current += cused rdlen -= cused - if not origin is None: + if origin is not None: server = server.relativize(origin) servers.append(server) return cls(rdclass, rdtype, hit, algorithm, key, servers) - from_wire = classmethod(from_wire) - - def choose_relativity(self, origin = None, relativize = True): + def choose_relativity(self, origin=None, relativize=True): servers = [] for server in self.servers: server = server.choose_relativity(origin, relativize) diff --git a/dns/rdtypes/ANY/ISDN.py b/dns/rdtypes/ANY/ISDN.py index 0a5c01d5..01284a82 100644 --- a/dns/rdtypes/ANY/ISDN.py +++ b/dns/rdtypes/ANY/ISDN.py @@ -13,11 +13,16 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +import struct + import dns.exception import dns.rdata import dns.tokenizer +from dns._compat import text_type + class ISDN(dns.rdata.Rdata): + """ISDN record @ivar address: the ISDN address @@ -30,8 +35,14 @@ class ISDN(dns.rdata.Rdata): def __init__(self, rdclass, rdtype, address, subaddress): super(ISDN, self).__init__(rdclass, rdtype) - self.address = address - self.subaddress = subaddress + if isinstance(address, text_type): + self.address = address.encode() + else: + self.address = address + if isinstance(address, text_type): + self.subaddress = subaddress.encode() + else: + self.subaddress = subaddress def to_text(self, origin=None, relativize=True, **kw): if self.subaddress: @@ -40,7 +51,8 @@ class ISDN(dns.rdata.Rdata): else: return '"%s"' % dns.rdata._escapify(self.address) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): address = tok.get_string() t = tok.get() if not t.is_eol_or_eof(): @@ -52,39 +64,35 @@ class ISDN(dns.rdata.Rdata): tok.get_eol() return cls(rdclass, rdtype, address, subaddress) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): l = len(self.address) assert l < 256 - byte = chr(l) - file.write(byte) + file.write(struct.pack('!B', l)) file.write(self.address) l = len(self.subaddress) if l > 0: assert l < 256 - byte = chr(l) - file.write(byte) + file.write(struct.pack('!B', l)) file.write(self.subaddress) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): - l = ord(wire[current]) + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): + l = wire[current] current += 1 rdlen -= 1 if l > rdlen: raise dns.exception.FormError - address = wire[current : current + l].unwrap() + address = wire[current: current + l].unwrap() current += l rdlen -= l if rdlen > 0: - l = ord(wire[current]) + l = wire[current] current += 1 rdlen -= 1 if l != rdlen: raise dns.exception.FormError - subaddress = wire[current : current + l].unwrap() + subaddress = wire[current: current + l].unwrap() else: subaddress = '' return cls(rdclass, rdtype, address, subaddress) - from_wire = classmethod(from_wire) diff --git a/dns/rdtypes/ANY/LOC.py b/dns/rdtypes/ANY/LOC.py index 73bf79ef..a4655d88 100644 --- a/dns/rdtypes/ANY/LOC.py +++ b/dns/rdtypes/ANY/LOC.py @@ -13,32 +13,34 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -import cStringIO import struct import dns.exception import dns.rdata +from dns._compat import long, xrange -_pows = (1L, 10L, 100L, 1000L, 10000L, 100000L, 1000000L, 10000000L, - 100000000L, 1000000000L, 10000000000L) + +_pows = tuple(long(10**i) for i in range(0, 11)) # default values are in centimeters _default_size = 100.0 _default_hprec = 1000000.0 _default_vprec = 1000.0 + def _exponent_of(what, desc): if what == 0: return 0 exp = None for i in xrange(len(_pows)): - if what // _pows[i] == 0L: + if what // _pows[i] == long(0): exp = i - 1 break if exp is None or exp < 0: raise dns.exception.SyntaxError("%s value out of bounds" % desc) return exp + def _float_to_tuple(what): if what < 0: sign = -1 @@ -55,6 +57,7 @@ def _float_to_tuple(what): what = int(what) return (degrees * sign, minutes, seconds, what) + def _tuple_to_float(what): if what[0] < 0: sign = -1 @@ -67,12 +70,14 @@ def _tuple_to_float(what): value += float(what[3]) / 3600000.0 return sign * value + def _encode_size(what, desc): - what = long(what); + what = long(what) exponent = _exponent_of(what, desc) & 0xF base = what // pow(10, exponent) & 0xF return base * 16 + exponent + def _decode_size(what, desc): exponent = what & 0x0F if exponent > 9: @@ -82,7 +87,9 @@ def _decode_size(what, desc): raise dns.exception.SyntaxError("bad %s base" % desc) return long(base) * pow(10, exponent) + class LOC(dns.rdata.Rdata): + """LOC record @ivar latitude: latitude @@ -105,7 +112,8 @@ class LOC(dns.rdata.Rdata): 'horizontal_precision', 'vertical_precision'] def __init__(self, rdclass, rdtype, latitude, longitude, altitude, - size=_default_size, hprec=_default_hprec, vprec=_default_vprec): + size=_default_size, hprec=_default_hprec, + vprec=_default_vprec): """Initialize a LOC record instance. The parameters I{latitude} and I{longitude} may be either a 4-tuple @@ -147,19 +155,20 @@ class LOC(dns.rdata.Rdata): lat_degrees, self.latitude[1], self.latitude[2], self.latitude[3], lat_hemisphere, long_degrees, self.longitude[1], self.longitude[2], self.longitude[3], long_hemisphere, self.altitude / 100.0 - ) + ) # do not print default values if self.size != _default_size or \ self.horizontal_precision != _default_hprec or \ - self.vertical_precision != _default_vprec: + self.vertical_precision != _default_vprec: text += " %0.2fm %0.2fm %0.2fm" % ( self.size / 100.0, self.horizontal_precision / 100.0, self.vertical_precision / 100.0 ) return text - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): latitude = [0, 0, 0, 0] longitude = [0, 0, 0, 0] size = _default_size @@ -174,13 +183,15 @@ class LOC(dns.rdata.Rdata): if '.' in t: (seconds, milliseconds) = t.split('.') if not seconds.isdigit(): - raise dns.exception.SyntaxError('bad latitude seconds value') + raise dns.exception.SyntaxError( + 'bad latitude seconds value') latitude[2] = int(seconds) if latitude[2] >= 60: raise dns.exception.SyntaxError('latitude seconds >= 60') l = len(milliseconds) if l == 0 or l > 3 or not milliseconds.isdigit(): - raise dns.exception.SyntaxError('bad latitude milliseconds value') + raise dns.exception.SyntaxError( + 'bad latitude milliseconds value') if l == 1: m = 100 elif l == 2: @@ -205,13 +216,15 @@ class LOC(dns.rdata.Rdata): if '.' in t: (seconds, milliseconds) = t.split('.') if not seconds.isdigit(): - raise dns.exception.SyntaxError('bad longitude seconds value') + raise dns.exception.SyntaxError( + 'bad longitude seconds value') longitude[2] = int(seconds) if longitude[2] >= 60: raise dns.exception.SyntaxError('longitude seconds >= 60') l = len(milliseconds) if l == 0 or l > 3 or not milliseconds.isdigit(): - raise dns.exception.SyntaxError('bad longitude milliseconds value') + raise dns.exception.SyntaxError( + 'bad longitude milliseconds value') if l == 1: m = 100 elif l == 2: @@ -230,35 +243,33 @@ class LOC(dns.rdata.Rdata): t = tok.get_string() if t[-1] == 'm': - t = t[0 : -1] + t = t[0: -1] altitude = float(t) * 100.0 # m -> cm token = tok.get().unescape() if not token.is_eol_or_eof(): value = token.value if value[-1] == 'm': - value = value[0 : -1] + value = value[0: -1] size = float(value) * 100.0 # m -> cm token = tok.get().unescape() if not token.is_eol_or_eof(): value = token.value if value[-1] == 'm': - value = value[0 : -1] + value = value[0: -1] hprec = float(value) * 100.0 # m -> cm token = tok.get().unescape() if not token.is_eol_or_eof(): value = token.value if value[-1] == 'm': - value = value[0 : -1] + value = value[0: -1] vprec = float(value) * 100.0 # m -> cm tok.get_eol() return cls(rdclass, rdtype, latitude, longitude, altitude, size, hprec, vprec) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): if self.latitude[0] < 0: sign = -1 degrees = long(-1 * self.latitude[0]) @@ -269,7 +280,7 @@ class LOC(dns.rdata.Rdata): self.latitude[1] * 60000 + self.latitude[2] * 1000 + self.latitude[3]) * sign - latitude = 0x80000000L + milliseconds + latitude = long(0x80000000) + milliseconds if self.longitude[0] < 0: sign = -1 degrees = long(-1 * self.longitude[0]) @@ -280,8 +291,8 @@ class LOC(dns.rdata.Rdata): self.longitude[1] * 60000 + self.longitude[2] * 1000 + self.longitude[3]) * sign - longitude = 0x80000000L + milliseconds - altitude = long(self.altitude) + 10000000L + longitude = long(0x80000000) + milliseconds + altitude = long(self.altitude) + long(10000000) size = _encode_size(self.size, "size") hprec = _encode_size(self.horizontal_precision, "horizontal precision") vprec = _encode_size(self.vertical_precision, "vertical precision") @@ -289,19 +300,20 @@ class LOC(dns.rdata.Rdata): longitude, altitude) file.write(wire) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): (version, size, hprec, vprec, latitude, longitude, altitude) = \ - struct.unpack("!BBBBIII", wire[current : current + rdlen]) - if latitude > 0x80000000L: - latitude = float(latitude - 0x80000000L) / 3600000 + struct.unpack("!BBBBIII", wire[current: current + rdlen]) + if latitude > long(0x80000000): + latitude = float(latitude - long(0x80000000)) / 3600000 else: - latitude = -1 * float(0x80000000L - latitude) / 3600000 + latitude = -1 * float(long(0x80000000) - latitude) / 3600000 if latitude < -90.0 or latitude > 90.0: raise dns.exception.FormError("bad latitude") - if longitude > 0x80000000L: - longitude = float(longitude - 0x80000000L) / 3600000 + if longitude > long(0x80000000): + longitude = float(longitude - long(0x80000000)) / 3600000 else: - longitude = -1 * float(0x80000000L - longitude) / 3600000 + longitude = -1 * float(long(0x80000000) - longitude) / 3600000 if longitude < -180.0 or longitude > 180.0: raise dns.exception.FormError("bad longitude") altitude = float(altitude) - 10000000.0 @@ -311,8 +323,6 @@ class LOC(dns.rdata.Rdata): return cls(rdclass, rdtype, latitude, longitude, altitude, size, hprec, vprec) - from_wire = classmethod(from_wire) - def _get_float_latitude(self): return _tuple_to_float(self.latitude) diff --git a/dns/rdtypes/ANY/MX.py b/dns/rdtypes/ANY/MX.py index c407c11a..3a6735dc 100644 --- a/dns/rdtypes/ANY/MX.py +++ b/dns/rdtypes/ANY/MX.py @@ -15,5 +15,7 @@ import dns.rdtypes.mxbase + class MX(dns.rdtypes.mxbase.MXBase): + """MX record""" diff --git a/dns/rdtypes/ANY/NS.py b/dns/rdtypes/ANY/NS.py index c3d02349..ae56d819 100644 --- a/dns/rdtypes/ANY/NS.py +++ b/dns/rdtypes/ANY/NS.py @@ -15,5 +15,7 @@ import dns.rdtypes.nsbase + class NS(dns.rdtypes.nsbase.NSBase): + """NS record""" diff --git a/dns/rdtypes/ANY/NSEC.py b/dns/rdtypes/ANY/NSEC.py index fd76cc43..dfe96859 100644 --- a/dns/rdtypes/ANY/NSEC.py +++ b/dns/rdtypes/ANY/NSEC.py @@ -13,14 +13,17 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -import cStringIO +import struct import dns.exception import dns.rdata import dns.rdatatype import dns.name +from dns._compat import xrange + class NSEC(dns.rdata.Rdata): + """NSEC record @ivar next: the next name @@ -41,15 +44,16 @@ class NSEC(dns.rdata.Rdata): for (window, bitmap) in self.windows: bits = [] for i in xrange(0, len(bitmap)): - byte = ord(bitmap[i]) + byte = bitmap[i] for j in xrange(0, 8): if byte & (0x80 >> j): - bits.append(dns.rdatatype.to_text(window * 256 + \ + bits.append(dns.rdatatype.to_text(window * 256 + i * 8 + j)) text += (' ' + ' '.join(bits)) return '%s%s' % (next, text) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): next = tok.get_name() next = next.choose_relativity(origin, relativize) rdtypes = [] @@ -67,7 +71,7 @@ class NSEC(dns.rdata.Rdata): window = 0 octets = 0 prior_rdtype = 0 - bitmap = ['\0'] * 32 + bitmap = bytearray(b'\0' * 32) windows = [] for nrdtype in rdtypes: if nrdtype == prior_rdtype: @@ -75,27 +79,26 @@ class NSEC(dns.rdata.Rdata): prior_rdtype = nrdtype new_window = nrdtype // 256 if new_window != window: - windows.append((window, ''.join(bitmap[0:octets]))) - bitmap = ['\0'] * 32 + windows.append((window, bitmap[0:octets])) + bitmap = bytearray(b'\0' * 32) window = new_window offset = nrdtype % 256 byte = offset // 8 bit = offset % 8 octets = byte + 1 - bitmap[byte] = chr(ord(bitmap[byte]) | (0x80 >> bit)) - windows.append((window, ''.join(bitmap[0:octets]))) - return cls(rdclass, rdtype, next, windows) + bitmap[byte] = bitmap[byte] | (0x80 >> bit) - from_text = classmethod(from_text) + windows.append((window, bitmap[0:octets])) + return cls(rdclass, rdtype, next, windows) - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): self.next.to_wire(file, None, origin) for (window, bitmap) in self.windows: - file.write(chr(window)) - file.write(chr(len(bitmap))) + file.write(struct.pack('!BB', window, len(bitmap))) file.write(bitmap) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): (next, cused) = dns.name.from_wire(wire[: current + rdlen], current) current += cused rdlen -= cused @@ -103,23 +106,21 @@ class NSEC(dns.rdata.Rdata): while rdlen > 0: if rdlen < 3: raise dns.exception.FormError("NSEC too short") - window = ord(wire[current]) - octets = ord(wire[current + 1]) + window = wire[current] + octets = wire[current + 1] if octets == 0 or octets > 32: raise dns.exception.FormError("bad NSEC octets") current += 2 rdlen -= 2 if rdlen < octets: raise dns.exception.FormError("bad NSEC bitmap length") - bitmap = wire[current : current + octets].unwrap() + bitmap = bytearray(wire[current: current + octets].unwrap()) current += octets rdlen -= octets windows.append((window, bitmap)) - if not origin is None: + if origin is not None: next = next.relativize(origin) return cls(rdclass, rdtype, next, windows) - from_wire = classmethod(from_wire) - - def choose_relativity(self, origin = None, relativize = True): + def choose_relativity(self, origin=None, relativize=True): self.next = self.next.choose_relativity(origin, relativize) diff --git a/dns/rdtypes/ANY/NSEC3.py b/dns/rdtypes/ANY/NSEC3.py index 1ed391dc..3982f4b4 100644 --- a/dns/rdtypes/ANY/NSEC3.py +++ b/dns/rdtypes/ANY/NSEC3.py @@ -14,18 +14,25 @@ # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. import base64 -import cStringIO +import binascii import string import struct import dns.exception import dns.rdata import dns.rdatatype +from dns._compat import xrange, text_type -b32_hex_to_normal = string.maketrans('0123456789ABCDEFGHIJKLMNOPQRSTUV', - 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567') -b32_normal_to_hex = string.maketrans('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', - '0123456789ABCDEFGHIJKLMNOPQRSTUV') +try: + b32_hex_to_normal = string.maketrans('0123456789ABCDEFGHIJKLMNOPQRSTUV', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567') + b32_normal_to_hex = string.maketrans('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', + '0123456789ABCDEFGHIJKLMNOPQRSTUV') +except AttributeError: + b32_hex_to_normal = bytes.maketrans(b'0123456789ABCDEFGHIJKLMNOPQRSTUV', + b'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567') + b32_normal_to_hex = bytes.maketrans(b'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', + b'0123456789ABCDEFGHIJKLMNOPQRSTUV') # hash algorithm constants SHA1 = 1 @@ -33,7 +40,9 @@ SHA1 = 1 # flag constants OPTOUT = 1 + class NSEC3(dns.rdata.Rdata): + """NSEC3 record @ivar algorithm: the hash algorithm number @@ -57,39 +66,45 @@ class NSEC3(dns.rdata.Rdata): self.algorithm = algorithm self.flags = flags self.iterations = iterations - self.salt = salt + if isinstance(salt, text_type): + self.salt = salt.encode() + else: + self.salt = salt self.next = next self.windows = windows def to_text(self, origin=None, relativize=True, **kw): - next = base64.b32encode(self.next).translate(b32_normal_to_hex).lower() - if self.salt == '': + next = base64.b32encode(self.next).translate( + b32_normal_to_hex).lower().decode() + if self.salt == b'': salt = '-' else: - salt = self.salt.encode('hex-codec') - text = '' + salt = binascii.hexlify(self.salt).decode() + text = u'' for (window, bitmap) in self.windows: bits = [] for i in xrange(0, len(bitmap)): - byte = ord(bitmap[i]) + byte = bitmap[i] for j in xrange(0, 8): if byte & (0x80 >> j): - bits.append(dns.rdatatype.to_text(window * 256 + \ + bits.append(dns.rdatatype.to_text(window * 256 + i * 8 + j)) - text += (' ' + ' '.join(bits)) - return '%u %u %u %s %s%s' % (self.algorithm, self.flags, self.iterations, - salt, next, text) + text += (u' ' + u' '.join(bits)) + return u'%u %u %u %s %s%s' % (self.algorithm, self.flags, + self.iterations, salt, next, text) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): algorithm = tok.get_uint8() flags = tok.get_uint8() iterations = tok.get_uint16() salt = tok.get_string() - if salt == '-': - salt = '' + if salt == u'-': + salt = b'' else: - salt = salt.decode('hex-codec') - next = tok.get_string().upper().translate(b32_hex_to_normal) + salt = binascii.unhexlify(salt.encode('ascii')) + next = tok.get_string().encode( + 'ascii').upper().translate(b32_hex_to_normal) next = base64.b32decode(next) rdtypes = [] while 1: @@ -106,7 +121,7 @@ class NSEC3(dns.rdata.Rdata): window = 0 octets = 0 prior_rdtype = 0 - bitmap = ['\0'] * 32 + bitmap = bytearray(b'\0' * 32) windows = [] for nrdtype in rdtypes: if nrdtype == prior_rdtype: @@ -116,20 +131,19 @@ class NSEC3(dns.rdata.Rdata): if new_window != window: if octets != 0: windows.append((window, ''.join(bitmap[0:octets]))) - bitmap = ['\0'] * 32 + bitmap = bytearray(b'\0' * 32) window = new_window offset = nrdtype % 256 byte = offset // 8 bit = offset % 8 octets = byte + 1 - bitmap[byte] = chr(ord(bitmap[byte]) | (0x80 >> bit)) + bitmap[byte] = bitmap[byte] | (0x80 >> bit) if octets != 0: - windows.append((window, ''.join(bitmap[0:octets]))) - return cls(rdclass, rdtype, algorithm, flags, iterations, salt, next, windows) + windows.append((window, bitmap[0:octets])) + return cls(rdclass, rdtype, algorithm, flags, iterations, salt, next, + windows) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): l = len(self.salt) file.write(struct.pack("!BBHB", self.algorithm, self.flags, self.iterations, l)) @@ -138,40 +152,41 @@ class NSEC3(dns.rdata.Rdata): file.write(struct.pack("!B", l)) file.write(self.next) for (window, bitmap) in self.windows: - file.write(chr(window)) - file.write(chr(len(bitmap))) + file.write(struct.pack("!BB", window, len(bitmap))) file.write(bitmap) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): - (algorithm, flags, iterations, slen) = struct.unpack('!BBHB', - wire[current : current + 5]) + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): + (algorithm, flags, iterations, slen) = \ + struct.unpack('!BBHB', wire[current: current + 5]) + current += 5 rdlen -= 5 - salt = wire[current : current + slen].unwrap() + salt = wire[current: current + slen].unwrap() current += slen rdlen -= slen - (nlen, ) = struct.unpack('!B', wire[current]) + nlen = wire[current] current += 1 rdlen -= 1 - next = wire[current : current + nlen].unwrap() + next = wire[current: current + nlen].unwrap() current += nlen rdlen -= nlen windows = [] while rdlen > 0: if rdlen < 3: raise dns.exception.FormError("NSEC3 too short") - window = ord(wire[current]) - octets = ord(wire[current + 1]) + window = wire[current] + octets = wire[current + 1] if octets == 0 or octets > 32: raise dns.exception.FormError("bad NSEC3 octets") current += 2 rdlen -= 2 if rdlen < octets: raise dns.exception.FormError("bad NSEC3 bitmap length") - bitmap = wire[current : current + octets].unwrap() + bitmap = bytearray(wire[current: current + octets].unwrap()) current += octets rdlen -= octets windows.append((window, bitmap)) - return cls(rdclass, rdtype, algorithm, flags, iterations, salt, next, windows) + return cls(rdclass, rdtype, algorithm, flags, iterations, salt, next, + windows) - from_wire = classmethod(from_wire) diff --git a/dns/rdtypes/ANY/NSEC3PARAM.py b/dns/rdtypes/ANY/NSEC3PARAM.py index e12b04fc..b506282b 100644 --- a/dns/rdtypes/ANY/NSEC3PARAM.py +++ b/dns/rdtypes/ANY/NSEC3PARAM.py @@ -13,13 +13,16 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -import cStringIO import struct +import binascii import dns.exception import dns.rdata +from dns._compat import text_type + class NSEC3PARAM(dns.rdata.Rdata): + """NSEC3PARAM record @ivar algorithm: the hash algorithm number @@ -38,16 +41,21 @@ class NSEC3PARAM(dns.rdata.Rdata): self.algorithm = algorithm self.flags = flags self.iterations = iterations - self.salt = salt + if isinstance(salt, text_type): + self.salt = salt.encode() + else: + self.salt = salt def to_text(self, origin=None, relativize=True, **kw): - if self.salt == '': + if self.salt == b'': salt = '-' else: - salt = self.salt.encode('hex-codec') - return '%u %u %u %s' % (self.algorithm, self.flags, self.iterations, salt) + salt = binascii.hexlify(self.salt).decode() + return '%u %u %u %s' % (self.algorithm, self.flags, self.iterations, + salt) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): algorithm = tok.get_uint8() flags = tok.get_uint8() iterations = tok.get_uint16() @@ -55,28 +63,27 @@ class NSEC3PARAM(dns.rdata.Rdata): if salt == '-': salt = '' else: - salt = salt.decode('hex-codec') + salt = binascii.unhexlify(salt.encode()) tok.get_eol() return cls(rdclass, rdtype, algorithm, flags, iterations, salt) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): l = len(self.salt) file.write(struct.pack("!BBHB", self.algorithm, self.flags, self.iterations, l)) file.write(self.salt) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): - (algorithm, flags, iterations, slen) = struct.unpack('!BBHB', - wire[current : current + 5]) + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): + (algorithm, flags, iterations, slen) = \ + struct.unpack('!BBHB', + wire[current: current + 5]) current += 5 rdlen -= 5 - salt = wire[current : current + slen].unwrap() + salt = wire[current: current + slen].unwrap() current += slen rdlen -= slen if rdlen != 0: raise dns.exception.FormError return cls(rdclass, rdtype, algorithm, flags, iterations, salt) - from_wire = classmethod(from_wire) diff --git a/dns/rdtypes/ANY/PTR.py b/dns/rdtypes/ANY/PTR.py index 963de63a..250187a6 100644 --- a/dns/rdtypes/ANY/PTR.py +++ b/dns/rdtypes/ANY/PTR.py @@ -15,5 +15,7 @@ import dns.rdtypes.nsbase + class PTR(dns.rdtypes.nsbase.NSBase): + """PTR record""" diff --git a/dns/rdtypes/ANY/RP.py b/dns/rdtypes/ANY/RP.py index cb6b7c8d..e9071c76 100644 --- a/dns/rdtypes/ANY/RP.py +++ b/dns/rdtypes/ANY/RP.py @@ -17,7 +17,9 @@ import dns.exception import dns.rdata import dns.name + class RP(dns.rdata.Rdata): + """RP record @ivar mbox: The responsible person's mailbox @@ -39,7 +41,8 @@ class RP(dns.rdata.Rdata): txt = self.txt.choose_relativity(origin, relativize) return "%s %s" % (str(mbox), str(txt)) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): mbox = tok.get_name() txt = tok.get_name() mbox = mbox.choose_relativity(origin, relativize) @@ -47,17 +50,16 @@ class RP(dns.rdata.Rdata): tok.get_eol() return cls(rdclass, rdtype, mbox, txt) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): self.mbox.to_wire(file, None, origin) self.txt.to_wire(file, None, origin) - def to_digestable(self, origin = None): + def to_digestable(self, origin=None): return self.mbox.to_digestable(origin) + \ self.txt.to_digestable(origin) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): (mbox, cused) = dns.name.from_wire(wire[: current + rdlen], current) current += cused @@ -68,13 +70,11 @@ class RP(dns.rdata.Rdata): current) if cused != rdlen: raise dns.exception.FormError - if not origin is None: + if origin is not None: mbox = mbox.relativize(origin) txt = txt.relativize(origin) return cls(rdclass, rdtype, mbox, txt) - from_wire = classmethod(from_wire) - - def choose_relativity(self, origin = None, relativize = True): + def choose_relativity(self, origin=None, relativize=True): self.mbox = self.mbox.choose_relativity(origin, relativize) self.txt = self.txt.choose_relativity(origin, relativize) diff --git a/dns/rdtypes/ANY/RRSIG.py b/dns/rdtypes/ANY/RRSIG.py index 7676a55d..953dfb9a 100644 --- a/dns/rdtypes/ANY/RRSIG.py +++ b/dns/rdtypes/ANY/RRSIG.py @@ -13,6 +13,7 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +import base64 import calendar import struct import time @@ -22,9 +23,12 @@ import dns.exception import dns.rdata import dns.rdatatype + class BadSigTime(dns.exception.DNSException): + """Time in DNS SIG or RRSIG resource record cannot be parsed.""" + def sigtime_to_posixtime(what): if len(what) != 14: raise BadSigTime @@ -37,10 +41,13 @@ def sigtime_to_posixtime(what): return calendar.timegm((year, month, day, hour, minute, second, 0, 0, 0)) + def posixtime_to_sigtime(what): return time.strftime('%Y%m%d%H%M%S', time.gmtime(what)) + class RRSIG(dns.rdata.Rdata): + """RRSIG record @ivar type_covered: the rdata type this signature covers @@ -94,9 +101,10 @@ class RRSIG(dns.rdata.Rdata): self.key_tag, self.signer.choose_relativity(origin, relativize), dns.rdata._base64ify(self.signature) - ) + ) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): type_covered = dns.rdatatype.from_text(tok.get_string()) algorithm = dns.dnssec.algorithm_from_text(tok.get_string()) labels = tok.get_int() @@ -113,16 +121,14 @@ class RRSIG(dns.rdata.Rdata): break if not t.is_identifier(): raise dns.exception.SyntaxError - chunks.append(t.value) - b64 = ''.join(chunks) - signature = b64.decode('base64_codec') + chunks.append(t.value.encode()) + b64 = b''.join(chunks) + signature = base64.b64decode(b64) return cls(rdclass, rdtype, type_covered, algorithm, labels, original_ttl, expiration, inception, key_tag, signer, signature) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): header = struct.pack('!HBBIIIH', self.type_covered, self.algorithm, self.labels, self.original_ttl, self.expiration, @@ -131,21 +137,20 @@ class RRSIG(dns.rdata.Rdata): self.signer.to_wire(file, None, origin) file.write(self.signature) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): - header = struct.unpack('!HBBIIIH', wire[current : current + 18]) + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): + header = struct.unpack('!HBBIIIH', wire[current: current + 18]) current += 18 rdlen -= 18 (signer, cused) = dns.name.from_wire(wire[: current + rdlen], current) current += cused rdlen -= cused - if not origin is None: + if origin is not None: signer = signer.relativize(origin) - signature = wire[current : current + rdlen].unwrap() + signature = wire[current: current + rdlen].unwrap() return cls(rdclass, rdtype, header[0], header[1], header[2], header[3], header[4], header[5], header[6], signer, signature) - from_wire = classmethod(from_wire) - - def choose_relativity(self, origin = None, relativize = True): + def choose_relativity(self, origin=None, relativize=True): self.signer = self.signer.choose_relativity(origin, relativize) diff --git a/dns/rdtypes/ANY/RT.py b/dns/rdtypes/ANY/RT.py index 5ba9417b..88b75486 100644 --- a/dns/rdtypes/ANY/RT.py +++ b/dns/rdtypes/ANY/RT.py @@ -15,5 +15,7 @@ import dns.rdtypes.mxbase + class RT(dns.rdtypes.mxbase.UncompressedDowncasingMX): + """RT record""" diff --git a/dns/rdtypes/ANY/SOA.py b/dns/rdtypes/ANY/SOA.py index de786147..cc0098e8 100644 --- a/dns/rdtypes/ANY/SOA.py +++ b/dns/rdtypes/ANY/SOA.py @@ -19,7 +19,9 @@ import dns.exception import dns.rdata import dns.name + class SOA(dns.rdata.Rdata): + """SOA record @ivar mname: the SOA MNAME (master name) field @@ -58,9 +60,10 @@ class SOA(dns.rdata.Rdata): rname = self.rname.choose_relativity(origin, relativize) return '%s %s %d %d %d %d %d' % ( mname, rname, self.serial, self.refresh, self.retry, - self.expire, self.minimum ) + self.expire, self.minimum) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): mname = tok.get_name() rname = tok.get_name() mname = mname.choose_relativity(origin, relativize) @@ -72,24 +75,23 @@ class SOA(dns.rdata.Rdata): minimum = tok.get_ttl() tok.get_eol() return cls(rdclass, rdtype, mname, rname, serial, refresh, retry, - expire, minimum ) + expire, minimum) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): self.mname.to_wire(file, compress, origin) self.rname.to_wire(file, compress, origin) five_ints = struct.pack('!IIIII', self.serial, self.refresh, self.retry, self.expire, self.minimum) file.write(five_ints) - def to_digestable(self, origin = None): + def to_digestable(self, origin=None): return self.mname.to_digestable(origin) + \ self.rname.to_digestable(origin) + \ struct.pack('!IIIII', self.serial, self.refresh, self.retry, self.expire, self.minimum) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): (mname, cused) = dns.name.from_wire(wire[: current + rdlen], current) current += cused rdlen -= cused @@ -99,16 +101,14 @@ class SOA(dns.rdata.Rdata): if rdlen != 20: raise dns.exception.FormError five_ints = struct.unpack('!IIIII', - wire[current : current + rdlen]) - if not origin is None: + wire[current: current + rdlen]) + if origin is not None: mname = mname.relativize(origin) rname = rname.relativize(origin) return cls(rdclass, rdtype, mname, rname, five_ints[0], five_ints[1], five_ints[2], five_ints[3], five_ints[4]) - from_wire = classmethod(from_wire) - - def choose_relativity(self, origin = None, relativize = True): + def choose_relativity(self, origin=None, relativize=True): self.mname = self.mname.choose_relativity(origin, relativize) self.rname = self.rname.choose_relativity(origin, relativize) diff --git a/dns/rdtypes/ANY/SPF.py b/dns/rdtypes/ANY/SPF.py index 1df95806..f3e0904e 100644 --- a/dns/rdtypes/ANY/SPF.py +++ b/dns/rdtypes/ANY/SPF.py @@ -15,7 +15,9 @@ import dns.rdtypes.txtbase + class SPF(dns.rdtypes.txtbase.TXTBase): + """SPF record @see: RFC 4408""" diff --git a/dns/rdtypes/ANY/SSHFP.py b/dns/rdtypes/ANY/SSHFP.py index f5a34c21..b6ed396f 100644 --- a/dns/rdtypes/ANY/SSHFP.py +++ b/dns/rdtypes/ANY/SSHFP.py @@ -14,11 +14,14 @@ # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. import struct +import binascii import dns.rdata import dns.rdatatype + class SSHFP(dns.rdata.Rdata): + """SSHFP record @ivar algorithm: the algorithm @@ -44,7 +47,8 @@ class SSHFP(dns.rdata.Rdata): dns.rdata._hexify(self.fingerprint, chunksize=128)) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): algorithm = tok.get_uint8() fp_type = tok.get_uint8() chunks = [] @@ -54,23 +58,21 @@ class SSHFP(dns.rdata.Rdata): break if not t.is_identifier(): raise dns.exception.SyntaxError - chunks.append(t.value) - fingerprint = ''.join(chunks) - fingerprint = fingerprint.decode('hex_codec') + chunks.append(t.value.encode()) + fingerprint = b''.join(chunks) + fingerprint = binascii.unhexlify(fingerprint) return cls(rdclass, rdtype, algorithm, fp_type, fingerprint) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): header = struct.pack("!BB", self.algorithm, self.fp_type) file.write(header) file.write(self.fingerprint) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): - header = struct.unpack("!BB", wire[current : current + 2]) + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): + header = struct.unpack("!BB", wire[current: current + 2]) current += 2 rdlen -= 2 - fingerprint = wire[current : current + rdlen].unwrap() + fingerprint = wire[current: current + rdlen].unwrap() return cls(rdclass, rdtype, header[0], header[1], fingerprint) - from_wire = classmethod(from_wire) diff --git a/dns/rdtypes/ANY/TLSA.py b/dns/rdtypes/ANY/TLSA.py index 92867ef3..23f4e94b 100644 --- a/dns/rdtypes/ANY/TLSA.py +++ b/dns/rdtypes/ANY/TLSA.py @@ -14,11 +14,14 @@ # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. import struct +import binascii import dns.rdata import dns.rdatatype + class TLSA(dns.rdata.Rdata): + """TLSA record @ivar usage: The certificate usage @@ -46,9 +49,10 @@ class TLSA(dns.rdata.Rdata): self.selector, self.mtype, dns.rdata._hexify(self.cert, - chunksize=128)) + chunksize=128)) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): usage = tok.get_uint8() selector = tok.get_uint8() mtype = tok.get_uint8() @@ -59,23 +63,21 @@ class TLSA(dns.rdata.Rdata): break if not t.is_identifier(): raise dns.exception.SyntaxError - cert_chunks.append(t.value) - cert = ''.join(cert_chunks) - cert = cert.decode('hex_codec') + cert_chunks.append(t.value.encode()) + cert = b''.join(cert_chunks) + cert = binascii.unhexlify(cert) return cls(rdclass, rdtype, usage, selector, mtype, cert) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): header = struct.pack("!BBB", self.usage, self.selector, self.mtype) file.write(header) file.write(self.cert) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): - header = struct.unpack("!BBB", wire[current : current + 3]) + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): + header = struct.unpack("!BBB", wire[current: current + 3]) current += 3 rdlen -= 3 - cert = wire[current : current + rdlen].unwrap() + cert = wire[current: current + rdlen].unwrap() return cls(rdclass, rdtype, header[0], header[1], header[2], cert) - from_wire = classmethod(from_wire) diff --git a/dns/rdtypes/ANY/TXT.py b/dns/rdtypes/ANY/TXT.py index f921b369..6c7fa450 100644 --- a/dns/rdtypes/ANY/TXT.py +++ b/dns/rdtypes/ANY/TXT.py @@ -15,5 +15,7 @@ import dns.rdtypes.txtbase + class TXT(dns.rdtypes.txtbase.TXTBase): + """TXT record""" diff --git a/dns/rdtypes/ANY/URI.py b/dns/rdtypes/ANY/URI.py index 7ff6057c..0c121d2c 100644 --- a/dns/rdtypes/ANY/URI.py +++ b/dns/rdtypes/ANY/URI.py @@ -19,8 +19,11 @@ import struct import dns.exception import dns.rdata import dns.name +from dns._compat import text_type + class URI(dns.rdata.Rdata): + """URI record @ivar priority: the priority @@ -39,12 +42,17 @@ class URI(dns.rdata.Rdata): self.weight = weight if len(target) < 1: raise dns.exception.SyntaxError("URI target cannot be empty") - self.target = target + if isinstance(target, text_type): + self.target = target.encode() + else: + self.target = target def to_text(self, origin=None, relativize=True, **kw): - return '%d %d "%s"' % (self.priority, self.weight, self.target) + return '%d %d "%s"' % (self.priority, self.weight, + self.target.decode()) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): priority = tok.get_uint16() weight = tok.get_uint16() target = tok.get().unescape() @@ -53,23 +61,21 @@ class URI(dns.rdata.Rdata): tok.get_eol() return cls(rdclass, rdtype, priority, weight, target.value) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): two_ints = struct.pack("!HH", self.priority, self.weight) file.write(two_ints) file.write(self.target) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): if rdlen < 5: raise dns.exception.FormError('URI RR is shorter than 5 octets') - (priority, weight) = struct.unpack('!HH', wire[current : current + 4]) + (priority, weight) = struct.unpack('!HH', wire[current: current + 4]) current += 4 rdlen -= 4 - target = wire[current : current + rdlen] + target = wire[current: current + rdlen] current += rdlen return cls(rdclass, rdtype, priority, weight, target) - from_wire = classmethod(from_wire) diff --git a/dns/rdtypes/ANY/X25.py b/dns/rdtypes/ANY/X25.py index 47671e4d..f5cca114 100644 --- a/dns/rdtypes/ANY/X25.py +++ b/dns/rdtypes/ANY/X25.py @@ -13,11 +13,16 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +import struct + import dns.exception import dns.rdata import dns.tokenizer +from dns._compat import text_type + class X25(dns.rdata.Rdata): + """X25 record @ivar address: the PSDN address @@ -28,32 +33,33 @@ class X25(dns.rdata.Rdata): def __init__(self, rdclass, rdtype, address): super(X25, self).__init__(rdclass, rdtype) - self.address = address + if isinstance(address, text_type): + self.address = address.encode() + else: + self.address = address def to_text(self, origin=None, relativize=True, **kw): return '"%s"' % dns.rdata._escapify(self.address) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): address = tok.get_string() tok.get_eol() return cls(rdclass, rdtype, address) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): l = len(self.address) assert l < 256 - byte = chr(l) - file.write(byte) + file.write(struct.pack('!B', l)) file.write(self.address) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): - l = ord(wire[current]) + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): + l = wire[current] current += 1 rdlen -= 1 if l != rdlen: raise dns.exception.FormError - address = wire[current : current + l].unwrap() + address = wire[current: current + l].unwrap() return cls(rdclass, rdtype, address) - from_wire = classmethod(from_wire) diff --git a/dns/rdtypes/IN/A.py b/dns/rdtypes/IN/A.py index 945d9348..42faf9ba 100644 --- a/dns/rdtypes/IN/A.py +++ b/dns/rdtypes/IN/A.py @@ -18,7 +18,9 @@ import dns.ipv4 import dns.rdata import dns.tokenizer + class A(dns.rdata.Rdata): + """A record. @ivar address: an IPv4 address @@ -29,24 +31,23 @@ class A(dns.rdata.Rdata): def __init__(self, rdclass, rdtype, address): super(A, self).__init__(rdclass, rdtype) # check that it's OK - junk = dns.ipv4.inet_aton(address) + dns.ipv4.inet_aton(address) self.address = address def to_text(self, origin=None, relativize=True, **kw): return self.address - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): address = tok.get_identifier() tok.get_eol() return cls(rdclass, rdtype, address) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): file.write(dns.ipv4.inet_aton(self.address)) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): - address = dns.ipv4.inet_ntoa(wire[current : current + rdlen]) + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): + address = dns.ipv4.inet_ntoa(wire[current: current + rdlen]).decode() return cls(rdclass, rdtype, address) - from_wire = classmethod(from_wire) diff --git a/dns/rdtypes/IN/AAAA.py b/dns/rdtypes/IN/AAAA.py index 65be1c25..d2c65c63 100644 --- a/dns/rdtypes/IN/AAAA.py +++ b/dns/rdtypes/IN/AAAA.py @@ -18,7 +18,9 @@ import dns.inet import dns.rdata import dns.tokenizer + class AAAA(dns.rdata.Rdata): + """AAAA record. @ivar address: an IPv6 address @@ -29,25 +31,24 @@ class AAAA(dns.rdata.Rdata): def __init__(self, rdclass, rdtype, address): super(AAAA, self).__init__(rdclass, rdtype) # check that it's OK - junk = dns.inet.inet_pton(dns.inet.AF_INET6, address) + dns.inet.inet_pton(dns.inet.AF_INET6, address) self.address = address def to_text(self, origin=None, relativize=True, **kw): return self.address - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): address = tok.get_identifier() tok.get_eol() return cls(rdclass, rdtype, address) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): file.write(dns.inet.inet_pton(dns.inet.AF_INET6, self.address)) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): address = dns.inet.inet_ntop(dns.inet.AF_INET6, - wire[current : current + rdlen]) + wire[current: current + rdlen]) return cls(rdclass, rdtype, address) - from_wire = classmethod(from_wire) diff --git a/dns/rdtypes/IN/APL.py b/dns/rdtypes/IN/APL.py index 6239f078..82026adf 100644 --- a/dns/rdtypes/IN/APL.py +++ b/dns/rdtypes/IN/APL.py @@ -13,15 +13,18 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -import cStringIO import struct +import binascii import dns.exception import dns.inet import dns.rdata import dns.tokenizer +from dns._compat import xrange + class APLItem(object): + """An APL list item. @ivar family: the address family (IANA address family registry) @@ -54,7 +57,7 @@ class APLItem(object): elif self.family == 2: address = dns.inet.inet_pton(dns.inet.AF_INET6, self.address) else: - address = self.address.decode('hex_codec') + address = binascii.unhexlify(self.address) # # Truncate least significant zero bytes. # @@ -63,7 +66,7 @@ class APLItem(object): if address[i] != chr(0): last = i + 1 break - address = address[0 : last] + address = address[0: last] l = len(address) assert l < 128 if self.negation: @@ -72,7 +75,9 @@ class APLItem(object): file.write(header) file.write(address) + class APL(dns.rdata.Rdata): + """APL record. @ivar items: a list of APL items @@ -88,7 +93,8 @@ class APL(dns.rdata.Rdata): def to_text(self, origin=None, relativize=True, **kw): return ' '.join(map(lambda x: str(x), self.items)) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): items = [] while 1: token = tok.get().unescape() @@ -109,20 +115,19 @@ class APL(dns.rdata.Rdata): return cls(rdclass, rdtype, items) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): for item in self.items: item.to_wire(file) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): items = [] while 1: if rdlen == 0: break if rdlen < 4: raise dns.exception.FormError - header = struct.unpack('!HBB', wire[current : current + 4]) + header = struct.unpack('!HBB', wire[current: current + 4]) afdlen = header[2] if afdlen > 127: negation = True @@ -133,7 +138,7 @@ class APL(dns.rdata.Rdata): rdlen -= 4 if rdlen < afdlen: raise dns.exception.FormError - address = wire[current : current + afdlen].unwrap() + address = wire[current: current + afdlen].unwrap() l = len(address) if header[0] == 1: if l < 4: @@ -155,4 +160,3 @@ class APL(dns.rdata.Rdata): items.append(item) return cls(rdclass, rdtype, items) - from_wire = classmethod(from_wire) diff --git a/dns/rdtypes/IN/DHCID.py b/dns/rdtypes/IN/DHCID.py index 705e4a4e..06a850ad 100644 --- a/dns/rdtypes/IN/DHCID.py +++ b/dns/rdtypes/IN/DHCID.py @@ -13,9 +13,13 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +import base64 + import dns.exception + class DHCID(dns.rdata.Rdata): + """DHCID record @ivar data: the data (the content of the RR is opaque as far as the @@ -32,7 +36,8 @@ class DHCID(dns.rdata.Rdata): def to_text(self, origin=None, relativize=True, **kw): return dns.rdata._base64ify(self.data) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): chunks = [] while 1: t = tok.get().unescape() @@ -40,18 +45,16 @@ class DHCID(dns.rdata.Rdata): break if not t.is_identifier(): raise dns.exception.SyntaxError - chunks.append(t.value) - b64 = ''.join(chunks) - data = b64.decode('base64_codec') + chunks.append(t.value.encode()) + b64 = b''.join(chunks) + data = base64.b64decode(b64) return cls(rdclass, rdtype, data) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): file.write(self.data) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): - data = wire[current : current + rdlen].unwrap() + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): + data = wire[current: current + rdlen].unwrap() return cls(rdclass, rdtype, data) - from_wire = classmethod(from_wire) diff --git a/dns/rdtypes/IN/IPSECKEY.py b/dns/rdtypes/IN/IPSECKEY.py index d5d16bd8..4f07bd09 100644 --- a/dns/rdtypes/IN/IPSECKEY.py +++ b/dns/rdtypes/IN/IPSECKEY.py @@ -13,14 +13,16 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -import cStringIO import struct +import base64 import dns.exception import dns.inet import dns.name + class IPSECKEY(dns.rdata.Rdata): + """IPSECKEY record @ivar precedence: the precedence for this key data @@ -41,19 +43,20 @@ class IPSECKEY(dns.rdata.Rdata): gateway, key): super(IPSECKEY, self).__init__(rdclass, rdtype) if gateway_type == 0: - if gateway != '.' and not gateway is None: + if gateway != '.' and gateway is not None: raise SyntaxError('invalid gateway for gateway type 0') gateway = None elif gateway_type == 1: # check that it's OK - junk = dns.inet.inet_pton(dns.inet.AF_INET, gateway) + dns.inet.inet_pton(dns.inet.AF_INET, gateway) elif gateway_type == 2: # check that it's OK - junk = dns.inet.inet_pton(dns.inet.AF_INET6, gateway) + dns.inet.inet_pton(dns.inet.AF_INET6, gateway) elif gateway_type == 3: pass else: - raise SyntaxError('invalid IPSECKEY gateway type: %d' % gateway_type) + raise SyntaxError( + 'invalid IPSECKEY gateway type: %d' % gateway_type) self.precedence = precedence self.gateway_type = gateway_type self.algorithm = algorithm @@ -75,7 +78,8 @@ class IPSECKEY(dns.rdata.Rdata): self.algorithm, gateway, dns.rdata._base64ify(self.key)) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): precedence = tok.get_uint8() gateway_type = tok.get_uint8() algorithm = tok.get_uint8() @@ -90,15 +94,13 @@ class IPSECKEY(dns.rdata.Rdata): break if not t.is_identifier(): raise dns.exception.SyntaxError - chunks.append(t.value) - b64 = ''.join(chunks) - key = b64.decode('base64_codec') + chunks.append(t.value.encode()) + b64 = b''.join(chunks) + key = base64.b64decode(b64) return cls(rdclass, rdtype, precedence, gateway_type, algorithm, gateway, key) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): header = struct.pack("!BBB", self.precedence, self.gateway_type, self.algorithm) file.write(header) @@ -114,10 +116,11 @@ class IPSECKEY(dns.rdata.Rdata): raise ValueError('invalid gateway type') file.write(self.key) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): if rdlen < 3: raise dns.exception.FormError - header = struct.unpack('!BBB', wire[current : current + 3]) + header = struct.unpack('!BBB', wire[current: current + 3]) gateway_type = header[1] current += 3 rdlen -= 3 @@ -125,12 +128,12 @@ class IPSECKEY(dns.rdata.Rdata): gateway = None elif gateway_type == 1: gateway = dns.inet.inet_ntop(dns.inet.AF_INET, - wire[current : current + 4]) + wire[current: current + 4]) current += 4 rdlen -= 4 elif gateway_type == 2: gateway = dns.inet.inet_ntop(dns.inet.AF_INET6, - wire[current : current + 16]) + wire[current: current + 16]) current += 16 rdlen -= 16 elif gateway_type == 3: @@ -140,8 +143,7 @@ class IPSECKEY(dns.rdata.Rdata): rdlen -= cused else: raise dns.exception.FormError('invalid IPSECKEY gateway type') - key = wire[current : current + rdlen].unwrap() + key = wire[current: current + rdlen].unwrap() return cls(rdclass, rdtype, header[0], gateway_type, header[2], gateway, key) - from_wire = classmethod(from_wire) diff --git a/dns/rdtypes/IN/KX.py b/dns/rdtypes/IN/KX.py index a01bddd1..adbfe34b 100644 --- a/dns/rdtypes/IN/KX.py +++ b/dns/rdtypes/IN/KX.py @@ -15,5 +15,7 @@ import dns.rdtypes.mxbase + class KX(dns.rdtypes.mxbase.UncompressedMX): + """KX record""" diff --git a/dns/rdtypes/IN/NAPTR.py b/dns/rdtypes/IN/NAPTR.py index 1357c669..5ae2feb1 100644 --- a/dns/rdtypes/IN/NAPTR.py +++ b/dns/rdtypes/IN/NAPTR.py @@ -18,15 +18,24 @@ import struct import dns.exception import dns.name import dns.rdata +from dns._compat import xrange, text_type + def _write_string(file, s): l = len(s) assert l < 256 - byte = chr(l) - file.write(byte) + file.write(struct.pack('!B', l)) file.write(s) + +def _sanitize(value): + if isinstance(value, text_type): + return value.encode() + return value + + class NAPTR(dns.rdata.Rdata): + """NAPTR record @ivar order: order @@ -49,11 +58,11 @@ class NAPTR(dns.rdata.Rdata): def __init__(self, rdclass, rdtype, order, preference, flags, service, regexp, replacement): super(NAPTR, self).__init__(rdclass, rdtype) + self.flags = _sanitize(flags) + self.service = _sanitize(service) + self.regexp = _sanitize(regexp) self.order = order self.preference = preference - self.flags = flags - self.service = service - self.regexp = regexp self.replacement = replacement def to_text(self, origin=None, relativize=True, **kw): @@ -63,9 +72,10 @@ class NAPTR(dns.rdata.Rdata): dns.rdata._escapify(self.flags), dns.rdata._escapify(self.service), dns.rdata._escapify(self.regexp), - self.replacement) + replacement) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): order = tok.get_uint16() preference = tok.get_uint16() flags = tok.get_string() @@ -77,9 +87,7 @@ class NAPTR(dns.rdata.Rdata): return cls(rdclass, rdtype, order, preference, flags, service, regexp, replacement) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): two_ints = struct.pack("!HH", self.order, self.preference) file.write(two_ints) _write_string(file, self.flags) @@ -87,18 +95,19 @@ class NAPTR(dns.rdata.Rdata): _write_string(file, self.regexp) self.replacement.to_wire(file, compress, origin) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): - (order, preference) = struct.unpack('!HH', wire[current : current + 4]) + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): + (order, preference) = struct.unpack('!HH', wire[current: current + 4]) current += 4 rdlen -= 4 strings = [] for i in xrange(3): - l = ord(wire[current]) + l = wire[current] current += 1 rdlen -= 1 if l > rdlen or rdlen < 0: raise dns.exception.FormError - s = wire[current : current + l].unwrap() + s = wire[current: current + l].unwrap() current += l rdlen -= l strings.append(s) @@ -106,13 +115,11 @@ class NAPTR(dns.rdata.Rdata): current) if cused != rdlen: raise dns.exception.FormError - if not origin is None: + if origin is not None: replacement = replacement.relativize(origin) return cls(rdclass, rdtype, order, preference, strings[0], strings[1], strings[2], replacement) - from_wire = classmethod(from_wire) - - def choose_relativity(self, origin = None, relativize = True): + def choose_relativity(self, origin=None, relativize=True): self.replacement = self.replacement.choose_relativity(origin, relativize) diff --git a/dns/rdtypes/IN/NSAP.py b/dns/rdtypes/IN/NSAP.py index c2040639..6dbe5af0 100644 --- a/dns/rdtypes/IN/NSAP.py +++ b/dns/rdtypes/IN/NSAP.py @@ -13,11 +13,15 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +import binascii + import dns.exception import dns.rdata import dns.tokenizer + class NSAP(dns.rdata.Rdata): + """NSAP record. @ivar address: a NASP @@ -31,26 +35,25 @@ class NSAP(dns.rdata.Rdata): self.address = address def to_text(self, origin=None, relativize=True, **kw): - return "0x%s" % self.address.encode('hex_codec') + return "0x%s" % binascii.hexlify(self.address).decode() - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): address = tok.get_string() - t = tok.get_eol() + tok.get_eol() if address[0:2] != '0x': raise dns.exception.SyntaxError('string does not start with 0x') address = address[2:].replace('.', '') if len(address) % 2 != 0: raise dns.exception.SyntaxError('hexstring has odd length') - address = address.decode('hex_codec') + address = binascii.unhexlify(address.encode()) return cls(rdclass, rdtype, address) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): file.write(self.address) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): - address = wire[current : current + rdlen].unwrap() + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): + address = wire[current: current + rdlen].unwrap() return cls(rdclass, rdtype, address) - from_wire = classmethod(from_wire) diff --git a/dns/rdtypes/IN/NSAP_PTR.py b/dns/rdtypes/IN/NSAP_PTR.py index 122c5123..56967df0 100644 --- a/dns/rdtypes/IN/NSAP_PTR.py +++ b/dns/rdtypes/IN/NSAP_PTR.py @@ -15,5 +15,7 @@ import dns.rdtypes.nsbase + class NSAP_PTR(dns.rdtypes.nsbase.UncompressedNS): + """NSAP-PTR record""" diff --git a/dns/rdtypes/IN/PX.py b/dns/rdtypes/IN/PX.py index 1b86789e..e1ef102b 100644 --- a/dns/rdtypes/IN/PX.py +++ b/dns/rdtypes/IN/PX.py @@ -19,7 +19,9 @@ import dns.exception import dns.rdata import dns.name + class PX(dns.rdata.Rdata): + """PX record. @ivar preference: the preference value @@ -43,7 +45,8 @@ class PX(dns.rdata.Rdata): mapx400 = self.mapx400.choose_relativity(origin, relativize) return '%d %s %s' % (self.preference, map822, mapx400) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): preference = tok.get_uint16() map822 = tok.get_name() map822 = map822.choose_relativity(origin, relativize) @@ -52,36 +55,33 @@ class PX(dns.rdata.Rdata): tok.get_eol() return cls(rdclass, rdtype, preference, map822, mapx400) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): pref = struct.pack("!H", self.preference) file.write(pref) self.map822.to_wire(file, None, origin) self.mapx400.to_wire(file, None, origin) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): - (preference, ) = struct.unpack('!H', wire[current : current + 2]) + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): + (preference, ) = struct.unpack('!H', wire[current: current + 2]) current += 2 rdlen -= 2 (map822, cused) = dns.name.from_wire(wire[: current + rdlen], - current) + current) if cused > rdlen: raise dns.exception.FormError current += cused rdlen -= cused - if not origin is None: + if origin is not None: map822 = map822.relativize(origin) (mapx400, cused) = dns.name.from_wire(wire[: current + rdlen], current) if cused != rdlen: raise dns.exception.FormError - if not origin is None: + if origin is not None: mapx400 = mapx400.relativize(origin) return cls(rdclass, rdtype, preference, map822, mapx400) - from_wire = classmethod(from_wire) - - def choose_relativity(self, origin = None, relativize = True): + def choose_relativity(self, origin=None, relativize=True): self.map822 = self.map822.choose_relativity(origin, relativize) self.mapx400 = self.mapx400.choose_relativity(origin, relativize) diff --git a/dns/rdtypes/IN/SRV.py b/dns/rdtypes/IN/SRV.py index 5da202b8..f4396d61 100644 --- a/dns/rdtypes/IN/SRV.py +++ b/dns/rdtypes/IN/SRV.py @@ -19,7 +19,9 @@ import dns.exception import dns.rdata import dns.name + class SRV(dns.rdata.Rdata): + """SRV record @ivar priority: the priority @@ -46,7 +48,8 @@ class SRV(dns.rdata.Rdata): return '%d %d %d %s' % (self.priority, self.weight, self.port, target) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): priority = tok.get_uint16() weight = tok.get_uint16() port = tok.get_uint16() @@ -55,27 +58,24 @@ class SRV(dns.rdata.Rdata): tok.get_eol() return cls(rdclass, rdtype, priority, weight, port, target) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): three_ints = struct.pack("!HHH", self.priority, self.weight, self.port) file.write(three_ints) self.target.to_wire(file, compress, origin) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): (priority, weight, port) = struct.unpack('!HHH', - wire[current : current + 6]) + wire[current: current + 6]) current += 6 rdlen -= 6 (target, cused) = dns.name.from_wire(wire[: current + rdlen], current) if cused != rdlen: raise dns.exception.FormError - if not origin is None: + if origin is not None: target = target.relativize(origin) return cls(rdclass, rdtype, priority, weight, port, target) - from_wire = classmethod(from_wire) - - def choose_relativity(self, origin = None, relativize = True): + def choose_relativity(self, origin=None, relativize=True): self.target = self.target.choose_relativity(origin, relativize) diff --git a/dns/rdtypes/IN/WKS.py b/dns/rdtypes/IN/WKS.py index 9d015044..da2a2d88 100644 --- a/dns/rdtypes/IN/WKS.py +++ b/dns/rdtypes/IN/WKS.py @@ -18,11 +18,14 @@ import struct import dns.ipv4 import dns.rdata +from dns._compat import xrange _proto_tcp = socket.getprotobyname('tcp') _proto_udp = socket.getprotobyname('udp') + class WKS(dns.rdata.Rdata): + """WKS record @ivar address: the address @@ -39,26 +42,30 @@ class WKS(dns.rdata.Rdata): super(WKS, self).__init__(rdclass, rdtype) self.address = address self.protocol = protocol - self.bitmap = bitmap + if not isinstance(bitmap, bytearray): + self.bitmap = bytearray(bitmap) + else: + self.bitmap = bitmap def to_text(self, origin=None, relativize=True, **kw): bits = [] for i in xrange(0, len(self.bitmap)): - byte = ord(self.bitmap[i]) + byte = self.bitmap[i] for j in xrange(0, 8): if byte & (0x80 >> j): bits.append(str(i * 8 + j)) text = ' '.join(bits) return '%s %d %s' % (self.address, self.protocol, text) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): address = tok.get_string() protocol = tok.get_string() if protocol.isdigit(): protocol = int(protocol) else: protocol = socket.getprotobyname(protocol) - bitmap = [] + bitmap = bytearray() while 1: token = tok.get().unescape() if token.is_eol_or_eof(): @@ -77,25 +84,23 @@ class WKS(dns.rdata.Rdata): l = len(bitmap) if l < i + 1: for j in xrange(l, i + 1): - bitmap.append('\x00') - bitmap[i] = chr(ord(bitmap[i]) | (0x80 >> (serv % 8))) + bitmap.append(0) + bitmap[i] = bitmap[i] | (0x80 >> (serv % 8)) bitmap = dns.rdata._truncate_bitmap(bitmap) return cls(rdclass, rdtype, address, protocol, bitmap) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): file.write(dns.ipv4.inet_aton(self.address)) protocol = struct.pack('!B', self.protocol) file.write(protocol) file.write(self.bitmap) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): - address = dns.ipv4.inet_ntoa(wire[current : current + 4]) - protocol, = struct.unpack('!B', wire[current + 4 : current + 5]) + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): + address = dns.ipv4.inet_ntoa(wire[current: current + 4]) + protocol, = struct.unpack('!B', wire[current + 4: current + 5]) current += 5 rdlen -= 5 - bitmap = wire[current : current + rdlen].unwrap() + bitmap = wire[current: current + rdlen].unwrap() return cls(rdclass, rdtype, address, protocol, bitmap) - from_wire = classmethod(from_wire) diff --git a/dns/rdtypes/dnskeybase.py b/dns/rdtypes/dnskeybase.py index 7e16085e..85c4b23f 100644 --- a/dns/rdtypes/dnskeybase.py +++ b/dns/rdtypes/dnskeybase.py @@ -21,7 +21,8 @@ import dns.dnssec import dns.rdata # wildcard import -__all__ = [ "SEP", "REVOKE", "ZONE", "flags_to_text_set", "flags_from_text_set" ] +__all__ = ["SEP", "REVOKE", "ZONE", + "flags_to_text_set", "flags_from_text_set"] # flag constants SEP = 0x0001 @@ -32,12 +33,13 @@ _flag_by_text = { 'SEP': SEP, 'REVOKE': REVOKE, 'ZONE': ZONE - } +} # We construct the inverse mapping programmatically to ensure that we # cannot make any mistakes (e.g. omissions, cut-and-paste errors) that # would cause the mapping not to be true inverse. -_flag_by_value = dict([(y, x) for x, y in _flag_by_text.items()]) +_flag_by_value = dict((y, x) for x, y in _flag_by_text.items()) + def flags_to_text_set(flags): """Convert a DNSKEY flags value to set texts @@ -68,7 +70,9 @@ def flags_from_text_set(texts_set): "DNSKEY flag '%s' is not supported" % text) return flags + class DNSKEYBase(dns.rdata.Rdata): + """Base class for rdata that is like a DNSKEY record @ivar flags: the key flags @@ -93,7 +97,8 @@ class DNSKEYBase(dns.rdata.Rdata): return '%d %d %d %s' % (self.flags, self.protocol, self.algorithm, dns.rdata._base64ify(self.key)) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): flags = tok.get_uint16() protocol = tok.get_uint8() algorithm = dns.dnssec.algorithm_from_text(tok.get_string()) @@ -104,30 +109,27 @@ class DNSKEYBase(dns.rdata.Rdata): break if not t.is_identifier(): raise dns.exception.SyntaxError - chunks.append(t.value) - b64 = ''.join(chunks) - key = b64.decode('base64_codec') + chunks.append(t.value.encode()) + b64 = b''.join(chunks) + key = base64.b64decode(b64) return cls(rdclass, rdtype, flags, protocol, algorithm, key) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): header = struct.pack("!HBB", self.flags, self.protocol, self.algorithm) file.write(header) file.write(self.key) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): if rdlen < 4: raise dns.exception.FormError - header = struct.unpack('!HBB', wire[current : current + 4]) + header = struct.unpack('!HBB', wire[current: current + 4]) current += 4 rdlen -= 4 - key = wire[current : current + rdlen].unwrap() + key = wire[current: current + rdlen].unwrap() return cls(rdclass, rdtype, header[0], header[1], header[2], key) - from_wire = classmethod(from_wire) - def flags_to_text_set(self): """Convert a DNSKEY flags value to set texts @rtype: set([string])""" diff --git a/dns/rdtypes/dsbase.py b/dns/rdtypes/dsbase.py index abcc57a7..80f792ac 100644 --- a/dns/rdtypes/dsbase.py +++ b/dns/rdtypes/dsbase.py @@ -14,11 +14,14 @@ # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. import struct +import binascii import dns.rdata import dns.rdatatype + class DSBase(dns.rdata.Rdata): + """Base class for rdata that is like a DS record @ivar key_tag: the key tag @@ -47,7 +50,8 @@ class DSBase(dns.rdata.Rdata): dns.rdata._hexify(self.digest, chunksize=128)) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): key_tag = tok.get_uint16() algorithm = tok.get_uint8() digest_type = tok.get_uint8() @@ -58,25 +62,23 @@ class DSBase(dns.rdata.Rdata): break if not t.is_identifier(): raise dns.exception.SyntaxError - chunks.append(t.value) - digest = ''.join(chunks) - digest = digest.decode('hex_codec') + chunks.append(t.value.encode()) + digest = b''.join(chunks) + digest = binascii.unhexlify(digest) return cls(rdclass, rdtype, key_tag, algorithm, digest_type, digest) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): header = struct.pack("!HBB", self.key_tag, self.algorithm, self.digest_type) file.write(header) file.write(self.digest) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): - header = struct.unpack("!HBB", wire[current : current + 4]) + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): + header = struct.unpack("!HBB", wire[current: current + 4]) current += 4 rdlen -= 4 - digest = wire[current : current + rdlen].unwrap() + digest = wire[current: current + rdlen].unwrap() return cls(rdclass, rdtype, header[0], header[1], header[2], digest) - from_wire = classmethod(from_wire) diff --git a/dns/rdtypes/euibase.py b/dns/rdtypes/euibase.py index 2af018d4..13109163 100644 --- a/dns/rdtypes/euibase.py +++ b/dns/rdtypes/euibase.py @@ -20,6 +20,7 @@ import dns.rdata class EUIBase(dns.rdata.Rdata): + """EUIxx record @ivar fingerprint: xx-bit Extended Unique Identifier (EUI-xx) @@ -41,6 +42,7 @@ class EUIBase(dns.rdata.Rdata): def to_text(self, origin=None, relativize=True, **kw): return dns.rdata._hexify(self.eui, chunksize=2).replace(' ', '-') + @classmethod def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): text = tok.get_string() tok.get_eol() @@ -54,18 +56,16 @@ class EUIBase(dns.rdata.Rdata): % i) text = text.replace('-', '') try: - data = binascii.unhexlify(text) + data = binascii.unhexlify(text.encode()) except (ValueError, TypeError) as ex: raise dns.exception.SyntaxError('Hex decoding error: %s' % str(ex)) return cls(rdclass, rdtype, data) - from_text = classmethod(from_text) - def to_wire(self, file, compress=None, origin=None): file.write(self.eui) + @classmethod def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): eui = wire[current:current + rdlen].unwrap() return cls(rdclass, rdtype, eui) - from_wire = classmethod(from_wire) diff --git a/dns/rdtypes/mxbase.py b/dns/rdtypes/mxbase.py index 754aff59..5ac8cef9 100644 --- a/dns/rdtypes/mxbase.py +++ b/dns/rdtypes/mxbase.py @@ -15,14 +15,16 @@ """MX-like base classes.""" -import cStringIO +from io import BytesIO import struct import dns.exception import dns.rdata import dns.name + class MXBase(dns.rdata.Rdata): + """Base class for rdata that is like an MX record. @ivar preference: the preference value @@ -41,57 +43,59 @@ class MXBase(dns.rdata.Rdata): exchange = self.exchange.choose_relativity(origin, relativize) return '%d %s' % (self.preference, exchange) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): preference = tok.get_uint16() exchange = tok.get_name() exchange = exchange.choose_relativity(origin, relativize) tok.get_eol() return cls(rdclass, rdtype, preference, exchange) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): pref = struct.pack("!H", self.preference) file.write(pref) self.exchange.to_wire(file, compress, origin) - def to_digestable(self, origin = None): + def to_digestable(self, origin=None): return struct.pack("!H", self.preference) + \ self.exchange.to_digestable(origin) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): - (preference, ) = struct.unpack('!H', wire[current : current + 2]) + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): + (preference, ) = struct.unpack('!H', wire[current: current + 2]) current += 2 rdlen -= 2 (exchange, cused) = dns.name.from_wire(wire[: current + rdlen], current) if cused != rdlen: raise dns.exception.FormError - if not origin is None: + if origin is not None: exchange = exchange.relativize(origin) return cls(rdclass, rdtype, preference, exchange) - from_wire = classmethod(from_wire) - - def choose_relativity(self, origin = None, relativize = True): + def choose_relativity(self, origin=None, relativize=True): self.exchange = self.exchange.choose_relativity(origin, relativize) + class UncompressedMX(MXBase): + """Base class for rdata that is like an MX record, but whose name is not compressed when converted to DNS wire format, and whose digestable form is not downcased.""" - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): super(UncompressedMX, self).to_wire(file, None, origin) - def to_digestable(self, origin = None): - f = cStringIO.StringIO() + def to_digestable(self, origin=None): + f = BytesIO() self.to_wire(f, None, origin) return f.getvalue() + class UncompressedDowncasingMX(MXBase): + """Base class for rdata that is like an MX record, but whose name is not compressed when convert to DNS wire format.""" - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): super(UncompressedDowncasingMX, self).to_wire(file, None, origin) diff --git a/dns/rdtypes/nsbase.py b/dns/rdtypes/nsbase.py index 84f088a1..79333a14 100644 --- a/dns/rdtypes/nsbase.py +++ b/dns/rdtypes/nsbase.py @@ -15,13 +15,15 @@ """NS-like base classes.""" -import cStringIO +from io import BytesIO import dns.exception import dns.rdata import dns.name + class NSBase(dns.rdata.Rdata): + """Base class for rdata that is like an NS record. @ivar target: the target name of the rdata @@ -37,43 +39,43 @@ class NSBase(dns.rdata.Rdata): target = self.target.choose_relativity(origin, relativize) return str(target) - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): target = tok.get_name() target = target.choose_relativity(origin, relativize) tok.get_eol() return cls(rdclass, rdtype, target) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): self.target.to_wire(file, compress, origin) - def to_digestable(self, origin = None): + def to_digestable(self, origin=None): return self.target.to_digestable(origin) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): (target, cused) = dns.name.from_wire(wire[: current + rdlen], current) if cused != rdlen: raise dns.exception.FormError - if not origin is None: + if origin is not None: target = target.relativize(origin) return cls(rdclass, rdtype, target) - from_wire = classmethod(from_wire) - - def choose_relativity(self, origin = None, relativize = True): + def choose_relativity(self, origin=None, relativize=True): self.target = self.target.choose_relativity(origin, relativize) + class UncompressedNS(NSBase): + """Base class for rdata that is like an NS record, but whose name is not compressed when convert to DNS wire format, and whose digestable form is not downcased.""" - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): super(UncompressedNS, self).to_wire(file, None, origin) - def to_digestable(self, origin = None): - f = cStringIO.StringIO() + def to_digestable(self, origin=None): + f = BytesIO() self.to_wire(f, None, origin) return f.getvalue() diff --git a/dns/rdtypes/txtbase.py b/dns/rdtypes/txtbase.py index cf4b4cef..54d7e6f0 100644 --- a/dns/rdtypes/txtbase.py +++ b/dns/rdtypes/txtbase.py @@ -15,11 +15,16 @@ """TXT-like base class.""" +import struct + import dns.exception import dns.rdata import dns.tokenizer +from dns._compat import binary_type + class TXTBase(dns.rdata.Rdata): + """Base class for rdata that is like a TXT record @ivar strings: the text strings @@ -31,7 +36,7 @@ class TXTBase(dns.rdata.Rdata): def __init__(self, rdclass, rdtype, strings): super(TXTBase, self).__init__(rdclass, rdtype) if isinstance(strings, str): - strings = [ strings ] + strings = [strings] self.strings = strings[:] def to_text(self, origin=None, relativize=True, **kw): @@ -42,7 +47,8 @@ class TXTBase(dns.rdata.Rdata): prefix = ' ' return txt - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + @classmethod + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): strings = [] while 1: token = tok.get().unescape() @@ -52,33 +58,34 @@ class TXTBase(dns.rdata.Rdata): raise dns.exception.SyntaxError("expected a string") if len(token.value) > 255: raise dns.exception.SyntaxError("string too long") - strings.append(token.value) + value = token.value + if isinstance(value, binary_type): + strings.append(value) + else: + strings.append(value.encode()) if len(strings) == 0: raise dns.exception.UnexpectedEnd return cls(rdclass, rdtype, strings) - from_text = classmethod(from_text) - - def to_wire(self, file, compress = None, origin = None): + def to_wire(self, file, compress=None, origin=None): for s in self.strings: l = len(s) assert l < 256 - byte = chr(l) - file.write(byte) + file.write(struct.pack('!B', l)) file.write(s) - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + @classmethod + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): strings = [] while rdlen > 0: - l = ord(wire[current]) + l = wire[current] current += 1 rdlen -= 1 if l > rdlen: raise dns.exception.FormError - s = wire[current : current + l].unwrap() + s = wire[current: current + l].unwrap() current += l rdlen -= l strings.append(s) return cls(rdclass, rdtype, strings) - from_wire = classmethod(from_wire) diff --git a/dns/renderer.py b/dns/renderer.py index 1708e92a..ed3ec66e 100644 --- a/dns/renderer.py +++ b/dns/renderer.py @@ -15,20 +15,25 @@ """Help for building DNS wire format messages""" -import cStringIO +from io import BytesIO import struct import random import time +import sys import dns.exception import dns.tsig +from ._compat import long + QUESTION = 0 ANSWER = 1 AUTHORITY = 2 ADDITIONAL = 3 + class Renderer(object): + """Helper class for building DNS wire-format messages. Most applications can use the higher-level L{dns.message.Message} @@ -51,7 +56,7 @@ class Renderer(object): wire = r.get_wire() @ivar output: where rendering is written - @type output: cStringIO.StringIO object + @type output: BytesIO object @ivar id: the message id @type id: int @ivar flags: the message flags @@ -86,7 +91,7 @@ class Renderer(object): @type origin: dns.name.Namem or None. """ - self.output = cStringIO.StringIO() + self.output = BytesIO() if id is None: self.id = random.randint(0, 65535) else: @@ -97,7 +102,7 @@ class Renderer(object): self.compress = {} self.section = QUESTION self.counts = [0, 0, 0, 0] - self.output.write('\x00' * 12) + self.output.write(b'\x00' * 12) self.mac = '' def _rollback(self, where): @@ -112,7 +117,7 @@ class Renderer(object): self.output.seek(where) self.output.truncate() keys_to_delete = [] - for k, v in self.compress.iteritems(): + for k, v in self.compress.items(): if v >= where: keys_to_delete.append(k) for k in keys_to_delete: @@ -218,13 +223,13 @@ class Renderer(object): """ # make sure the EDNS version in ednsflags agrees with edns - ednsflags &= 0xFF00FFFFL + ednsflags &= long(0xFF00FFFF) ednsflags |= (edns << 16) self._set_section(ADDITIONAL) before = self.output.tell() self.output.write(struct.pack('!BHHIH', 0, dns.rdatatype.OPT, payload, ednsflags, 0)) - if not options is None: + if options is not None: lstart = self.output.tell() for opt in options: stuff = struct.pack("!HH", opt.otype, 0) diff --git a/dns/resolver.py b/dns/resolver.py index 1835cde4..2b63792a 100644 --- a/dns/resolver.py +++ b/dns/resolver.py @@ -40,16 +40,19 @@ import dns.rdataclass import dns.rdatatype import dns.reversename import dns.tsig +from ._compat import xrange, string_types if sys.platform == 'win32': import _winreg + class NXDOMAIN(dns.exception.DNSException): + """The DNS query name does not exist.""" supp_kwargs = set(['qname']) def __str__(self): - if not 'qname' in self.kwargs: + if 'qname' not in self.kwargs: return super(NXDOMAIN, self).__str__() qname = self.kwargs['qname'] @@ -62,7 +65,9 @@ class NXDOMAIN(dns.exception.DNSException): qname = qname[0] return "%s: %s" % (msg, (str(qname))) + class YXDOMAIN(dns.exception.DNSException): + """The DNS query name is too long after DNAME substitution.""" # The definition of the Timeout exception has moved from here to the @@ -73,6 +78,7 @@ Timeout = dns.exception.Timeout class NoAnswer(dns.exception.DNSException): + """The DNS response does not contain an answer to the question.""" fmt = '%s: {query}' % __doc__[:-1] supp_kwargs = set(['response']) @@ -81,7 +87,9 @@ class NoAnswer(dns.exception.DNSException): return super(NoAnswer, self)._fmt_kwargs( query=kwargs['response'].question) + class NoNameservers(dns.exception.DNSException): + """All nameservers failed to answer the query. @param errors: list of servers and respective errors @@ -103,15 +111,22 @@ class NoNameservers(dns.exception.DNSException): class NotAbsolute(dns.exception.DNSException): + """An absolute domain name is required but a relative name was provided.""" + class NoRootSOA(dns.exception.DNSException): + """There is no SOA RR at the DNS root name. This should never happen!""" + class NoMetaqueries(dns.exception.DNSException): + """DNS metaqueries are not allowed.""" + class Answer(object): + """DNS stub resolver answer Instances of this class bundle up the result of a successful DNS @@ -141,6 +156,7 @@ class Answer(object): @ivar canonical_name: The canonical name of the query name @type canonical_name: dns.name.Name object """ + def __init__(self, qname, rdtype, rdclass, response, raise_on_no_answer=True): self.qname = qname @@ -184,7 +200,7 @@ class Answer(object): # of qname. try: srrset = response.find_rrset(response.authority, qname, - rdclass, dns.rdatatype.SOA) + rdclass, dns.rdatatype.SOA) if min_ttl == -1 or srrset.ttl < min_ttl: min_ttl = srrset.ttl if srrset[0].minimum < min_ttl: @@ -229,7 +245,9 @@ class Answer(object): def __delslice__(self, i, j): del self.rrset[i:j] + class Cache(object): + """Simple DNS answer cache. @ivar data: A dictionary of cached data @@ -261,7 +279,7 @@ class Cache(object): now = time.time() if self.next_cleaning <= now: keys_to_delete = [] - for (k, v) in self.data.iteritems(): + for (k, v) in self.data.items(): if v.expiration <= now: keys_to_delete.append(k) for k in keys_to_delete: @@ -316,8 +334,8 @@ class Cache(object): try: self.lock.acquire() - if not key is None: - if self.data.has_key(key): + if key is not None: + if key in self.data: del self.data[key] else: self.data = {} @@ -325,9 +343,12 @@ class Cache(object): finally: self.lock.release() + class LRUCacheNode(object): + """LRUCache node. """ + def __init__(self, key, value): self.key = key self.value = value @@ -350,7 +371,9 @@ class LRUCacheNode(object): self.next.prev = self.prev self.prev.next = self.next + class LRUCache(object): + """Bounded least-recently-used DNS answer cache. This cache is better than the simple cache (above) if you're @@ -370,7 +393,8 @@ class LRUCache(object): def __init__(self, max_size=100000): """Initialize a DNS cache. - @param max_size: The maximum number of nodes to cache; the default is 100000. Must be > 1. + @param max_size: The maximum number of nodes to cache; the default is + 100000. Must be > 1. @type max_size: int """ self.data = {} @@ -418,7 +442,7 @@ class LRUCache(object): try: self.lock.acquire() node = self.data.get(key) - if not node is None: + if node is not None: node.unlink() del self.data[node.key] while len(self.data) >= self.max_size: @@ -442,9 +466,9 @@ class LRUCache(object): """ try: self.lock.acquire() - if not key is None: + if key is not None: node = self.data.get(key) - if not node is None: + if node is not None: node.unlink() del self.data[node.key] else: @@ -458,7 +482,9 @@ class LRUCache(object): finally: self.lock.release() + class Resolver(object): + """DNS stub resolver @ivar domain: The domain of this host @@ -492,7 +518,8 @@ class Resolver(object): @type ednsflags: int @ivar payload: The EDNS payload size. The default is 0. @type payload: int - @ivar flags: The message flags to use. The default is None (i.e. not overwritten) + @ivar flags: The message flags to use. The default is None (i.e. not + overwritten) @type flags: int @ivar cache: The cache to use. The default is None. @type cache: dns.resolver.Cache object @@ -500,6 +527,7 @@ class Resolver(object): The default is 'false'. @type retry_servfail: bool """ + def __init__(self, filename='/etc/resolv.conf', configure=True): """Initialize a resolver instance. @@ -547,7 +575,7 @@ class Resolver(object): """Process f as a file in the /etc/resolv.conf format. If f is a string, it is used as the name of the file to open; otherwise it is treated as the file itself.""" - if isinstance(f, str) or isinstance(f, unicode): + if isinstance(f, string_types): try: f = open(f, 'r') except IOError: @@ -606,7 +634,7 @@ class Resolver(object): split_char = self._determine_split_char(nameservers) ns_list = nameservers.split(split_char) for ns in ns_list: - if not ns in self.nameservers: + if ns not in self.nameservers: self.nameservers.append(ns) def _config_win32_domain(self, domain): @@ -621,7 +649,7 @@ class Resolver(object): split_char = self._determine_split_char(search) search_list = search.split(split_char) for s in search_list: - if not s in self.search: + if s not in self.search: self.search.append(dns.name.from_text(s)) def _config_win32_fromkey(self, key): @@ -704,59 +732,59 @@ class Resolver(object): lm.Close() def _win32_is_nic_enabled(self, lm, guid, interface_key): - # Look in the Windows Registry to determine whether the network - # interface corresponding to the given guid is enabled. - # - # (Code contributed by Paul Marks, thanks!) - # - try: - # This hard-coded location seems to be consistent, at least - # from Windows 2000 through Vista. - connection_key = _winreg.OpenKey( - lm, - r'SYSTEM\CurrentControlSet\Control\Network' - r'\{4D36E972-E325-11CE-BFC1-08002BE10318}' - r'\%s\Connection' % guid) - - try: - # The PnpInstanceID points to a key inside Enum - (pnp_id, ttype) = _winreg.QueryValueEx( - connection_key, 'PnpInstanceID') - - if ttype != _winreg.REG_SZ: - raise ValueError - - device_key = _winreg.OpenKey( - lm, r'SYSTEM\CurrentControlSet\Enum\%s' % pnp_id) - - try: - # Get ConfigFlags for this device - (flags, ttype) = _winreg.QueryValueEx( - device_key, 'ConfigFlags') - - if ttype != _winreg.REG_DWORD: - raise ValueError - - # Based on experimentation, bit 0x1 indicates that the - # device is disabled. - return not (flags & 0x1) - - finally: - device_key.Close() - finally: - connection_key.Close() - except (EnvironmentError, ValueError): - # Pre-vista, enabled interfaces seem to have a non-empty - # NTEContextList; this was how dnspython detected enabled - # nics before the code above was contributed. We've retained - # the old method since we don't know if the code above works - # on Windows 95/98/ME. - try: - (nte, ttype) = _winreg.QueryValueEx(interface_key, - 'NTEContextList') - return nte is not None - except WindowsError: - return False + # Look in the Windows Registry to determine whether the network + # interface corresponding to the given guid is enabled. + # + # (Code contributed by Paul Marks, thanks!) + # + try: + # This hard-coded location seems to be consistent, at least + # from Windows 2000 through Vista. + connection_key = _winreg.OpenKey( + lm, + r'SYSTEM\CurrentControlSet\Control\Network' + r'\{4D36E972-E325-11CE-BFC1-08002BE10318}' + r'\%s\Connection' % guid) + + try: + # The PnpInstanceID points to a key inside Enum + (pnp_id, ttype) = _winreg.QueryValueEx( + connection_key, 'PnpInstanceID') + + if ttype != _winreg.REG_SZ: + raise ValueError + + device_key = _winreg.OpenKey( + lm, r'SYSTEM\CurrentControlSet\Enum\%s' % pnp_id) + + try: + # Get ConfigFlags for this device + (flags, ttype) = _winreg.QueryValueEx( + device_key, 'ConfigFlags') + + if ttype != _winreg.REG_DWORD: + raise ValueError + + # Based on experimentation, bit 0x1 indicates that the + # device is disabled. + return not (flags & 0x1) + + finally: + device_key.Close() + finally: + connection_key.Close() + except (EnvironmentError, ValueError): + # Pre-vista, enabled interfaces seem to have a non-empty + # NTEContextList; this was how dnspython detected enabled + # nics before the code above was contributed. We've retained + # the old method since we don't know if the code above works + # on Windows 95/98/ME. + try: + (nte, ttype) = _winreg.QueryValueEx(interface_key, + 'NTEContextList') + return nte is not None + except WindowsError: + return False def _compute_timeout(self, start): now = time.time() @@ -791,7 +819,8 @@ class Resolver(object): @type rdclass: int or string @param tcp: use TCP to make the query (default is False). @type tcp: bool - @param source: bind to this IP address (defaults to machine default IP). + @param source: bind to this IP address (defaults to machine default + IP). @type source: IP address in dotted quad notation @param raise_on_no_answer: raise NoAnswer if there's no answer (defaults is True). @@ -808,13 +837,13 @@ class Resolver(object): @raises NoNameservers: no non-broken nameservers are available to answer the question.""" - if isinstance(qname, (str, unicode)): + if isinstance(qname, string_types): qname = dns.name.from_text(qname, None) - if isinstance(rdtype, (str, unicode)): + if isinstance(rdtype, string_types): rdtype = dns.rdatatype.from_text(rdtype) if dns.rdatatype.is_metatype(rdtype): raise NoMetaqueries - if isinstance(rdclass, (str, unicode)): + if isinstance(rdclass, string_types): rdclass = dns.rdataclass.from_text(rdclass) if dns.rdataclass.is_metaclass(rdclass): raise NoMetaqueries @@ -834,13 +863,13 @@ class Resolver(object): for qname in qnames_to_try: if self.cache: answer = self.cache.get((qname, rdtype, rdclass)) - if not answer is None: + if answer is not None: if answer.rrset is None and raise_on_no_answer: raise NoAnswer else: return answer request = dns.message.make_query(qname, rdtype, rdclass) - if not self.keyname is None: + if self.keyname is not None: request.use_tsig(self.keyring, self.keyname, algorithm=self.keyalgorithm) request.use_edns(self.edns, self.ednsflags, self.payload) @@ -877,10 +906,11 @@ class Resolver(object): # Response truncated; retry with TCP. tcp_attempt = True timeout = self._compute_timeout(start) - response = dns.query.tcp(request, nameserver, - timeout, port, - source=source, - source_port=source_port) + response = \ + dns.query.tcp(request, nameserver, + timeout, port, + source=source, + source_port=source_port) except (socket.error, dns.exception.Timeout) as ex: # # Communication failure or timeout. Go to the @@ -928,7 +958,7 @@ class Resolver(object): response)) raise ex if rcode == dns.rcode.NOERROR or \ - rcode == dns.rcode.NXDOMAIN: + rcode == dns.rcode.NXDOMAIN: break # # We got a response, but we're not happy with the @@ -940,7 +970,7 @@ class Resolver(object): errors.append((nameserver, tcp_attempt, port, dns.rcode.to_text(rcode), response)) response = None - if not response is None: + if response is not None: break # # All nameservers failed! @@ -1013,6 +1043,7 @@ class Resolver(object): default_resolver = None + def get_default_resolver(): """Get the default resolver, initializing it if necessary.""" global default_resolver @@ -1020,6 +1051,7 @@ def get_default_resolver(): default_resolver = Resolver() return default_resolver + def query(qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN, tcp=False, source=None, raise_on_no_answer=True, source_port=0): @@ -1032,6 +1064,7 @@ def query(qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN, return get_default_resolver().query(qname, rdtype, rdclass, tcp, source, raise_on_no_answer, source_port) + def zone_for_name(name, rdclass=dns.rdataclass.IN, tcp=False, resolver=None): """Find the name of the zone which contains the specified name. @@ -1045,7 +1078,7 @@ def zone_for_name(name, rdclass=dns.rdataclass.IN, tcp=False, resolver=None): @type resolver: dns.resolver.Resolver object or None @rtype: dns.name.Name""" - if isinstance(name, (str, unicode)): + if isinstance(name, string_types): name = dns.name.from_text(name, dns.name.root) if resolver is None: resolver = get_default_resolver() @@ -1070,9 +1103,9 @@ def zone_for_name(name, rdclass=dns.rdataclass.IN, tcp=False, resolver=None): # _protocols_for_socktype = { - socket.SOCK_DGRAM : [socket.SOL_UDP], - socket.SOCK_STREAM : [socket.SOL_TCP], - } + socket.SOCK_DGRAM: [socket.SOL_UDP], + socket.SOCK_STREAM: [socket.SOL_TCP], +} _resolver = None _original_getaddrinfo = socket.getaddrinfo @@ -1082,9 +1115,10 @@ _original_gethostbyname = socket.gethostbyname _original_gethostbyname_ex = socket.gethostbyname_ex _original_gethostbyaddr = socket.gethostbyaddr + def _getaddrinfo(host=None, service=None, family=socket.AF_UNSPEC, socktype=0, proto=0, flags=0): - if flags & (socket.AI_ADDRCONFIG|socket.AI_V4MAPPED) != 0: + if flags & (socket.AI_ADDRCONFIG | socket.AI_V4MAPPED) != 0: raise NotImplementedError if host is None and service is None: raise socket.gaierror(socket.EAI_NONAME) @@ -1119,7 +1153,6 @@ def _getaddrinfo(host=None, service=None, family=socket.AF_UNSPEC, socktype=0, except: if flags & socket.AI_NUMERICHOST == 0: try: - qname = None if family == socket.AF_INET6 or family == socket.AF_UNSPEC: v6 = _resolver.query(host, dns.rdatatype.AAAA, raise_on_no_answer=False) @@ -1182,6 +1215,7 @@ def _getaddrinfo(host=None, service=None, family=socket.AF_UNSPEC, socktype=0, raise socket.gaierror(socket.EAI_NONAME) return tuples + def _getnameinfo(sockaddr, flags=0): host = sockaddr[0] port = sockaddr[1] @@ -1221,6 +1255,7 @@ def _getnameinfo(sockaddr, flags=0): service = socket.getservbyport(port, pname) return (hostname, service) + def _getfqdn(name=None): if name is None: name = socket.gethostname() @@ -1229,23 +1264,26 @@ def _getfqdn(name=None): except: return name + def _gethostbyname(name): return _gethostbyname_ex(name)[2][0] + def _gethostbyname_ex(name): aliases = [] addresses = [] tuples = _getaddrinfo(name, 0, socket.AF_INET, socket.SOCK_STREAM, - socket.SOL_TCP, socket.AI_CANONNAME) + socket.SOL_TCP, socket.AI_CANONNAME) canonical = tuples[0][3] for item in tuples: addresses.append(item[4][0]) # XXX we just ignore aliases return (canonical, aliases, addresses) + def _gethostbyaddr(ip): try: - addr = dns.ipv6.inet_aton(ip) + dns.ipv6.inet_aton(ip) sockaddr = (ip, 80, 0, 0) family = socket.AF_INET6 except: @@ -1262,6 +1300,7 @@ def _gethostbyaddr(ip): # XXX we just ignore aliases return (canonical, aliases, addresses) + def override_system_resolver(resolver=None): """Override the system resolver routines in the socket module with versions which use dnspython's resolver. @@ -1287,6 +1326,7 @@ def override_system_resolver(resolver=None): socket.gethostbyname_ex = _gethostbyname_ex socket.gethostbyaddr = _gethostbyaddr + def restore_system_resolver(): """Undo the effects of override_system_resolver(). """ diff --git a/dns/reversename.py b/dns/reversename.py index b80c60a0..a27e7050 100644 --- a/dns/reversename.py +++ b/dns/reversename.py @@ -21,6 +21,9 @@ @type ipv6_reverse_domain: dns.name.Name object """ +import binascii +import sys + import dns.name import dns.ipv6 import dns.ipv4 @@ -28,6 +31,7 @@ import dns.ipv4 ipv4_reverse_domain = dns.name.from_text('in-addr.arpa.') ipv6_reverse_domain = dns.name.from_text('ip6.arpa.') + def from_address(text): """Convert an IPv4 or IPv6 address in textual form into a Name object whose value is the reverse-map domain name of the address. @@ -39,17 +43,22 @@ def from_address(text): try: v6 = dns.ipv6.inet_aton(text) if dns.ipv6.is_mapped(v6): - parts = ['%d' % ord(byte) for byte in v6[12:]] + if sys.version_info >= (3,): + parts = ['%d' % byte for byte in v6[12:]] + else: + parts = ['%d' % ord(byte) for byte in v6[12:]] origin = ipv4_reverse_domain else: - parts = list(v6.encode('hex_codec')) + parts = [x for x in str(binascii.hexlify(v6).decode())] origin = ipv6_reverse_domain except: - parts = ['%d' % ord(byte) for byte in dns.ipv4.inet_aton(text)] + parts = ['%d' % + byte for byte in bytearray(dns.ipv4.inet_aton(text))] origin = ipv4_reverse_domain parts.reverse() return dns.name.from_text('.'.join(parts), origin=origin) + def to_address(name): """Convert a reverse map domain name into textual address form. @param name: an IPv4 or IPv6 address in reverse-map form. @@ -60,7 +69,7 @@ def to_address(name): name = name.relativize(ipv4_reverse_domain) labels = list(name.labels) labels.reverse() - text = '.'.join(labels) + text = b'.'.join(labels) # run through inet_aton() to check syntax and make pretty. return dns.ipv4.inet_ntoa(dns.ipv4.inet_aton(text)) elif name.is_subdomain(ipv6_reverse_domain): @@ -71,9 +80,9 @@ def to_address(name): i = 0 l = len(labels) while i < l: - parts.append(''.join(labels[i:i+4])) + parts.append(b''.join(labels[i:i + 4])) i += 4 - text = ':'.join(parts) + text = b':'.join(parts) # run through inet_aton() to check syntax and make pretty. return dns.ipv6.inet_ntoa(dns.ipv6.inet_aton(text)) else: diff --git a/dns/rrset.py b/dns/rrset.py index f6051fea..6ad71da8 100644 --- a/dns/rrset.py +++ b/dns/rrset.py @@ -15,12 +15,16 @@ """DNS RRsets (an RRset is a named rdataset)""" + import dns.name import dns.rdataset import dns.rdataclass import dns.renderer +from ._compat import string_types + class RRset(dns.rdataset.Rdataset): + """A DNS RRset (named rdataset). RRset inherits from Rdataset, and RRsets can be treated as @@ -51,7 +55,7 @@ class RRset(dns.rdataset.Rdataset): ctext = '' else: ctext = '(' + dns.rdatatype.to_text(self.covers) + ')' - if not self.deleting is None: + if self.deleting is not None: dtext = ' delete=' + dns.rdataclass.to_text(self.deleting) else: dtext = '' @@ -122,11 +126,11 @@ def from_text_list(name, ttl, rdclass, rdtype, text_rdatas): @rtype: dns.rrset.RRset object """ - if isinstance(name, (str, unicode)): + if isinstance(name, string_types): name = dns.name.from_text(name, None) - if isinstance(rdclass, (str, unicode)): + if isinstance(rdclass, string_types): rdclass = dns.rdataclass.from_text(rdclass) - if isinstance(rdtype, (str, unicode)): + if isinstance(rdtype, string_types): rdtype = dns.rdatatype.from_text(rdtype) r = RRset(name, rdclass, rdtype) r.update_ttl(ttl) @@ -135,6 +139,7 @@ def from_text_list(name, ttl, rdclass, rdtype, text_rdatas): r.add(rd) return r + def from_text(name, ttl, rdclass, rdtype, *text_rdatas): """Create an RRset with the specified name, TTL, class, and type and with the specified rdatas in text format. @@ -144,6 +149,7 @@ def from_text(name, ttl, rdclass, rdtype, *text_rdatas): return from_text_list(name, ttl, rdclass, rdtype, text_rdatas) + def from_rdata_list(name, ttl, rdatas): """Create an RRset with the specified name and TTL, and with the specified list of rdata objects. @@ -151,7 +157,7 @@ def from_rdata_list(name, ttl, rdatas): @rtype: dns.rrset.RRset object """ - if isinstance(name, (str, unicode)): + if isinstance(name, string_types): name = dns.name.from_text(name, None) if len(rdatas) == 0: @@ -161,10 +167,10 @@ def from_rdata_list(name, ttl, rdatas): if r is None: r = RRset(name, rd.rdclass, rd.rdtype) r.update_ttl(ttl) - first_time = False r.add(rd) return r + def from_rdata(name, ttl, *rdatas): """Create an RRset with the specified name and TTL, and with the specified rdata objects. diff --git a/dns/set.py b/dns/set.py index 14c76a05..f13af5f3 100644 --- a/dns/set.py +++ b/dns/set.py @@ -15,7 +15,9 @@ """A simple Set class.""" + class Set(object): + """A simple set class. Sets are not in Python until 2.3, and rdata are not immutable so @@ -35,7 +37,7 @@ class Set(object): """ self.items = [] - if not items is None: + if items is not None: for item in items: self.add(item) @@ -44,7 +46,7 @@ class Set(object): def add(self, item): """Add an item to the set.""" - if not item in self.items: + if item not in self.items: self.items.append(item) def remove(self, item): @@ -208,10 +210,10 @@ class Set(object): # Yes, this is inefficient but the sets we're dealing with are # usually quite small, so it shouldn't hurt too much. for item in self.items: - if not item in other.items: + if item not in other.items: return False for item in other.items: - if not item in self.items: + if item not in self.items: return False return True @@ -245,7 +247,7 @@ class Set(object): if not isinstance(other, Set): raise ValueError('other must be a Set instance') for item in self.items: - if not item in other.items: + if item not in other.items: return False return True @@ -258,6 +260,6 @@ class Set(object): if not isinstance(other, Set): raise ValueError('other must be a Set instance') for item in other.items: - if not item in self.items: + if item not in self.items: return False return True diff --git a/dns/tokenizer.py b/dns/tokenizer.py index b046f2b7..e5b09adf 100644 --- a/dns/tokenizer.py +++ b/dns/tokenizer.py @@ -15,23 +15,24 @@ """Tokenize DNS master file format""" -import cStringIO +from io import StringIO import sys import dns.exception import dns.name import dns.ttl +from ._compat import long, text_type, binary_type _DELIMITERS = { - ' ' : True, - '\t' : True, - '\n' : True, - ';' : True, - '(' : True, - ')' : True, - '"' : True } + ' ': True, + '\t': True, + '\n': True, + ';': True, + '(': True, + ')': True, + '"': True} -_QUOTING_DELIMITERS = { '"' : True } +_QUOTING_DELIMITERS = {'"': True} EOF = 0 EOL = 1 @@ -41,10 +42,14 @@ QUOTED_STRING = 4 COMMENT = 5 DELIMITER = 6 + class UngetBufferFull(dns.exception.DNSException): + """An attempt was made to unget a token when the unget buffer was full.""" + class Token(object): + """A DNS master file format token. @ivar ttype: The token type @@ -153,7 +158,9 @@ class Token(object): else: raise IndexError + class Tokenizer(object): + """A DNS master file format tokenizer. A token is a (type, value) tuple, where I{type} is an int, and @@ -195,8 +202,12 @@ class Tokenizer(object): @type filename: string """ - if isinstance(f, (str, unicode)): - f = cStringIO.StringIO(f) + if isinstance(f, text_type): + f = StringIO(f) + if filename is None: + filename = '' + elif isinstance(f, binary_type): + f = StringIO(f.decode()) if filename is None: filename = '' else: @@ -255,7 +266,7 @@ class Tokenizer(object): @raises UngetBufferFull: there is already an ungotten char """ - if not self.ungotten_char is None: + if self.ungotten_char is not None: raise UngetBufferFull self.ungotten_char = c @@ -279,7 +290,7 @@ class Tokenizer(object): return skipped skipped += 1 - def get(self, want_leading = False, want_comment = False): + def get(self, want_leading=False, want_comment=False): """Get the next token. @param want_leading: If True, return a WHITESPACE token if the @@ -293,7 +304,7 @@ class Tokenizer(object): @raises dns.exception.SyntaxError: input was badly formed """ - if not self.ungotten_token is None: + if self.ungotten_token is not None: token = self.ungotten_token self.ungotten_token = None if token.is_whitespace(): @@ -350,7 +361,8 @@ class Tokenizer(object): return Token(COMMENT, token) elif c == '': if self.multiline: - raise dns.exception.SyntaxError('unbalanced parentheses') + raise dns.exception.SyntaxError( + 'unbalanced parentheses') return Token(EOF) elif self.multiline: self.skip_whitespace() @@ -413,7 +425,7 @@ class Tokenizer(object): @raises UngetBufferFull: there is already an ungotten token """ - if not self.ungotten_token is None: + if self.ungotten_token is not None: raise UngetBufferFull self.ungotten_token = token @@ -427,6 +439,8 @@ class Tokenizer(object): raise StopIteration return token + __next__ = next + def __iter__(self): return self @@ -456,7 +470,8 @@ class Tokenizer(object): value = self.get_int() if value < 0 or value > 255: - raise dns.exception.SyntaxError('%d is not an unsigned 8-bit integer' % value) + raise dns.exception.SyntaxError( + '%d is not an unsigned 8-bit integer' % value) return value def get_uint16(self): @@ -469,7 +484,8 @@ class Tokenizer(object): value = self.get_int() if value < 0 or value > 65535: - raise dns.exception.SyntaxError('%d is not an unsigned 16-bit integer' % value) + raise dns.exception.SyntaxError( + '%d is not an unsigned 16-bit integer' % value) return value def get_uint32(self): @@ -486,8 +502,9 @@ class Tokenizer(object): if not token.value.isdigit(): raise dns.exception.SyntaxError('expecting an integer') value = long(token.value) - if value < 0 or value > 4294967296L: - raise dns.exception.SyntaxError('%d is not an unsigned 32-bit integer' % value) + if value < 0 or value > long(4294967296): + raise dns.exception.SyntaxError( + '%d is not an unsigned 32-bit integer' % value) return value def get_string(self, origin=None): @@ -535,7 +552,9 @@ class Tokenizer(object): token = self.get() if not token.is_eol_or_eof(): - raise dns.exception.SyntaxError('expected EOL or EOF, got %d "%s"' % (token.ttype, token.value)) + raise dns.exception.SyntaxError( + 'expected EOL or EOF, got %d "%s"' % (token.ttype, + token.value)) return token.value def get_ttl(self): diff --git a/dns/tsig.py b/dns/tsig.py index 7c16a9b3..55348528 100644 --- a/dns/tsig.py +++ b/dns/tsig.py @@ -23,26 +23,41 @@ import dns.exception import dns.hash import dns.rdataclass import dns.name +from ._compat import long, string_types + class BadTime(dns.exception.DNSException): + """The current time is not within the TSIG's validity time.""" + class BadSignature(dns.exception.DNSException): + """The TSIG signature fails to verify.""" + class PeerError(dns.exception.DNSException): + """Base class for all TSIG errors generated by the remote peer""" + class PeerBadKey(PeerError): + """The peer didn't know the key we used""" + class PeerBadSignature(PeerError): + """The peer didn't like the signature we sent""" + class PeerBadTime(PeerError): + """The peer didn't like the time we sent""" + class PeerBadTruncation(PeerError): + """The peer didn't like amount of truncation in the TSIG we sent""" # TSIG Algorithms @@ -61,6 +76,7 @@ BADKEY = 17 BADTIME = 18 BADTRUNC = 22 + def sign(wire, keyname, secret, time, fudge, original_id, error, other_data, request_mac, ctx=None, multi=False, first=True, algorithm=default_algorithm): @@ -86,9 +102,9 @@ def sign(wire, keyname, secret, time, fudge, original_id, error, ctx.update(keyname.to_digestable()) ctx.update(struct.pack('!H', dns.rdataclass.ANY)) ctx.update(struct.pack('!I', 0)) - long_time = time + 0L - upper_time = (long_time >> 32) & 0xffffL - lower_time = long_time & 0xffffffffL + long_time = time + long(0) + upper_time = (long_time >> 32) & long(0xffff) + lower_time = long_time & long(0xffffffff) time_mac = struct.pack('!HIH', upper_time, lower_time, fudge) pre_mac = algorithm_name + time_mac ol = len(other_data) @@ -112,12 +128,14 @@ def sign(wire, keyname, secret, time, fudge, original_id, error, ctx = None return (tsig_rdata, mac, ctx) + def hmac_md5(wire, keyname, secret, time, fudge, original_id, error, other_data, request_mac, ctx=None, multi=False, first=True, algorithm=default_algorithm): return sign(wire, keyname, secret, time, fudge, original_id, error, other_data, request_mac, ctx, multi, first, algorithm) + def validate(wire, keyname, secret, now, request_mac, tsig_start, tsig_rdata, tsig_rdlen, ctx=None, multi=False, first=True): """Validate the specified TSIG rdata against the other input parameters. @@ -137,13 +155,13 @@ def validate(wire, keyname, secret, now, request_mac, tsig_start, tsig_rdata, (aname, used) = dns.name.from_wire(wire, current) current = current + used (upper_time, lower_time, fudge, mac_size) = \ - struct.unpack("!HIHH", wire[current:current + 10]) - time = ((upper_time + 0L) << 32) + (lower_time + 0L) + struct.unpack("!HIHH", wire[current:current + 10]) + time = ((upper_time + long(0)) << 32) + (lower_time + long(0)) current += 10 mac = wire[current:current + mac_size] current += mac_size (original_id, error, other_size) = \ - struct.unpack("!HHH", wire[current:current + 6]) + struct.unpack("!HHH", wire[current:current + 6]) current += 6 other_data = wire[current:current + other_size] current += other_size @@ -171,23 +189,6 @@ def validate(wire, keyname, secret, now, request_mac, tsig_start, tsig_rdata, raise BadSignature return ctx -_hashes = None - -def _maybe_add_hash(tsig_alg, hash_alg): - try: - _hashes[tsig_alg] = dns.hash.get(hash_alg) - except KeyError: - pass - -def _setup_hashes(): - global _hashes - _hashes = {} - _maybe_add_hash(HMAC_SHA224, 'SHA224') - _maybe_add_hash(HMAC_SHA256, 'SHA256') - _maybe_add_hash(HMAC_SHA384, 'SHA384') - _maybe_add_hash(HMAC_SHA512, 'SHA512') - _maybe_add_hash(HMAC_SHA1, 'SHA1') - _maybe_add_hash(HMAC_MD5, 'MD5') def get_algorithm(algorithm): """Returns the wire format string and the hash module to use for the @@ -197,24 +198,16 @@ def get_algorithm(algorithm): @raises NotImplementedError: I{algorithm} is not supported """ - global _hashes - if _hashes is None: - _setup_hashes() - - if isinstance(algorithm, (str, unicode)): + if isinstance(algorithm, string_types): algorithm = dns.name.from_text(algorithm) - if sys.hexversion < 0x02050200 and \ - (algorithm == HMAC_SHA384 or algorithm == HMAC_SHA512): - raise NotImplementedError("TSIG algorithm " + str(algorithm) + - " requires Python 2.5.2 or later") - try: - return (algorithm.to_digestable(), _hashes[algorithm]) + return (algorithm.to_digestable(), dns.hash.hashes[algorithm]) except KeyError: raise NotImplementedError("TSIG algorithm " + str(algorithm) + " is not supported") + def get_algorithm_and_mac(wire, tsig_rdata, tsig_rdlen): """Return the tsig algorithm for the specified tsig_rdata @raises FormError: The TSIG is badly formed. @@ -223,7 +216,7 @@ def get_algorithm_and_mac(wire, tsig_rdata, tsig_rdlen): (aname, used) = dns.name.from_wire(wire, current) current = current + used (upper_time, lower_time, fudge, mac_size) = \ - struct.unpack("!HIHH", wire[current:current + 10]) + struct.unpack("!HIHH", wire[current:current + 10]) current += 10 mac = wire[current:current + mac_size] current += mac_size diff --git a/dns/tsigkeyring.py b/dns/tsigkeyring.py index 10de4db5..295bac14 100644 --- a/dns/tsigkeyring.py +++ b/dns/tsigkeyring.py @@ -19,11 +19,12 @@ import base64 import dns.name + def from_text(textring): """Convert a dictionary containing (textual DNS name, base64 secret) pairs into a binary keyring which has (dns.name.Name, binary secret) pairs. @rtype: dict""" - + keyring = {} for keytext in textring: keyname = dns.name.from_text(keytext) @@ -31,11 +32,12 @@ def from_text(textring): keyring[keyname] = secret return keyring + def to_text(keyring): """Convert a dictionary containing (dns.name.Name, binary secret) pairs into a text keyring which has (textual DNS name, base64 secret) pairs. @rtype: dict""" - + textring = {} for keyname in keyring: keytext = keyname.to_text() diff --git a/dns/ttl.py b/dns/ttl.py index dc92b214..a27d8251 100644 --- a/dns/ttl.py +++ b/dns/ttl.py @@ -16,10 +16,14 @@ """DNS TTL conversion.""" import dns.exception +from ._compat import long + class BadTTL(dns.exception.SyntaxError): + """DNS TTL value is not well-formed.""" + def from_text(text): """Convert the text form of a TTL to an integer. @@ -36,8 +40,8 @@ def from_text(text): else: if not text[0].isdigit(): raise BadTTL - total = 0L - current = 0L + total = long(0) + current = long(0) for c in text: if c.isdigit(): current *= 10 @@ -45,13 +49,13 @@ def from_text(text): else: c = c.lower() if c == 'w': - total += current * 604800L + total += current * long(604800) elif c == 'd': - total += current * 86400L + total += current * long(86400) elif c == 'h': - total += current * 3600L + total += current * long(3600) elif c == 'm': - total += current * 60L + total += current * long(60) elif c == 's': total += current else: @@ -59,6 +63,6 @@ def from_text(text): current = 0 if not current == 0: raise BadTTL("trailing integer") - if total < 0L or total > 2147483647L: + if total < long(0) or total > long(2147483647): raise BadTTL("TTL should be between 0 and 2^31 - 1 (inclusive)") return total diff --git a/dns/update.py b/dns/update.py index 8b214721..59728d98 100644 --- a/dns/update.py +++ b/dns/update.py @@ -15,6 +15,7 @@ """DNS Dynamic Update Support""" + import dns.message import dns.name import dns.opcode @@ -22,8 +23,11 @@ import dns.rdata import dns.rdataclass import dns.rdataset import dns.tsig +from ._compat import string_types + class Update(dns.message.Message): + def __init__(self, zone, rdclass=dns.rdataclass.IN, keyring=None, keyname=None, keyalgorithm=dns.tsig.default_algorithm): """Initialize a new DNS Update object. @@ -51,15 +55,15 @@ class Update(dns.message.Message): """ super(Update, self).__init__() self.flags |= dns.opcode.to_flags(dns.opcode.UPDATE) - if isinstance(zone, (str, unicode)): + if isinstance(zone, string_types): zone = dns.name.from_text(zone) self.origin = zone - if isinstance(rdclass, (str, unicode)): + if isinstance(rdclass, string_types): rdclass = dns.rdataclass.from_text(rdclass) self.zone_rdclass = rdclass self.find_rrset(self.question, self.origin, rdclass, dns.rdatatype.SOA, create=True, force_unique=True) - if not keyring is None: + if keyring is not None: self.use_tsig(keyring, keyname, algorithm=keyalgorithm) def _add_rr(self, name, ttl, rd, deleting=None, section=None): @@ -85,7 +89,7 @@ class Update(dns.message.Message): - ttl, rdtype, string...""" - if isinstance(name, (str, unicode)): + if isinstance(name, string_types): name = dns.name.from_text(name, None) if isinstance(args[0], dns.rdataset.Rdataset): for rds in args: @@ -103,7 +107,7 @@ class Update(dns.message.Message): self._add_rr(name, ttl, rd, section=section) else: rdtype = args.pop(0) - if isinstance(rdtype, (str, unicode)): + if isinstance(rdtype, string_types): rdtype = dns.rdatatype.from_text(rdtype) if replace: self.delete(name, rdtype) @@ -135,12 +139,12 @@ class Update(dns.message.Message): - rdtype, [string...]""" - if isinstance(name, (str, unicode)): + if isinstance(name, string_types): name = dns.name.from_text(name, None) if len(args) == 0: - rrset = self.find_rrset(self.authority, name, dns.rdataclass.ANY, - dns.rdatatype.ANY, dns.rdatatype.NONE, - dns.rdatatype.ANY, True, True) + self.find_rrset(self.authority, name, dns.rdataclass.ANY, + dns.rdatatype.ANY, dns.rdatatype.NONE, + dns.rdatatype.ANY, True, True) elif isinstance(args[0], dns.rdataset.Rdataset): for rds in args: for rd in rds: @@ -152,14 +156,14 @@ class Update(dns.message.Message): self._add_rr(name, 0, rd, dns.rdataclass.NONE) else: rdtype = args.pop(0) - if isinstance(rdtype, (str, unicode)): + if isinstance(rdtype, string_types): rdtype = dns.rdatatype.from_text(rdtype) if len(args) == 0: - rrset = self.find_rrset(self.authority, name, - self.zone_rdclass, rdtype, - dns.rdatatype.NONE, - dns.rdataclass.ANY, - True, True) + self.find_rrset(self.authority, name, + self.zone_rdclass, rdtype, + dns.rdatatype.NONE, + dns.rdataclass.ANY, + True, True) else: for s in args: rd = dns.rdata.from_text(self.zone_rdclass, rdtype, s, @@ -193,16 +197,16 @@ class Update(dns.message.Message): - rdtype, string...""" - if isinstance(name, (str, unicode)): + if isinstance(name, string_types): name = dns.name.from_text(name, None) if len(args) == 0: - rrset = self.find_rrset(self.answer, name, - dns.rdataclass.ANY, dns.rdatatype.ANY, - dns.rdatatype.NONE, None, - True, True) + self.find_rrset(self.answer, name, + dns.rdataclass.ANY, dns.rdatatype.ANY, + dns.rdatatype.NONE, None, + True, True) elif isinstance(args[0], dns.rdataset.Rdataset) or \ - isinstance(args[0], dns.rdata.Rdata) or \ - len(args) > 1: + isinstance(args[0], dns.rdata.Rdata) or \ + len(args) > 1: if not isinstance(args[0], dns.rdataset.Rdataset): # Add a 0 TTL args = list(args) @@ -210,31 +214,31 @@ class Update(dns.message.Message): self._add(False, self.answer, name, *args) else: rdtype = args[0] - if isinstance(rdtype, (str, unicode)): + if isinstance(rdtype, string_types): rdtype = dns.rdatatype.from_text(rdtype) - rrset = self.find_rrset(self.answer, name, - dns.rdataclass.ANY, rdtype, - dns.rdatatype.NONE, None, - True, True) + self.find_rrset(self.answer, name, + dns.rdataclass.ANY, rdtype, + dns.rdatatype.NONE, None, + True, True) def absent(self, name, rdtype=None): """Require that an owner name (and optionally an rdata type) does not exist as a prerequisite to the execution of the update.""" - if isinstance(name, (str, unicode)): + if isinstance(name, string_types): name = dns.name.from_text(name, None) if rdtype is None: - rrset = self.find_rrset(self.answer, name, - dns.rdataclass.NONE, dns.rdatatype.ANY, - dns.rdatatype.NONE, None, - True, True) + self.find_rrset(self.answer, name, + dns.rdataclass.NONE, dns.rdatatype.ANY, + dns.rdatatype.NONE, None, + True, True) else: - if isinstance(rdtype, (str, unicode)): + if isinstance(rdtype, string_types): rdtype = dns.rdatatype.from_text(rdtype) - rrset = self.find_rrset(self.answer, name, - dns.rdataclass.NONE, rdtype, - dns.rdatatype.NONE, None, - True, True) + self.find_rrset(self.answer, name, + dns.rdataclass.NONE, rdtype, + dns.rdatatype.NONE, None, + True, True) def to_wire(self, origin=None, max_size=65535): """Return a string containing the update in DNS compressed wire diff --git a/dns/version.py b/dns/version.py index c3822795..8279fef5 100644 --- a/dns/version.py +++ b/dns/version.py @@ -31,4 +31,4 @@ else: (MAJOR, MINOR, MICRO, RELEASELEVEL, SERIAL) hexversion = MAJOR << 24 | MINOR << 16 | MICRO << 8 | RELEASELEVEL << 4 | \ - SERIAL + SERIAL diff --git a/dns/wiredata.py b/dns/wiredata.py index 1d14bd32..b381f7b9 100644 --- a/dns/wiredata.py +++ b/dns/wiredata.py @@ -15,9 +15,9 @@ """DNS Wire Data Helper""" -import sys import dns.exception +from ._compat import binary_type, string_types # Figure out what constant python passes for an unspecified slice bound. # It's supposed to be sys.maxint, yet on 64-bit windows sys.maxint is 2^31 - 1 @@ -25,19 +25,26 @@ import dns.exception # extra comparisons, duplicating code, or weakening WireData, we just figure # out what constant Python will use. + class _SliceUnspecifiedBound(str): + def __getslice__(self, i, j): return j _unspecified_bound = _SliceUnspecifiedBound('')[1:] -class WireData(str): + +class WireData(binary_type): # WireData is a string with stricter slicing + def __getitem__(self, key): try: - return WireData(super(WireData, self).__getitem__(key)) + if isinstance(key, slice): + return WireData(super(WireData, self).__getitem__(key)) + return bytearray(self.unwrap())[key] except IndexError: raise dns.exception.FormError + def __getslice__(self, i, j): try: if j == _unspecified_bound: @@ -53,6 +60,7 @@ class WireData(str): return WireData(super(WireData, self).__getslice__(i, j)) except IndexError: raise dns.exception.FormError + def __iter__(self): i = 0 while 1: @@ -61,11 +69,16 @@ class WireData(str): i += 1 except dns.exception.FormError: raise StopIteration + def unwrap(self): - return str(self) + return binary_type(self) + def maybe_wrap(wire): - if not isinstance(wire, WireData): - return WireData(wire) - else: + if isinstance(wire, WireData): return wire + elif isinstance(wire, binary_type): + return WireData(wire) + elif isinstance(wire, string_types): + return WireData(wire.encode()) + raise ValueError("unhandled type %s" % type(wire)) diff --git a/dns/zone.py b/dns/zone.py index 453bbb9c..2b7826c3 100644 --- a/dns/zone.py +++ b/dns/zone.py @@ -19,6 +19,7 @@ from __future__ import generators import sys import re +from io import BytesIO import dns.exception import dns.name @@ -30,25 +31,31 @@ import dns.rrset import dns.tokenizer import dns.ttl import dns.grange +from ._compat import string_types, text_type -try: - from cStringIO import StringIO -except ImportError: - from io import StringIO class BadZone(dns.exception.DNSException): + """The DNS zone is malformed.""" + class NoSOA(BadZone): + """The DNS zone has no SOA RR at its origin.""" + class NoNS(BadZone): + """The DNS zone has no NS RRset at its origin.""" + class UnknownOrigin(BadZone): + """The DNS zone's origin is unknown.""" + class Zone(object): + """A DNS zone. A Zone is a mapping from names to nodes. The zone object may be @@ -110,13 +117,14 @@ class Zone(object): return not self.__eq__(other) def _validate_name(self, name): - if isinstance(name, (str, unicode)): + if isinstance(name, string_types): name = dns.name.from_text(name, None) elif not isinstance(name, dns.name.Name): raise KeyError("name parameter must be convertable to a DNS name") if name.is_absolute(): if not name.is_subdomain(self.origin): - raise KeyError("name parameter must be a subdomain of the zone origin") + raise KeyError( + "name parameter must be a subdomain of the zone origin") if self.relativize: name = name.relativize(self.origin) return name @@ -148,12 +156,11 @@ class Zone(object): def values(self): return self.nodes.values() - def iteritems(self): - return self.nodes.iteritems() - def items(self): return self.nodes.items() + iteritems = items + def get(self, key): key = self._validate_name(key) return self.nodes.get(key) @@ -208,7 +215,7 @@ class Zone(object): """ name = self._validate_name(name) - if self.nodes.has_key(name): + if name in self.nodes: del self.nodes[name] def find_rdataset(self, name, rdtype, covers=dns.rdatatype.NONE, @@ -240,9 +247,9 @@ class Zone(object): """ name = self._validate_name(name) - if isinstance(rdtype, (str, unicode)): + if isinstance(rdtype, string_types): rdtype = dns.rdatatype.from_text(rdtype) - if isinstance(covers, (str, unicode)): + if isinstance(covers, string_types): covers = dns.rdatatype.from_text(covers) node = self.find_node(name, create) return node.find_rdataset(self.rdclass, rdtype, covers, create) @@ -303,12 +310,12 @@ class Zone(object): """ name = self._validate_name(name) - if isinstance(rdtype, (str, unicode)): + if isinstance(rdtype, string_types): rdtype = dns.rdatatype.from_text(rdtype) - if isinstance(covers, (str, unicode)): + if isinstance(covers, string_types): covers = dns.rdatatype.from_text(covers) node = self.get_node(name) - if not node is None: + if node is not None: node.delete_rdataset(self.rdclass, rdtype, covers) if len(node) == 0: self.delete_node(name) @@ -366,9 +373,9 @@ class Zone(object): """ name = self._validate_name(name) - if isinstance(rdtype, (str, unicode)): + if isinstance(rdtype, string_types): rdtype = dns.rdatatype.from_text(rdtype) - if isinstance(covers, (str, unicode)): + if isinstance(covers, string_types): covers = dns.rdatatype.from_text(covers) rdataset = self.nodes[name].find_rdataset(self.rdclass, rdtype, covers) rrset = dns.rrset.RRset(name, self.rdclass, rdtype, covers) @@ -422,9 +429,9 @@ class Zone(object): @type covers: int or string """ - if isinstance(rdtype, (str, unicode)): + if isinstance(rdtype, string_types): rdtype = dns.rdatatype.from_text(rdtype) - if isinstance(covers, (str, unicode)): + if isinstance(covers, string_types): covers = dns.rdatatype.from_text(covers) for (name, node) in self.iteritems(): for rds in node: @@ -445,9 +452,9 @@ class Zone(object): @type covers: int or string """ - if isinstance(rdtype, (str, unicode)): + if isinstance(rdtype, string_types): rdtype = dns.rdatatype.from_text(rdtype) - if isinstance(covers, (str, unicode)): + if isinstance(covers, string_types): covers = dns.rdatatype.from_text(covers) for (name, node) in self.iteritems(): for rds in node: @@ -474,31 +481,32 @@ class Zone(object): @type nl: string or None """ - if sys.hexversion >= 0x02030000: - # allow Unicode filenames - str_type = basestring - else: - str_type = str + str_type = string_types + if nl is None: - opts = 'w' + opts = 'wb' else: opts = 'wb' + if isinstance(f, str_type): - f = file(f, opts) + f = open(f, opts) want_close = True else: want_close = False try: if sorted: - names = self.keys() + names = list(self.keys()) names.sort() else: names = self.iterkeys() for n in names: l = self[n].to_text(n, origin=self.origin, relativize=relativize) + if isinstance(l, text_type): + l = l.encode() if nl is None: - print >> f, l + f.write(l) + f.write('\n') else: f.write(l) f.write(nl) @@ -521,7 +529,7 @@ class Zone(object): LF on POSIX, CRLF on Windows, CR on Macintosh). @type nl: string or None """ - temp_buffer = StringIO() + temp_buffer = BytesIO() self.to_file(temp_buffer, sorted, relativize, nl) return_value = temp_buffer.getvalue() temp_buffer.close() @@ -545,6 +553,7 @@ class Zone(object): class _MasterReader(object): + """Read a DNS master file @ivar tok: The tokenizer @@ -573,7 +582,7 @@ class _MasterReader(object): def __init__(self, tok, origin, rdclass, relativize, zone_factory=Zone, allow_include=False, check_origin=True): - if isinstance(origin, (str, unicode)): + if isinstance(origin, string_types): origin = dns.name.from_text(origin) self.tok = tok self.current_origin = origin @@ -597,9 +606,10 @@ class _MasterReader(object): # Name if self.current_origin is None: raise UnknownOrigin - token = self.tok.get(want_leading = True) + token = self.tok.get(want_leading=True) if not token.is_whitespace(): - self.last_name = dns.name.from_text(token.value, self.current_origin) + self.last_name = dns.name.from_text( + token.value, self.current_origin) else: token = self.tok.get() if token.is_eol_or_eof(): @@ -639,7 +649,8 @@ class _MasterReader(object): try: rdtype = dns.rdatatype.from_text(token.value) except: - raise dns.exception.SyntaxError("unknown rdatatype '%s'" % token.value) + raise dns.exception.SyntaxError( + "unknown rdatatype '%s'" % token.value) n = self.zone.nodes.get(name) if n is None: n = self.zone.node_factory() @@ -658,7 +669,8 @@ class _MasterReader(object): # We convert them to syntax errors so that we can emit # helpful filename:line info. (ty, va) = sys.exc_info()[:2] - raise dns.exception.SyntaxError("caught exception %s: %s" % (str(ty), str(va))) + raise dns.exception.SyntaxError( + "caught exception %s: %s" % (str(ty), str(va))) rd.choose_relativity(self.zone.origin, self.relativize) covers = rd.covers() @@ -760,7 +772,7 @@ class _MasterReader(object): raise dns.exception.SyntaxError except: raise dns.exception.SyntaxError("unknown rdatatype '%s'" % - token.value) + token.value) # lhs (required) try: @@ -768,28 +780,26 @@ class _MasterReader(object): except: raise dns.exception.SyntaxError - lmod, lsign, loffset, lwidth, lbase = self._parse_modify(lhs) rmod, rsign, roffset, rwidth, rbase = self._parse_modify(rhs) for i in range(start, stop + 1, step): # +1 because bind is inclusive and python is exclusive - if lsign == '+': + if lsign == u'+': lindex = i + int(loffset) - elif lsign == '-': + elif lsign == u'-': lindex = i - int(loffset) - if rsign == '-': + if rsign == u'-': rindex = i - int(roffset) - elif rsign == '+': + elif rsign == u'+': rindex = i + int(roffset) lzfindex = str(lindex).zfill(int(lwidth)) rzfindex = str(rindex).zfill(int(rwidth)) - - name = lhs.replace('$%s' % (lmod), lzfindex) - rdata = rhs.replace('$%s' % (rmod), rzfindex) + name = lhs.replace(u'$%s' % (lmod), lzfindex) + rdata = rhs.replace(u'$%s' % (rmod), rzfindex) self.last_name = dns.name.from_text(name, self.current_origin) name = self.last_name @@ -818,7 +828,7 @@ class _MasterReader(object): # helpful filename:line info. (ty, va) = sys.exc_info()[:2] raise dns.exception.SyntaxError("caught exception %s: %s" % - (str(ty), str(va))) + (str(ty), str(va))) rd.choose_relativity(self.zone.origin, self.relativize) covers = rd.covers() @@ -836,7 +846,7 @@ class _MasterReader(object): while 1: token = self.tok.get(True, True) if token.is_eof(): - if not self.current_file is None: + if self.current_file is not None: self.current_file.close() if len(self.saved_state) > 0: (self.tok, @@ -851,29 +861,31 @@ class _MasterReader(object): elif token.is_comment(): self.tok.get_eol() continue - elif token.value[0] == '$': - u = token.value.upper() - if u == '$TTL': + elif token.value[0] == u'$': + c = token.value.upper() + if c == u'$TTL': token = self.tok.get() if not token.is_identifier(): raise dns.exception.SyntaxError("bad $TTL") self.ttl = dns.ttl.from_text(token.value) self.tok.get_eol() - elif u == '$ORIGIN': + elif c == u'$ORIGIN': self.current_origin = self.tok.get_name() self.tok.get_eol() if self.zone.origin is None: self.zone.origin = self.current_origin - elif u == '$INCLUDE' and self.allow_include: + elif c == u'$INCLUDE' and self.allow_include: token = self.tok.get() filename = token.value token = self.tok.get() if token.is_identifier(): - new_origin = dns.name.from_text(token.value, \ - self.current_origin) + new_origin =\ + dns.name.from_text(token.value, + self.current_origin) self.tok.get_eol() elif not token.is_eol_or_eof(): - raise dns.exception.SyntaxError("bad origin in $INCLUDE") + raise dns.exception.SyntaxError( + "bad origin in $INCLUDE") else: new_origin = self.current_origin self.saved_state.append((self.tok, @@ -881,29 +893,32 @@ class _MasterReader(object): self.last_name, self.current_file, self.ttl)) - self.current_file = file(filename, 'r') + self.current_file = open(filename, 'r') self.tok = dns.tokenizer.Tokenizer(self.current_file, filename) self.current_origin = new_origin - elif u == '$GENERATE': + elif c == u'$GENERATE': self._generate_line() else: - raise dns.exception.SyntaxError("Unknown master file directive '" + u + "'") + raise dns.exception.SyntaxError( + "Unknown master file directive '" + c + "'") continue self.tok.unget(token) self._rr_line() - except dns.exception.SyntaxError, detail: + except dns.exception.SyntaxError as detail: (filename, line_number) = self.tok.where() if detail is None: detail = "syntax error" - raise dns.exception.SyntaxError("%s:%d: %s" % (filename, line_number, detail)) + raise dns.exception.SyntaxError( + "%s:%d: %s" % (filename, line_number, detail)) # Now that we're done reading, do some basic checking of the zone. if self.check_origin: self.zone.check_origin() -def from_text(text, origin = None, rdclass = dns.rdataclass.IN, - relativize = True, zone_factory=Zone, filename=None, + +def from_text(text, origin=None, rdclass=dns.rdataclass.IN, + relativize=True, zone_factory=Zone, filename=None, allow_include=False, check_origin=True): """Build a zone object from a master file format string. @@ -945,8 +960,9 @@ def from_text(text, origin = None, rdclass = dns.rdataclass.IN, reader.read() return reader.zone -def from_file(f, origin = None, rdclass = dns.rdataclass.IN, - relativize = True, zone_factory=Zone, filename=None, + +def from_file(f, origin=None, rdclass=dns.rdataclass.IN, + relativize=True, zone_factory=Zone, filename=None, allow_include=True, check_origin=True): """Read a master file and build a zone object. @@ -976,17 +992,13 @@ def from_file(f, origin = None, rdclass = dns.rdataclass.IN, @rtype: dns.zone.Zone object """ - if sys.hexversion >= 0x02030000: - # allow Unicode filenames; turn on universal newline support - str_type = basestring - opts = 'rU' - else: - str_type = str - opts = 'r' + str_type = string_types + opts = 'rU' + if isinstance(f, str_type): if filename is None: filename = f - f = file(f, opts) + f = open(f, opts) want_close = True else: if filename is None: @@ -1001,6 +1013,7 @@ def from_file(f, origin = None, rdclass = dns.rdataclass.IN, f.close() return z + def from_xfr(xfr, zone_factory=Zone, relativize=True, check_origin=True): """Convert the output of a zone transfer generator into a zone object. diff --git a/setup.py b/setup.py index d9a5f96f..f284e13c 100755 --- a/setup.py +++ b/setup.py @@ -52,10 +52,7 @@ direct manipulation of DNS zones, messages, names, and records.""", "Topic :: Software Development :: Libraries :: Python Modules", ], 'test_suite': 'tests', + 'provides': ['dns'], } -if sys.hexversion >= 0x02050000: - kwargs['requires'] = [] - kwargs['provides'] = ['dns'] - setup(**kwargs) diff --git a/tests/test_bugs.py b/tests/test_bugs.py index 5d9d58b5..3d780b47 100644 --- a/tests/test_bugs.py +++ b/tests/test_bugs.py @@ -13,7 +13,7 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -from six import BytesIO +from io import BytesIO try: import unittest2 as unittest except ImportError: @@ -28,15 +28,15 @@ class BugsTestCase(unittest.TestCase): def test_float_LOC(self): rdata = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.LOC, - "30 30 0.000 N 100 30 0.000 W 10.00m 20m 2000m 20m") + u"30 30 0.000 N 100 30 0.000 W 10.00m 20m 2000m 20m") self.failUnless(rdata.float_latitude == 30.5) self.failUnless(rdata.float_longitude == -100.5) def test_SOA_BIND8_TTL(self): rdata1 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.SOA, - "a b 100 1s 1m 1h 1d") + u"a b 100 1s 1m 1h 1d") rdata2 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.SOA, - "a b 100 1 60 3600 86400") + u"a b 100 1 60 3600 86400") self.failUnless(rdata1 == rdata2) def test_TTL_bounds_check(self): @@ -46,7 +46,7 @@ class BugsTestCase(unittest.TestCase): def test_empty_NSEC3_window(self): rdata = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NSEC3, - "1 0 100 ABCD SCBCQHKU35969L2A68P3AD59LHF30715") + u"1 0 100 ABCD SCBCQHKU35969L2A68P3AD59LHF30715") self.failUnless(rdata.windows == []) def test_zero_size_APL(self): @@ -58,12 +58,12 @@ class BugsTestCase(unittest.TestCase): def test_CAA_from_wire(self): rdata = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.CAA, - '0 issue "ca.example.net"'); - f = cStringIO.StringIO() + u'0 issue "ca.example.net"'); + f = BytesIO() rdata.to_wire(f) wire = f.getvalue() rdlen = len(wire) - wire += "trailing garbage" + wire += b"trailing garbage" rdata2 = dns.rdata.from_wire(dns.rdataclass.IN, dns.rdatatype.CAA, wire, 0, rdlen) self.failUnless(rdata == rdata2) diff --git a/tests/test_dnssec.py b/tests/test_dnssec.py index 64e6638e..e972a857 100644 --- a/tests/test_dnssec.py +++ b/tests/test_dnssec.py @@ -233,4 +233,4 @@ if __name__ == '__main__': if import_ok: unittest.main() else: - print 'skipping DNSSEC tests because pycrypto is not installed' + print('skipping DNSSEC tests because pycrypto is not installed') diff --git a/tests/test_generate.py b/tests/test_generate.py index d3bb2ee9..a7230534 100644 --- a/tests/test_generate.py +++ b/tests/test_generate.py @@ -16,7 +16,6 @@ import sys sys.path.insert(0, '../') # Force the local project to be *the* dns -import cStringIO import filecmp import os try: @@ -30,9 +29,11 @@ import dns.rdataclass import dns.rdatatype import dns.rrset import dns.zone +from dns._compat import long import pprint + pp = pprint.PrettyPrinter(indent=2) import pdb @@ -136,6 +137,9 @@ ns2 3600 IN A 10.0.0.2 $GENERATE 27-28 $.2 PTR zlb${-26}.oob """ +def _rdata_sort(a): + return (a[0], a[2].rdclass, a[2].to_text()) + class GenerateTestCase(unittest.TestCase): @@ -152,7 +156,7 @@ class GenerateTestCase(unittest.TestCase): def testIterateAllRdatas2(self): z = dns.zone.from_text(example_text2, 'example.', relativize=True) l = list(z.iterate_rdatas()) - l.sort() + l.sort(key=_rdata_sort) exl = [(dns.name.from_text('@', None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, @@ -190,13 +194,13 @@ class GenerateTestCase(unittest.TestCase): dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, '10.0.0.5'))] - exl.sort() + exl.sort(key=_rdata_sort) self.failUnless(l == exl) def testIterateAllRdatas3(self): z = dns.zone.from_text(example_text3, 'example.', relativize=True) l = list(z.iterate_rdatas()) - l.sort() + l.sort(key=_rdata_sort) exl = [(dns.name.from_text('@', None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, @@ -230,271 +234,271 @@ class GenerateTestCase(unittest.TestCase): (dns.name.from_text('foo8', None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, '10.0.0.8'))] - exl.sort() + exl.sort(key=_rdata_sort) self.failUnless(l == exl) def testGenerate1(self): z = dns.zone.from_text(example_text4, 'example.', relativize=True) l = list(z.iterate_rdatas()) - l.sort() + l.sort(key=_rdata_sort) exl = [(dns.name.from_text('@', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, 'ns1')), (dns.name.from_text('@', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, 'ns2')), (dns.name.from_text('@', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.SOA, 'foo bar 1 2 3 4 5')), (dns.name.from_text('bar.foo', None), - 300L, + long(300), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX, '0 blaz.foo')), (dns.name.from_text('ns1', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, '10.0.0.1')), (dns.name.from_text('ns2', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, '10.0.0.2')), (dns.name.from_text('wp-db01.services.mozilla.com', None), - 0L, + long(0), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.CNAME, 'SERVER.FOOBAR.')), (dns.name.from_text('wp-db02.services.mozilla.com', None), - 0L, + long(0), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.CNAME, 'SERVER.FOOBAR.')), (dns.name.from_text('wp-db03.services.mozilla.com', None), - 0L, + long(0), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.CNAME, 'SERVER.FOOBAR.'))] - exl.sort() - self.failUnless(l == exl) + exl.sort(key=_rdata_sort) + self.assertEqual(l, exl) def testGenerate2(self): z = dns.zone.from_text(example_text5, 'example.', relativize=True) l = list(z.iterate_rdatas()) - l.sort() + l.sort(key=_rdata_sort) exl = [(dns.name.from_text('@', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, 'ns1')), (dns.name.from_text('@', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, 'ns2')), (dns.name.from_text('@', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.SOA, 'foo bar 1 2 3 4 5')), (dns.name.from_text('bar.foo', None), - 300L, + long(300), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX, '0 blaz.foo')), (dns.name.from_text('ns1', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, '10.0.0.1')), (dns.name.from_text('ns2', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, '10.0.0.2')), - (dns.name.from_text('wp-db21.services.mozilla.com', None), 0L, + (dns.name.from_text('wp-db21.services.mozilla.com', None), long(0), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.CNAME, 'SERVER.FOOBAR.')), - (dns.name.from_text('wp-db22.services.mozilla.com', None), 0L, + (dns.name.from_text('wp-db22.services.mozilla.com', None), long(0), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.CNAME, 'SERVER.FOOBAR.')), - (dns.name.from_text('wp-db23.services.mozilla.com', None), 0L, + (dns.name.from_text('wp-db23.services.mozilla.com', None), long(0), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.CNAME, 'SERVER.FOOBAR.'))] - exl.sort() + exl.sort(key=_rdata_sort) self.failUnless(l == exl) def testGenerate3(self): z = dns.zone.from_text(example_text6, 'example.', relativize=True) l = list(z.iterate_rdatas()) - l.sort() + l.sort(key=_rdata_sort) exl = [(dns.name.from_text('@', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, 'ns1')), (dns.name.from_text('@', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, 'ns2')), (dns.name.from_text('@', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.SOA, 'foo bar 1 2 3 4 5')), (dns.name.from_text('bar.foo', None), - 300L, + long(300), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX, '0 blaz.foo')), (dns.name.from_text('ns1', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, '10.0.0.1')), (dns.name.from_text('ns2', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, '10.0.0.2')), - (dns.name.from_text('wp-db21.services.mozilla.com', None), 0L, + (dns.name.from_text('wp-db21.services.mozilla.com', None), long(0), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.CNAME, 'SERVER.FOOBAR.')), - (dns.name.from_text('wp-db22.services.mozilla.com', None), 0L, + (dns.name.from_text('wp-db22.services.mozilla.com', None), long(0), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.CNAME, 'SERVER.FOOBAR.')), - (dns.name.from_text('wp-db23.services.mozilla.com', None), 0L, + (dns.name.from_text('wp-db23.services.mozilla.com', None), long(0), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.CNAME, 'SERVER.FOOBAR.'))] - exl.sort() + exl.sort(key=_rdata_sort) self.failUnless(l == exl) def testGenerate4(self): z = dns.zone.from_text(example_text7, 'example.', relativize=True) l = list(z.iterate_rdatas()) - l.sort() + l.sort(key=_rdata_sort) exl = [(dns.name.from_text('@', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, 'ns1')), (dns.name.from_text('@', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, 'ns2')), (dns.name.from_text('@', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.SOA, 'foo bar 1 2 3 4 5')), (dns.name.from_text('bar.foo', None), - 300L, + long(300), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX, '0 blaz.foo')), (dns.name.from_text('ns1', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, '10.0.0.1')), (dns.name.from_text('ns2', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, '10.0.0.2')), - (dns.name.from_text('sync1.db', None), 3600L, + (dns.name.from_text('sync1.db', None), long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, '10.10.16.0')), - (dns.name.from_text('sync2.db', None), 3600L, + (dns.name.from_text('sync2.db', None), long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, '10.10.16.0')), - (dns.name.from_text('sync3.db', None), 3600L, + (dns.name.from_text('sync3.db', None), long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, '10.10.16.0'))] - exl.sort() + exl.sort(key=_rdata_sort) self.failUnless(l == exl) def testGenerate6(self): z = dns.zone.from_text(example_text9, 'example.', relativize=True) l = list(z.iterate_rdatas()) - l.sort() + l.sort(key=_rdata_sort) exl = [(dns.name.from_text('@', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, 'ns1')), (dns.name.from_text('@', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, 'ns2')), (dns.name.from_text('@', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.SOA, 'foo bar 1 2 3 4 5')), (dns.name.from_text('bar.foo', None), - 300L, + long(300), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX, '0 blaz.foo')), (dns.name.from_text('ns1', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, '10.0.0.1')), (dns.name.from_text('ns2', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, '10.0.0.2')), - (dns.name.from_text('wp-db01', None), 3600L, + (dns.name.from_text('wp-db01', None), long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, '10.10.16.0')), - (dns.name.from_text('wp-db02', None), 3600L, + (dns.name.from_text('wp-db02', None), long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, '10.10.16.0')), - (dns.name.from_text('sync1.db', None), 3600L, + (dns.name.from_text('sync1.db', None), long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, '10.10.16.0')), - (dns.name.from_text('sync2.db', None), 3600L, + (dns.name.from_text('sync2.db', None), long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, '10.10.16.0')), - (dns.name.from_text('sync3.db', None), 3600L, + (dns.name.from_text('sync3.db', None), long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, '10.10.16.0'))] - exl.sort() + exl.sort(key=_rdata_sort) self.failUnless(l == exl) def testGenerate7(self): z = dns.zone.from_text(example_text10, 'example.', relativize=True) l = list(z.iterate_rdatas()) - l.sort() + l.sort(key=_rdata_sort) exl = [(dns.name.from_text('@', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, 'ns1')), (dns.name.from_text('@', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, 'ns2')), (dns.name.from_text('@', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.SOA, 'foo bar 1 2 3 4 5')), (dns.name.from_text('bar.foo', None), - 300L, + long(300), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX, '0 blaz.foo')), (dns.name.from_text('ns1', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, '10.0.0.1')), (dns.name.from_text('ns2', None), - 3600L, + long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, '10.0.0.2')), - (dns.name.from_text('27.2', None), 3600L, + (dns.name.from_text('27.2', None), long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.PTR, 'zlb1.oob')), - (dns.name.from_text('28.2', None), 3600L, + (dns.name.from_text('28.2', None), long(3600), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.PTR, 'zlb2.oob'))] - exl.sort() + exl.sort(key=_rdata_sort) self.failUnless(l == exl) diff --git a/tests/test_grange.py b/tests/test_grange.py index 4ffe26db..c2bbb192 100644 --- a/tests/test_grange.py +++ b/tests/test_grange.py @@ -16,7 +16,6 @@ import sys sys.path.insert(0, '../') -import cStringIO import filecmp import os try: diff --git a/tests/test_message.py b/tests/test_message.py index 8dcdf0ed..79562654 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -13,15 +13,16 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -import cStringIO import os try: import unittest2 as unittest except ImportError: import unittest +import binascii import dns.exception import dns.message +from dns._compat import xrange query_text = """id 1234 opcode QUERY @@ -36,10 +37,10 @@ wwww.dnspython.org. IN A ;AUTHORITY ;ADDITIONAL""" -goodhex = '04d201000001000000000001047777777709646e73707974686f6e' \ - '036f726700000100010000291000000080000000' +goodhex = b'04d201000001000000000001047777777709646e73707974686f6e' \ + b'036f726700000100010000291000000080000000' -goodwire = goodhex.decode('hex_codec') +goodwire = binascii.unhexlify(goodhex) answer_text = """id 1234 opcode QUERY @@ -69,7 +70,7 @@ goodhex2 = '04d2 8500 0001 0001 0003 0001' \ 'c091 0001 0001 00000e10 0004 cc98ba96' -goodwire2 = goodhex2.replace(' ', '').decode('hex_codec') +goodwire2 = binascii.unhexlify(goodhex2.replace(' ', '').encode()) query_text_2 = """id 1234 opcode QUERY @@ -84,10 +85,10 @@ wwww.dnspython.org. IN A ;AUTHORITY ;ADDITIONAL""" -goodhex3 = '04d2010f0001000000000001047777777709646e73707974686f6e' \ - '036f726700000100010000291000ff0080000000' +goodhex3 = b'04d2010f0001000000000001047777777709646e73707974686f6e' \ + b'036f726700000100010000291000ff0080000000' -goodwire3 = goodhex3.decode('hex_codec') +goodwire3 = binascii.unhexlify(goodhex3) class MessageTestCase(unittest.TestCase): @@ -119,7 +120,7 @@ class MessageTestCase(unittest.TestCase): def test_EDNS_from_wire1(self): m = dns.message.from_wire(goodwire) - self.failUnless(str(m) == query_text) + self.assertEqual(str(m), query_text) def test_EDNS_to_wire2(self): q = dns.message.from_text(query_text_2) @@ -149,13 +150,13 @@ class MessageTestCase(unittest.TestCase): def test_TrailingJunk(self): def bad(): - badwire = goodwire + '\x00' + badwire = goodwire + b'\x00' m = dns.message.from_wire(badwire) self.failUnlessRaises(dns.message.TrailingJunk, bad) def test_ShortHeader(self): def bad(): - badwire = '\x00' * 11 + badwire = b'\x00' * 11 m = dns.message.from_wire(badwire) self.failUnlessRaises(dns.message.ShortHeader, bad) diff --git a/tests/test_name.py b/tests/test_name.py index 2c000632..4935564d 100644 --- a/tests/test_name.py +++ b/tests/test_name.py @@ -18,7 +18,7 @@ try: except ImportError: import unittest -import cStringIO +from io import BytesIO import socket import dns.name @@ -31,15 +31,15 @@ class NameTestCase(unittest.TestCase): def testFromTextRel1(self): n = dns.name.from_text('foo.bar') - self.failUnless(n.labels == ('foo', 'bar', '')) + self.assertEqual(n.labels, (b'foo', b'bar', b'')) def testFromTextRel2(self): n = dns.name.from_text('foo.bar', origin=self.origin) - self.failUnless(n.labels == ('foo', 'bar', 'example', '')) + self.assertEqual(n.labels, (b'foo', b'bar', b'example', b'')) def testFromTextRel3(self): n = dns.name.from_text('foo.bar', origin=None) - self.failUnless(n.labels == ('foo', 'bar')) + self.assertEqual(n.labels, (b'foo', b'bar')) def testFromTextRel4(self): n = dns.name.from_text('@', origin=None) @@ -51,33 +51,33 @@ class NameTestCase(unittest.TestCase): def testFromTextAbs1(self): n = dns.name.from_text('foo.bar.') - self.failUnless(n.labels == ('foo', 'bar', '')) + self.assertEqual(n.labels,(b'foo', b'bar', b'')) def testTortureFromText(self): good = [ - r'.', - r'a', - r'a.', - r'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - r'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - r'\000.\008.\010.\032.\046.\092.\099.\255', - r'\\', - r'\..\.', - r'\\.\\', - r'!"#%&/()=+-', - r'\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255.\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255.\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255.\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255', + br'.', + br'a', + br'a.', + br'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + br'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + br'\000.\008.\010.\032.\046.\092.\099.\255', + br'\\', + br'\..\.', + br'\\.\\', + br'!"#%&/()=+-', + br'\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255.\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255.\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255.\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255', ] bad = [ - r'..', - r'.a', - r'\\..', - '\\', # yes, we don't want the 'r' prefix! - r'\0', - r'\00', - r'\00Z', - r'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - r'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - r'\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255.\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255.\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255.\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255', + br'..', + br'.a', + br'\\..', + b'\\', # yes, we don't want the 'r' prefix! + br'\0', + br'\00', + br'\00Z', + br'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + br'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + br'\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255.\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255.\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255.\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255', ] for t in good: try: @@ -137,7 +137,7 @@ class NameTestCase(unittest.TestCase): def testHash1(self): n1 = dns.name.from_text('fOo.COM') n2 = dns.name.from_text('foo.com') - self.failUnless(hash(n1) == hash(n2)) + self.assertEqual(hash(n1), hash(n2)) def testCompare1(self): n1 = dns.name.from_text('a') @@ -200,60 +200,60 @@ class NameTestCase(unittest.TestCase): def testCanonicalize1(self): n = dns.name.from_text('FOO.bar', origin=self.origin) c = n.canonicalize() - self.failUnless(c.labels == ('foo', 'bar', 'example', '')) + self.assertEqual(c.labels, (b'foo', b'bar', b'example', b'')) def testToText1(self): n = dns.name.from_text('FOO.bar', origin=self.origin) t = n.to_text() - self.failUnless(t == 'FOO.bar.example.') + self.assertEqual(t, b'FOO.bar.example.') def testToText2(self): n = dns.name.from_text('FOO.bar', origin=self.origin) t = n.to_text(True) - self.failUnless(t == 'FOO.bar.example') + self.assertEqual(t, b'FOO.bar.example') def testToText3(self): n = dns.name.from_text('FOO.bar', origin=None) t = n.to_text() - self.failUnless(t == 'FOO.bar') + self.assertEqual(t, b'FOO.bar') def testToText4(self): t = dns.name.empty.to_text() - self.failUnless(t == '@') + self.assertEqual(t, b'@') def testToText5(self): t = dns.name.root.to_text() - self.failUnless(t == '.') + self.assertEqual(t, b'.') def testToText6(self): n = dns.name.from_text('FOO bar', origin=None) t = n.to_text() - self.failUnless(t == r'FOO\032bar') + self.assertEqual(t, br'FOO\032bar') def testToText7(self): n = dns.name.from_text(r'FOO\.bar', origin=None) t = n.to_text() - self.failUnless(t == r'FOO\.bar') + self.assertEqual(t, b'FOO\.bar') def testToText8(self): n = dns.name.from_text(r'\070OO\.bar', origin=None) t = n.to_text() - self.failUnless(t == r'FOO\.bar') + self.assertEqual(t, b'FOO\.bar') def testSlice1(self): n = dns.name.from_text(r'a.b.c.', origin=None) s = n[:] - self.failUnless(s == ('a', 'b', 'c', '')) + self.assertEqual(s, (b'a', b'b', b'c', b'')) def testSlice2(self): n = dns.name.from_text(r'a.b.c.', origin=None) s = n[:2] - self.failUnless(s == ('a', 'b')) + self.assertEqual(s, (b'a', b'b')) def testSlice3(self): n = dns.name.from_text(r'a.b.c.', origin=None) s = n[2:] - self.failUnless(s == ('c', '')) + self.assertEqual(s, (b'c', b'')) def testEmptyLabel1(self): def bad(): @@ -332,13 +332,13 @@ class NameTestCase(unittest.TestCase): def testBadEscape(self): def bad(): n = dns.name.from_text(r'a.b\0q1.c.') - print n + print(n) self.failUnlessRaises(dns.name.BadEscape, bad) def testDigestable1(self): n = dns.name.from_text('FOO.bar') d = n.to_digestable() - self.failUnless(d == '\x03foo\x03bar\x00') + self.assertEqual(d, b'\x03foo\x03bar\x00') def testDigestable2(self): n1 = dns.name.from_text('FOO.bar') @@ -349,12 +349,12 @@ class NameTestCase(unittest.TestCase): def testDigestable3(self): d = dns.name.root.to_digestable() - self.failUnless(d == '\x00') + self.assertEqual(d, b'\x00') def testDigestable4(self): n = dns.name.from_text('FOO.bar', None) d = n.to_digestable(dns.name.root) - self.failUnless(d == '\x03foo\x03bar\x00') + self.assertEqual(d, b'\x03foo\x03bar\x00') def testBadDigestable(self): def bad(): @@ -364,56 +364,56 @@ class NameTestCase(unittest.TestCase): def testToWire1(self): n = dns.name.from_text('FOO.bar') - f = cStringIO.StringIO() + f = BytesIO() compress = {} n.to_wire(f, compress) - self.failUnless(f.getvalue() == '\x03FOO\x03bar\x00') + self.assertEqual(f.getvalue(), b'\x03FOO\x03bar\x00') def testToWire2(self): n = dns.name.from_text('FOO.bar') - f = cStringIO.StringIO() + f = BytesIO() compress = {} n.to_wire(f, compress) n.to_wire(f, compress) - self.failUnless(f.getvalue() == '\x03FOO\x03bar\x00\xc0\x00') + self.assertEqual(f.getvalue(), b'\x03FOO\x03bar\x00\xc0\x00') def testToWire3(self): n1 = dns.name.from_text('FOO.bar') n2 = dns.name.from_text('foo.bar') - f = cStringIO.StringIO() + f = BytesIO() compress = {} n1.to_wire(f, compress) n2.to_wire(f, compress) - self.failUnless(f.getvalue() == '\x03FOO\x03bar\x00\xc0\x00') + self.assertEqual(f.getvalue(), b'\x03FOO\x03bar\x00\xc0\x00') def testToWire4(self): n1 = dns.name.from_text('FOO.bar') n2 = dns.name.from_text('a.foo.bar') - f = cStringIO.StringIO() + f = BytesIO() compress = {} n1.to_wire(f, compress) n2.to_wire(f, compress) - self.failUnless(f.getvalue() == '\x03FOO\x03bar\x00\x01\x61\xc0\x00') + self.assertEqual(f.getvalue(), b'\x03FOO\x03bar\x00\x01\x61\xc0\x00') def testToWire5(self): n1 = dns.name.from_text('FOO.bar') n2 = dns.name.from_text('a.foo.bar') - f = cStringIO.StringIO() + f = BytesIO() compress = {} n1.to_wire(f, compress) n2.to_wire(f, None) - self.failUnless(f.getvalue() == \ - '\x03FOO\x03bar\x00\x01\x61\x03foo\x03bar\x00') + self.assertEqual(f.getvalue(), + b'\x03FOO\x03bar\x00\x01\x61\x03foo\x03bar\x00') def testToWire6(self): n = dns.name.from_text('FOO.bar') v = n.to_wire() - self.failUnless(v == '\x03FOO\x03bar\x00') + self.assertEqual(v, b'\x03FOO\x03bar\x00') def testBadToWire(self): def bad(): n = dns.name.from_text('FOO.bar', None) - f = cStringIO.StringIO() + f = BytesIO() compress = {} n.to_wire(f, compress) self.failUnlessRaises(dns.name.NeedAbsoluteNameOrOrigin, bad) @@ -554,7 +554,7 @@ class NameTestCase(unittest.TestCase): n2 == en2 and cused2 == ecused2) def testFromWire1(self): - w = '\x03foo\x00\x01a\xc0\x00\x01b\xc0\x05' + w = b'\x03foo\x00\x01a\xc0\x00\x01b\xc0\x05' current = 0 (n1, cused1) = dns.name.from_wire(w, current) current += cused1 @@ -573,25 +573,25 @@ class NameTestCase(unittest.TestCase): def testBadFromWire1(self): def bad(): - w = '\x03foo\xc0\x04' + w = b'\x03foo\xc0\x04' (n, cused) = dns.name.from_wire(w, 0) self.failUnlessRaises(dns.name.BadPointer, bad) def testBadFromWire2(self): def bad(): - w = '\x03foo\xc0\x05' + w = b'\x03foo\xc0\x05' (n, cused) = dns.name.from_wire(w, 0) self.failUnlessRaises(dns.name.BadPointer, bad) def testBadFromWire3(self): def bad(): - w = '\xbffoo' + w = b'\xbffoo' (n, cused) = dns.name.from_wire(w, 0) self.failUnlessRaises(dns.name.BadLabelType, bad) def testBadFromWire4(self): def bad(): - w = '\x41foo' + w = b'\x41foo' (n, cused) = dns.name.from_wire(w, 0) self.failUnlessRaises(dns.name.BadLabelType, bad) @@ -619,48 +619,48 @@ class NameTestCase(unittest.TestCase): def testFromUnicode1(self): n = dns.name.from_text(u'foo.bar') - self.failUnless(n.labels == ('foo', 'bar', '')) + self.assertEqual(n.labels, (b'foo', b'bar', b'')) def testFromUnicode2(self): n = dns.name.from_text(u'foo\u1234bar.bar') - self.failUnless(n.labels == ('xn--foobar-r5z', 'bar', '')) + self.assertEqual(n.labels, (b'xn--foobar-r5z', b'bar', b'')) def testFromUnicodeAlternateDot1(self): n = dns.name.from_text(u'foo\u3002bar') - self.failUnless(n.labels == ('foo', 'bar', '')) + self.assertEqual(n.labels, (b'foo', b'bar', b'')) def testFromUnicodeAlternateDot2(self): n = dns.name.from_text(u'foo\uff0ebar') - self.failUnless(n.labels == ('foo', 'bar', '')) + self.assertEqual(n.labels, (b'foo', b'bar', b'')) def testFromUnicodeAlternateDot3(self): n = dns.name.from_text(u'foo\uff61bar') - self.failUnless(n.labels == ('foo', 'bar', '')) + self.assertEqual(n.labels, (b'foo', b'bar', b'')) def testToUnicode1(self): n = dns.name.from_text(u'foo.bar') s = n.to_unicode() - self.failUnless(s == u'foo.bar.') + self.assertEqual(s, u'foo.bar.') def testToUnicode2(self): n = dns.name.from_text(u'foo\u1234bar.bar') s = n.to_unicode() - self.failUnless(s == u'foo\u1234bar.bar.') + self.assertEqual(s, u'foo\u1234bar.bar.') def testToUnicode3(self): n = dns.name.from_text('foo.bar') s = n.to_unicode() - self.failUnless(s == u'foo.bar.') + self.assertEqual(s, u'foo.bar.') def testReverseIPv4(self): e = dns.name.from_text('1.0.0.127.in-addr.arpa.') n = dns.reversename.from_address('127.0.0.1') - self.failUnless(e == n) + self.assertEqual(e, n) def testReverseIPv6(self): e = dns.name.from_text('1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.') - n = dns.reversename.from_address('::1') - self.failUnless(e == n) + n = dns.reversename.from_address(b'::1') + self.assertEqual(e, n) def testReverseIPv6MappedIpv4(self): e = dns.name.from_text('1.0.0.127.in-addr.arpa.') @@ -679,15 +679,15 @@ class NameTestCase(unittest.TestCase): def testForwardIPv4(self): n = dns.name.from_text('1.0.0.127.in-addr.arpa.') - e = '127.0.0.1' + e = b'127.0.0.1' text = dns.reversename.to_address(n) - self.failUnless(text == e) + self.assertEqual(text, e) def testForwardIPv6(self): n = dns.name.from_text('1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.') - e = '::1' + e = b'::1' text = dns.reversename.to_address(n) - self.failUnless(text == e) + self.assertEqual(text, e) def testE164ToEnum(self): text = '+1 650 555 1212' @@ -697,9 +697,9 @@ class NameTestCase(unittest.TestCase): def testEnumToE164(self): n = dns.name.from_text('2.1.2.1.5.5.5.0.5.6.1.e164.arpa.') - e = '+16505551212' + e = b'+16505551212' text = dns.e164.to_e164(n) - self.failUnless(text == e) + self.assertEqual(text,e) if __name__ == '__main__': unittest.main() diff --git a/tests/test_ntoaaton.py b/tests/test_ntoaaton.py index bad0917f..043e3d4e 100644 --- a/tests/test_ntoaaton.py +++ b/tests/test_ntoaaton.py @@ -17,6 +17,7 @@ try: import unittest2 as unittest except ImportError: import unittest +import binascii import dns.exception import dns.ipv4 @@ -28,7 +29,7 @@ ntoa4 = dns.ipv4.inet_ntoa aton6 = dns.ipv6.inet_aton ntoa6 = dns.ipv6.inet_ntoa -v4_bad_addrs = ['256.1.1.1', '1.1.1', '1.1.1.1.1', '01.1.1.1', +v4_bad_addrs = ['256.1.1.1', '1.1.1', '1.1.1.1.1', #'01.1.1.1', '+1.1.1.1', '1.1.1.1+', '1..2.3.4', '.1.2.3.4', '1.2.3.4.'] @@ -52,8 +53,8 @@ class NtoAAtoNTestCase(unittest.TestCase): def test_aton5(self): a = aton6('1:2:3:4:5:6:7:8') - self.failUnless(a == \ - '00010002000300040005000600070008'.decode('hex_codec')) + self.assertEqual(a, + binascii.unhexlify(b'00010002000300040005000600070008')) def test_bad_aton1(self): def bad(): @@ -72,89 +73,89 @@ class NtoAAtoNTestCase(unittest.TestCase): def test_aton1(self): a = aton6('::') - self.failUnless(a == '\x00' * 16) + self.assertEqual(a, b'\x00' * 16) def test_aton2(self): a = aton6('::1') - self.failUnless(a == '\x00' * 15 + '\x01') + self.assertEqual(a, b'\x00' * 15 + b'\x01') def test_aton3(self): a = aton6('::10.0.0.1') - self.failUnless(a == '\x00' * 12 + '\x0a\x00\x00\x01') + self.assertEqual(a, b'\x00' * 12 + b'\x0a\x00\x00\x01') def test_aton4(self): a = aton6('abcd::dcba') - self.failUnless(a == '\xab\xcd' + '\x00' * 12 + '\xdc\xba') + self.assertEqual(a, b'\xab\xcd' + b'\x00' * 12 + b'\xdc\xba') def test_ntoa1(self): - b = '00010002000300040005000600070008'.decode('hex_codec') + b = binascii.unhexlify(b'00010002000300040005000600070008') t = ntoa6(b) - self.failUnless(t == '1:2:3:4:5:6:7:8') + self.assertEqual(t, b'1:2:3:4:5:6:7:8') def test_ntoa2(self): - b = '\x00' * 16 + b = b'\x00' * 16 t = ntoa6(b) - self.failUnless(t == '::') + self.assertEqual(t, b'::') def test_ntoa3(self): - b = '\x00' * 15 + '\x01' + b = b'\x00' * 15 + b'\x01' t = ntoa6(b) - self.failUnless(t == '::1') + self.assertEqual(t, b'::1') def test_ntoa4(self): - b = '\x80' + '\x00' * 15 + b = b'\x80' + b'\x00' * 15 t = ntoa6(b) - self.failUnless(t == '8000::') + self.assertEqual(t, b'8000::') def test_ntoa5(self): - b = '\x01\xcd' + '\x00' * 12 + '\x03\xef' + b = b'\x01\xcd' + b'\x00' * 12 + b'\x03\xef' t = ntoa6(b) - self.failUnless(t == '1cd::3ef') + self.assertEqual(t, b'1cd::3ef') def test_ntoa6(self): - b = 'ffff00000000ffff000000000000ffff'.decode('hex_codec') + b = binascii.unhexlify(b'ffff00000000ffff000000000000ffff') t = ntoa6(b) - self.failUnless(t == 'ffff:0:0:ffff::ffff') + self.assertEqual(t, b'ffff:0:0:ffff::ffff') def test_ntoa7(self): - b = '00000000ffff000000000000ffffffff'.decode('hex_codec') + b = binascii.unhexlify(b'00000000ffff000000000000ffffffff') t = ntoa6(b) - self.failUnless(t == '0:0:ffff::ffff:ffff') + self.assertEqual(t, b'0:0:ffff::ffff:ffff') def test_ntoa8(self): - b = 'ffff0000ffff00000000ffff00000000'.decode('hex_codec') + b = binascii.unhexlify(b'ffff0000ffff00000000ffff00000000') t = ntoa6(b) - self.failUnless(t == 'ffff:0:ffff::ffff:0:0') + self.assertEqual(t, b'ffff:0:ffff::ffff:0:0') def test_ntoa9(self): - b = '0000000000000000000000000a000001'.decode('hex_codec') + b = binascii.unhexlify(b'0000000000000000000000000a000001') t = ntoa6(b) - self.failUnless(t == '::10.0.0.1') + self.assertEqual(t, b'::10.0.0.1') def test_ntoa10(self): - b = '0000000000000000000000010a000001'.decode('hex_codec') + b = binascii.unhexlify(b'0000000000000000000000010a000001') t = ntoa6(b) - self.failUnless(t == '::1:a00:1') + self.assertEqual(t, b'::1:a00:1') def test_ntoa11(self): - b = '00000000000000000000ffff0a000001'.decode('hex_codec') + b = binascii.unhexlify(b'00000000000000000000ffff0a000001') t = ntoa6(b) - self.failUnless(t == '::ffff:10.0.0.1') + self.assertEqual(t, b'::ffff:10.0.0.1') def test_ntoa12(self): - b = '000000000000000000000000ffffffff'.decode('hex_codec') + b = binascii.unhexlify(b'000000000000000000000000ffffffff') t = ntoa6(b) - self.failUnless(t == '::255.255.255.255') + self.assertEqual(t, b'::255.255.255.255') def test_ntoa13(self): - b = '00000000000000000000ffffffffffff'.decode('hex_codec') + b = binascii.unhexlify(b'00000000000000000000ffffffffffff') t = ntoa6(b) - self.failUnless(t == '::ffff:255.255.255.255') + self.assertEqual(t, b'::ffff:255.255.255.255') def test_ntoa14(self): - b = '0000000000000000000000000001ffff'.decode('hex_codec') + b = binascii.unhexlify(b'0000000000000000000000000001ffff') t = ntoa6(b) - self.failUnless(t == '::0.1.255.255') + self.assertEqual(t, b'::0.1.255.255') def test_bad_ntoa1(self): def bad(): @@ -167,14 +168,14 @@ class NtoAAtoNTestCase(unittest.TestCase): self.failUnlessRaises(ValueError, bad) def test_good_v4_aton(self): - pairs = [('1.2.3.4', '\x01\x02\x03\x04'), - ('255.255.255.255', '\xff\xff\xff\xff'), - ('0.0.0.0', '\x00\x00\x00\x00')] + pairs = [(b'1.2.3.4', b'\x01\x02\x03\x04'), + (b'255.255.255.255', b'\xff\xff\xff\xff'), + (b'0.0.0.0', b'\x00\x00\x00\x00')] for (t, b) in pairs: b1 = aton4(t) t1 = ntoa4(b1) - self.failUnless(b1 == b) - self.failUnless(t1 == t) + self.assertEqual(b1, b) + self.assertEqual(t1, t) def test_bad_v4_aton(self): def make_bad(a): @@ -182,6 +183,7 @@ class NtoAAtoNTestCase(unittest.TestCase): return aton4(a) return bad for addr in v4_bad_addrs: + print(addr) self.failUnlessRaises(dns.exception.SyntaxError, make_bad(addr)) def test_bad_v6_aton(self): @@ -197,10 +199,10 @@ class NtoAAtoNTestCase(unittest.TestCase): self.failUnlessRaises(dns.exception.SyntaxError, make_bad(addr)) def test_rfc5952_section_4_2_2(self): - addr = '2001:db8:0:1:1:1:1:1' + addr = b'2001:db8:0:1:1:1:1:1' b1 = aton6(addr) t1 = ntoa6(b1) - self.failUnless(t1 == addr) + self.assertEqual(t1, addr) def test_is_mapped(self): t1 = '2001:db8:0:1:1:1:1:1' diff --git a/tests/test_rdtypeanyeui.py b/tests/test_rdtypeanyeui.py index e7ad8623..fa8339f3 100644 --- a/tests/test_rdtypeanyeui.py +++ b/tests/test_rdtypeanyeui.py @@ -18,13 +18,11 @@ try: import unittest2 as unittest except ImportError: import unittest -try: - from StringIO import StringIO -except ImportError: - from io import BytesIO as StringIO +from io import BytesIO, StringIO import dns.rrset import dns.rdtypes.ANY.EUI48 +import dns.rdtypes.ANY.EUI64 import dns.exception @@ -94,7 +92,7 @@ class RdtypeAnyEUI48TestCase(unittest.TestCase): inst = dns.rdtypes.ANY.EUI48.EUI48(dns.rdataclass.IN, dns.rdatatype.EUI48, eui) - buff = StringIO() + buff = BytesIO() inst.to_wire(buff) self.assertEqual(buff.getvalue(), eui) @@ -193,7 +191,7 @@ class RdtypeAnyEUI64TestCase(unittest.TestCase): inst = dns.rdtypes.ANY.EUI64.EUI64(dns.rdataclass.IN, dns.rdatatype.EUI64, eui) - buff = StringIO() + buff = BytesIO() inst.to_wire(buff) self.assertEqual(buff.getvalue(), eui) diff --git a/tests/test_resolver.py b/tests/test_resolver.py index 90e0f15a..29c39594 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -13,7 +13,7 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -import cStringIO +from io import StringIO import select import sys import time @@ -28,8 +28,9 @@ import dns.name import dns.rdataclass import dns.rdatatype import dns.resolver +from dns._compat import xrange -resolv_conf = """ +resolv_conf = u""" /t/t # comment 1 ; comment 2 @@ -58,7 +59,7 @@ class BaseResolverTests(object): if sys.platform != 'win32': def testRead(self): - f = cStringIO.StringIO(resolv_conf) + f = StringIO(resolv_conf) r = dns.resolver.Resolver(f) self.failUnless(r.nameservers == ['10.0.0.1', '10.0.0.2'] and r.domain == dns.name.from_text('foo')) diff --git a/tests/test_tokenizer.py b/tests/test_tokenizer.py index d75de3b1..9cd9f76a 100644 --- a/tests/test_tokenizer.py +++ b/tests/test_tokenizer.py @@ -14,6 +14,10 @@ # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. import unittest +try: + import unittest2 as unittest +except ImportError: + import unittest import dns.exception import dns.tokenizer diff --git a/tests/test_update.py b/tests/test_update.py index 3e888ba0..95e7175f 100644 --- a/tests/test_update.py +++ b/tests/test_update.py @@ -17,6 +17,7 @@ try: import unittest2 as unittest except ImportError: import unittest +import binascii import dns.update import dns.rdata @@ -37,7 +38,7 @@ goodhex = '0001 2800 0001 0005 0007 0000' \ '04626c617ac00c 0001 00ff 00000000 0000' \ 'c049 00ff 00ff 00000000 0000' -goodwire = goodhex.replace(' ', '').decode('hex_codec') +goodwire = binascii.unhexlify(goodhex.replace(' ', '').encode()) update_text="""id 1 opcode UPDATE diff --git a/tests/test_zone.py b/tests/test_zone.py index e4dd76b4..9ae23b7c 100644 --- a/tests/test_zone.py +++ b/tests/test_zone.py @@ -13,7 +13,7 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -import cStringIO +from io import BytesIO, StringIO import filecmp import os try: @@ -28,6 +28,9 @@ import dns.rdatatype import dns.rrset import dns.zone +def here(filename): + return os.path.join(os.path.dirname(__file__), filename) + example_text = """$TTL 3600 $ORIGIN example. @ soa foo bar 1 2 3 4 5 @@ -86,8 +89,8 @@ $ORIGIN example. @ soa foo bar 1 2 3 4 5 """ -include_text = """$INCLUDE "example" -""" +include_text = """$INCLUDE "%s" +""" % here("example") bad_directive_text = """$FOO bar $ORIGIN example. @@ -98,54 +101,61 @@ ns1 1d1s a 10.0.0.1 ns2 1w1D1h1m1S a 10.0.0.2 """ -_keep_output = False +_keep_output = True + +def _rdata_sort(a): + return (a[0], a[2].rdclass, a[2].to_text()) class ZoneTestCase(unittest.TestCase): def testFromFile1(self): - z = dns.zone.from_file('example', 'example') + z = dns.zone.from_file(here('example'), 'example') ok = False try: - z.to_file('example1.out', nl='\x0a') - ok = filecmp.cmp('example1.out', 'example1.good') + z.to_file(here('example1.out'), nl=b'\x0a') + ok = filecmp.cmp(here('example1.out'), + here('example1.good')) finally: if not _keep_output: - os.unlink('example1.out') + os.unlink(here('example1.out')) self.failUnless(ok) def testFromFile2(self): - z = dns.zone.from_file('example', 'example', relativize=False) + z = dns.zone.from_file(here('example'), 'example', relativize=False) ok = False try: - z.to_file('example2.out', relativize=False, nl='\x0a') - ok = filecmp.cmp('example2.out', 'example2.good') + z.to_file(here('example2.out'), relativize=False, nl=b'\x0a') + ok = filecmp.cmp(here('example2.out'), + here('example2.good')) finally: if not _keep_output: - os.unlink('example2.out') + os.unlink(here('example2.out')) self.failUnless(ok) def testToText(self): - z = dns.zone.from_file('example', 'example') + z = dns.zone.from_file(here('example'), 'example') ok = False try: - text_zone = z.to_text(nl='\x0a') - f = open('example3.out', 'wb') + text_zone = z.to_text(nl=b'\x0a') + f = open(here('example3.out'), 'wb') f.write(text_zone) f.close() - ok = filecmp.cmp('example3.out', 'example3.good') + ok = filecmp.cmp(here('example3.out'), + here('example3.good')) finally: if not _keep_output: - os.unlink('example3.out') + os.unlink(here('example3.out')) self.failUnless(ok) def testFromText(self): z = dns.zone.from_text(example_text, 'example.', relativize=True) - f = cStringIO.StringIO() - names = z.nodes.keys() + f = StringIO() + names = list(z.nodes.keys()) names.sort() for n in names: - print >> f, z[n].to_text(n) - self.failUnless(f.getvalue() == example_text_output) + f.write(z[n].to_text(n)) + f.write(u'\n') + self.assertEqual(f.getvalue(), example_text_output) def testTorture1(self): # @@ -153,10 +163,10 @@ class ZoneTestCase(unittest.TestCase): # for each RR in the zone, convert the rdata into wire format # and then back out, and see if we get equal rdatas. # - f = cStringIO.StringIO() + f = BytesIO() o = dns.name.from_text('example.') - z = dns.zone.from_file('example', o) - for (name, node) in z.iteritems(): + z = dns.zone.from_file(here('example'), o) + for (name, node) in z.items(): for rds in node: for rd in rds: f.seek(0) @@ -339,7 +349,7 @@ class ZoneTestCase(unittest.TestCase): def testIterateAllRdatas(self): z = dns.zone.from_text(example_text, 'example.', relativize=True) l = list(z.iterate_rdatas()) - l.sort() + l.sort(key=_rdata_sort) exl = [(dns.name.from_text('@', None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, @@ -364,6 +374,7 @@ class ZoneTestCase(unittest.TestCase): 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, '10.0.0.2'))] + exl.sort(key=_rdata_sort) self.failUnless(l == exl) def testTTLs(self): @@ -393,7 +404,7 @@ class ZoneTestCase(unittest.TestCase): def testInclude(self): z1 = dns.zone.from_text(include_text, 'example.', relativize=True, allow_include=True) - z2 = dns.zone.from_file('example', 'example.', relativize=True) + z2 = dns.zone.from_file(here('example'), 'example.', relativize=True) self.failUnless(z1 == z2) def testBadDirective(self): -- 2.47.3