KoD 1.0
- completato il supporto ai torrent e aggiunto ilcorsaronero.xyz - aggiunto supporto agli episodi locali, ovvero poter inserire nella libreria di kodi un misto tra puntate di kod e file scaricati altrove - le viste ora si salvano di nuovo dal menu laterale, ma rimangono salvate per il tipo di contenuto visualizzato e non per il singolo menu - ripensato il menu rapido, che ora è più rapido, ridisegnate alcune finestre
This commit is contained in:
Executable
+25
@@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2013 the BabelFish authors. All rights reserved.
|
||||
# Use of this source code is governed by the 3-clause BSD license
|
||||
# that can be found in the LICENSE file.
|
||||
#
|
||||
__title__ = 'babelfish'
|
||||
__version__ = '0.5.5-dev'
|
||||
__author__ = 'Antoine Bertin'
|
||||
__license__ = 'BSD'
|
||||
__copyright__ = 'Copyright 2015 the BabelFish authors'
|
||||
|
||||
import sys
|
||||
|
||||
if sys.version_info[0] >= 3:
|
||||
basestr = str
|
||||
else:
|
||||
basestr = basestring
|
||||
|
||||
from .converters import (LanguageConverter, LanguageReverseConverter, LanguageEquivalenceConverter, CountryConverter,
|
||||
CountryReverseConverter)
|
||||
from .country import country_converters, COUNTRIES, COUNTRY_MATRIX, Country
|
||||
from .exceptions import Error, LanguageConvertError, LanguageReverseError, CountryConvertError, CountryReverseError
|
||||
from .language import language_converters, LANGUAGES, LANGUAGE_MATRIX, Language
|
||||
from .script import SCRIPTS, SCRIPT_MATRIX, Script
|
||||
Executable
+289
@@ -0,0 +1,289 @@
|
||||
# Copyright (c) 2013 the BabelFish authors. All rights reserved.
|
||||
# Use of this source code is governed by the 3-clause BSD license
|
||||
# that can be found in the LICENSE file.
|
||||
#
|
||||
import collections
|
||||
import functools
|
||||
from importlib import import_module
|
||||
|
||||
# from pkg_resources import iter_entry_points, EntryPoint
|
||||
from ..exceptions import LanguageConvertError, LanguageReverseError
|
||||
|
||||
|
||||
# from https://github.com/kennethreitz/requests/blob/master/requests/structures.py
|
||||
class CaseInsensitiveDict(collections.MutableMapping):
|
||||
"""A case-insensitive ``dict``-like object.
|
||||
|
||||
Implements all methods and operations of
|
||||
``collections.MutableMapping`` as well as dict's ``copy``. Also
|
||||
provides ``lower_items``.
|
||||
|
||||
All keys are expected to be strings. The structure remembers the
|
||||
case of the last key to be set, and ``iter(instance)``,
|
||||
``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()``
|
||||
will contain case-sensitive keys. However, querying and contains
|
||||
testing is case insensitive:
|
||||
|
||||
cid = CaseInsensitiveDict()
|
||||
cid['English'] = 'eng'
|
||||
cid['ENGLISH'] == 'eng' # True
|
||||
list(cid) == ['English'] # True
|
||||
|
||||
If the constructor, ``.update``, or equality comparison
|
||||
operations are given keys that have equal ``.lower()``s, the
|
||||
behavior is undefined.
|
||||
|
||||
"""
|
||||
def __init__(self, data=None, **kwargs):
|
||||
self._store = dict()
|
||||
if data is None:
|
||||
data = {}
|
||||
self.update(data, **kwargs)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
# Use the lowercased key for lookups, but store the actual
|
||||
# key alongside the value.
|
||||
self._store[key.lower()] = (key, value)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._store[key.lower()][1]
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self._store[key.lower()]
|
||||
|
||||
def __iter__(self):
|
||||
return (casedkey for casedkey, mappedvalue in self._store.values())
|
||||
|
||||
def __len__(self):
|
||||
return len(self._store)
|
||||
|
||||
def lower_items(self):
|
||||
"""Like iteritems(), but with all lowercase keys."""
|
||||
return (
|
||||
(lowerkey, keyval[1])
|
||||
for (lowerkey, keyval)
|
||||
in self._store.items()
|
||||
)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, collections.Mapping):
|
||||
other = CaseInsensitiveDict(other)
|
||||
else:
|
||||
return NotImplemented
|
||||
# Compare insensitively
|
||||
return dict(self.lower_items()) == dict(other.lower_items())
|
||||
|
||||
# Copy is required
|
||||
def copy(self):
|
||||
return CaseInsensitiveDict(self._store.values())
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%r)' % (self.__class__.__name__, dict(self.items()))
|
||||
|
||||
|
||||
class LanguageConverter(object):
|
||||
"""A :class:`LanguageConverter` supports converting an alpha3 language code with an
|
||||
alpha2 country code and a script code into a custom code
|
||||
|
||||
.. attribute:: codes
|
||||
|
||||
Set of possible custom codes
|
||||
|
||||
"""
|
||||
def convert(self, alpha3, country=None, script=None):
|
||||
"""Convert an alpha3 language code with an alpha2 country code and a script code
|
||||
into a custom code
|
||||
|
||||
:param string alpha3: ISO-639-3 language code
|
||||
:param country: ISO-3166 country code, if any
|
||||
:type country: string or None
|
||||
:param script: ISO-15924 script code, if any
|
||||
:type script: string or None
|
||||
:return: the corresponding custom code
|
||||
:rtype: string
|
||||
:raise: :class:`~babelfish.exceptions.LanguageConvertError`
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class LanguageReverseConverter(LanguageConverter):
|
||||
"""A :class:`LanguageConverter` able to reverse a custom code into a alpha3
|
||||
ISO-639-3 language code, alpha2 ISO-3166-1 country code and ISO-15924 script code
|
||||
|
||||
"""
|
||||
def reverse(self, code):
|
||||
"""Reverse a custom code into alpha3, country and script code
|
||||
|
||||
:param string code: custom code to reverse
|
||||
:return: the corresponding alpha3 ISO-639-3 language code, alpha2 ISO-3166-1 country code and ISO-15924 script code
|
||||
:rtype: tuple
|
||||
:raise: :class:`~babelfish.exceptions.LanguageReverseError`
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class LanguageEquivalenceConverter(LanguageReverseConverter):
|
||||
"""A :class:`LanguageEquivalenceConverter` is a utility class that allows you to easily define a
|
||||
:class:`LanguageReverseConverter` by only specifying the dict from alpha3 to their corresponding symbols.
|
||||
|
||||
You must specify the dict of equivalence as a class variable named SYMBOLS.
|
||||
|
||||
If you also set the class variable CASE_SENSITIVE to ``True`` then the reverse conversion function will be
|
||||
case-sensitive (it is case-insensitive by default).
|
||||
|
||||
Example::
|
||||
|
||||
class MyCodeConverter(babelfish.LanguageEquivalenceConverter):
|
||||
CASE_SENSITIVE = True
|
||||
SYMBOLS = {'fra': 'mycode1', 'eng': 'mycode2'}
|
||||
|
||||
"""
|
||||
CASE_SENSITIVE = False
|
||||
|
||||
def __init__(self):
|
||||
self.codes = set()
|
||||
self.to_symbol = {}
|
||||
if self.CASE_SENSITIVE:
|
||||
self.from_symbol = {}
|
||||
else:
|
||||
self.from_symbol = CaseInsensitiveDict()
|
||||
|
||||
for alpha3, symbol in self.SYMBOLS.items():
|
||||
self.to_symbol[alpha3] = symbol
|
||||
self.from_symbol[symbol] = (alpha3, None, None)
|
||||
self.codes.add(symbol)
|
||||
|
||||
def convert(self, alpha3, country=None, script=None):
|
||||
try:
|
||||
return self.to_symbol[alpha3]
|
||||
except KeyError:
|
||||
raise LanguageConvertError(alpha3, country, script)
|
||||
|
||||
def reverse(self, code):
|
||||
try:
|
||||
return self.from_symbol[code]
|
||||
except KeyError:
|
||||
raise LanguageReverseError(code)
|
||||
|
||||
|
||||
class CountryConverter(object):
|
||||
"""A :class:`CountryConverter` supports converting an alpha2 country code
|
||||
into a custom code
|
||||
|
||||
.. attribute:: codes
|
||||
|
||||
Set of possible custom codes
|
||||
|
||||
"""
|
||||
def convert(self, alpha2):
|
||||
"""Convert an alpha2 country code into a custom code
|
||||
|
||||
:param string alpha2: ISO-3166-1 language code
|
||||
:return: the corresponding custom code
|
||||
:rtype: string
|
||||
:raise: :class:`~babelfish.exceptions.CountryConvertError`
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class CountryReverseConverter(CountryConverter):
|
||||
"""A :class:`CountryConverter` able to reverse a custom code into a alpha2
|
||||
ISO-3166-1 country code
|
||||
|
||||
"""
|
||||
def reverse(self, code):
|
||||
"""Reverse a custom code into alpha2 code
|
||||
|
||||
:param string code: custom code to reverse
|
||||
:return: the corresponding alpha2 ISO-3166-1 country code
|
||||
:rtype: string
|
||||
:raise: :class:`~babelfish.exceptions.CountryReverseError`
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class ConverterManager(object):
|
||||
"""Manager for babelfish converters behaving like a dict with lazy loading
|
||||
|
||||
Loading is done in this order:
|
||||
|
||||
* Entry point converters
|
||||
* Registered converters
|
||||
* Internal converters
|
||||
|
||||
.. attribute:: entry_point
|
||||
|
||||
The entry point where to look for converters
|
||||
|
||||
.. attribute:: internal_converters
|
||||
|
||||
Internal converters with entry point syntax
|
||||
|
||||
"""
|
||||
entry_point = ''
|
||||
internal_converters = []
|
||||
|
||||
def __init__(self):
|
||||
#: Registered converters with entry point syntax
|
||||
self.registered_converters = []
|
||||
|
||||
#: Loaded converters
|
||||
self.converters = {}
|
||||
|
||||
def __getitem__(self, name):
|
||||
"""Get a converter, lazy loading it if necessary"""
|
||||
if name in self.converters:
|
||||
return self.converters[name]
|
||||
# for ep in iter_entry_points(self.entry_point):
|
||||
# if ep.name == name:
|
||||
# self.converters[ep.name] = ep.load()()
|
||||
# return self.converters[ep.name]
|
||||
def parse(str):
|
||||
import re
|
||||
match = re.match('(?P<name>\w+) = (?P<module>[a-z0-9.]+):(?P<class>\w+)', str)
|
||||
print match.groupdict()
|
||||
return match.groupdict()
|
||||
for ep in (parse(c) for c in self.registered_converters + self.internal_converters):
|
||||
if ep.get('name') == name:
|
||||
cl = getattr(import_module(ep.get('module')), ep.get('class'))
|
||||
self.converters[ep.get('name')] = cl()
|
||||
return self.converters[ep.get('name')]
|
||||
raise KeyError(name)
|
||||
|
||||
def __setitem__(self, name, converter):
|
||||
"""Load a converter"""
|
||||
self.converters[name] = converter
|
||||
|
||||
def __delitem__(self, name):
|
||||
"""Unload a converter"""
|
||||
del self.converters[name]
|
||||
|
||||
def __iter__(self):
|
||||
"""Iterator over loaded converters"""
|
||||
return iter(self.converters)
|
||||
|
||||
def register(self, entry_point):
|
||||
"""Register a converter
|
||||
|
||||
:param string entry_point: converter to register (entry point syntax)
|
||||
:raise: ValueError if already registered
|
||||
|
||||
"""
|
||||
if entry_point in self.registered_converters:
|
||||
raise ValueError('Already registered')
|
||||
self.registered_converters.insert(0, entry_point)
|
||||
|
||||
def unregister(self, entry_point):
|
||||
"""Unregister a converter
|
||||
|
||||
:param string entry_point: converter to unregister (entry point syntax)
|
||||
|
||||
"""
|
||||
self.registered_converters.remove(entry_point)
|
||||
|
||||
def __contains__(self, name):
|
||||
return name in self.converters
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2013 the BabelFish authors. All rights reserved.
|
||||
# Use of this source code is governed by the 3-clause BSD license
|
||||
# that can be found in the LICENSE file.
|
||||
#
|
||||
from __future__ import unicode_literals
|
||||
from . import LanguageEquivalenceConverter
|
||||
from ..language import LANGUAGE_MATRIX
|
||||
|
||||
|
||||
class Alpha2Converter(LanguageEquivalenceConverter):
|
||||
CASE_SENSITIVE = True
|
||||
SYMBOLS = {}
|
||||
for iso_language in LANGUAGE_MATRIX:
|
||||
if iso_language.alpha2:
|
||||
SYMBOLS[iso_language.alpha3] = iso_language.alpha2
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2013 the BabelFish authors. All rights reserved.
|
||||
# Use of this source code is governed by the 3-clause BSD license
|
||||
# that can be found in the LICENSE file.
|
||||
#
|
||||
from __future__ import unicode_literals
|
||||
from . import LanguageEquivalenceConverter
|
||||
from ..language import LANGUAGE_MATRIX
|
||||
|
||||
|
||||
class Alpha3BConverter(LanguageEquivalenceConverter):
|
||||
CASE_SENSITIVE = True
|
||||
SYMBOLS = {}
|
||||
for iso_language in LANGUAGE_MATRIX:
|
||||
if iso_language.alpha3b:
|
||||
SYMBOLS[iso_language.alpha3] = iso_language.alpha3b
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2013 the BabelFish authors. All rights reserved.
|
||||
# Use of this source code is governed by the 3-clause BSD license
|
||||
# that can be found in the LICENSE file.
|
||||
#
|
||||
from __future__ import unicode_literals
|
||||
from . import LanguageEquivalenceConverter
|
||||
from ..language import LANGUAGE_MATRIX
|
||||
|
||||
|
||||
class Alpha3TConverter(LanguageEquivalenceConverter):
|
||||
CASE_SENSITIVE = True
|
||||
SYMBOLS = {}
|
||||
for iso_language in LANGUAGE_MATRIX:
|
||||
if iso_language.alpha3t:
|
||||
SYMBOLS[iso_language.alpha3] = iso_language.alpha3t
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2013 the BabelFish authors. All rights reserved.
|
||||
# Use of this source code is governed by the 3-clause BSD license
|
||||
# that can be found in the LICENSE file.
|
||||
#
|
||||
from __future__ import unicode_literals
|
||||
from . import CountryReverseConverter, CaseInsensitiveDict
|
||||
from ..country import COUNTRY_MATRIX
|
||||
from ..exceptions import CountryConvertError, CountryReverseError
|
||||
|
||||
|
||||
class CountryNameConverter(CountryReverseConverter):
|
||||
def __init__(self):
|
||||
self.codes = set()
|
||||
self.to_name = {}
|
||||
self.from_name = CaseInsensitiveDict()
|
||||
for country in COUNTRY_MATRIX:
|
||||
self.codes.add(country.name)
|
||||
self.to_name[country.alpha2] = country.name
|
||||
self.from_name[country.name] = country.alpha2
|
||||
|
||||
def convert(self, alpha2):
|
||||
if alpha2 not in self.to_name:
|
||||
raise CountryConvertError(alpha2)
|
||||
return self.to_name[alpha2]
|
||||
|
||||
def reverse(self, name):
|
||||
if name not in self.from_name:
|
||||
raise CountryReverseError(name)
|
||||
return self.from_name[name]
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2013 the BabelFish authors. All rights reserved.
|
||||
# Use of this source code is governed by the 3-clause BSD license
|
||||
# that can be found in the LICENSE file.
|
||||
#
|
||||
from __future__ import unicode_literals
|
||||
from . import LanguageEquivalenceConverter
|
||||
from ..language import LANGUAGE_MATRIX
|
||||
|
||||
|
||||
class NameConverter(LanguageEquivalenceConverter):
|
||||
CASE_SENSITIVE = False
|
||||
SYMBOLS = {}
|
||||
for iso_language in LANGUAGE_MATRIX:
|
||||
if iso_language.name:
|
||||
SYMBOLS[iso_language.alpha3] = iso_language.name
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2013 the BabelFish authors. All rights reserved.
|
||||
# Use of this source code is governed by the 3-clause BSD license
|
||||
# that can be found in the LICENSE file.
|
||||
#
|
||||
from __future__ import unicode_literals
|
||||
from . import LanguageReverseConverter, CaseInsensitiveDict
|
||||
from ..exceptions import LanguageReverseError
|
||||
from ..language import language_converters
|
||||
|
||||
|
||||
class OpenSubtitlesConverter(LanguageReverseConverter):
|
||||
def __init__(self):
|
||||
self.alpha3b_converter = language_converters['alpha3b']
|
||||
self.alpha2_converter = language_converters['alpha2']
|
||||
self.to_opensubtitles = {('por', 'BR'): 'pob', ('gre', None): 'ell', ('srp', None): 'scc', ('srp', 'ME'): 'mne'}
|
||||
self.from_opensubtitles = CaseInsensitiveDict({'pob': ('por', 'BR'), 'pb': ('por', 'BR'), 'ell': ('ell', None),
|
||||
'scc': ('srp', None), 'mne': ('srp', 'ME')})
|
||||
self.codes = (self.alpha2_converter.codes | self.alpha3b_converter.codes | set(['pob', 'pb', 'scc', 'mne']))
|
||||
|
||||
def convert(self, alpha3, country=None, script=None):
|
||||
alpha3b = self.alpha3b_converter.convert(alpha3, country, script)
|
||||
if (alpha3b, country) in self.to_opensubtitles:
|
||||
return self.to_opensubtitles[(alpha3b, country)]
|
||||
return alpha3b
|
||||
|
||||
def reverse(self, opensubtitles):
|
||||
if opensubtitles in self.from_opensubtitles:
|
||||
return self.from_opensubtitles[opensubtitles]
|
||||
for conv in [self.alpha3b_converter, self.alpha2_converter]:
|
||||
try:
|
||||
return conv.reverse(opensubtitles)
|
||||
except LanguageReverseError:
|
||||
pass
|
||||
raise LanguageReverseError(opensubtitles)
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2013 the BabelFish authors. All rights reserved.
|
||||
# Use of this source code is governed by the 3-clause BSD license
|
||||
# that can be found in the LICENSE file.
|
||||
#
|
||||
from __future__ import unicode_literals
|
||||
from . import LanguageConverter
|
||||
from ..exceptions import LanguageConvertError
|
||||
from ..language import LANGUAGE_MATRIX
|
||||
|
||||
|
||||
class ScopeConverter(LanguageConverter):
|
||||
FULLNAME = {'I': 'individual', 'M': 'macrolanguage', 'S': 'special'}
|
||||
SYMBOLS = {}
|
||||
for iso_language in LANGUAGE_MATRIX:
|
||||
SYMBOLS[iso_language.alpha3] = iso_language.scope
|
||||
codes = set(SYMBOLS.values())
|
||||
|
||||
def convert(self, alpha3, country=None, script=None):
|
||||
if self.SYMBOLS[alpha3] in self.FULLNAME:
|
||||
return self.FULLNAME[self.SYMBOLS[alpha3]]
|
||||
raise LanguageConvertError(alpha3, country, script)
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2013 the BabelFish authors. All rights reserved.
|
||||
# Use of this source code is governed by the 3-clause BSD license
|
||||
# that can be found in the LICENSE file.
|
||||
#
|
||||
from __future__ import unicode_literals
|
||||
from . import LanguageConverter
|
||||
from ..exceptions import LanguageConvertError
|
||||
from ..language import LANGUAGE_MATRIX
|
||||
|
||||
|
||||
class LanguageTypeConverter(LanguageConverter):
|
||||
FULLNAME = {'A': 'ancient', 'C': 'constructed', 'E': 'extinct', 'H': 'historical', 'L': 'living', 'S': 'special'}
|
||||
SYMBOLS = {}
|
||||
for iso_language in LANGUAGE_MATRIX:
|
||||
SYMBOLS[iso_language.alpha3] = iso_language.type
|
||||
codes = set(SYMBOLS.values())
|
||||
|
||||
def convert(self, alpha3, country=None, script=None):
|
||||
if self.SYMBOLS[alpha3] in self.FULLNAME:
|
||||
return self.FULLNAME[self.SYMBOLS[alpha3]]
|
||||
raise LanguageConvertError(alpha3, country, script)
|
||||
Executable
+108
@@ -0,0 +1,108 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2013 the BabelFish authors. All rights reserved.
|
||||
# Use of this source code is governed by the 3-clause BSD license
|
||||
# that can be found in the LICENSE file.
|
||||
#
|
||||
from __future__ import unicode_literals
|
||||
from collections import namedtuple
|
||||
from functools import partial
|
||||
# from pkg_resources import resource_stream # @UnresolvedImport
|
||||
import os
|
||||
from .converters import ConverterManager
|
||||
from . import basestr
|
||||
|
||||
|
||||
COUNTRIES = {}
|
||||
COUNTRY_MATRIX = []
|
||||
|
||||
#: The namedtuple used in the :data:`COUNTRY_MATRIX`
|
||||
IsoCountry = namedtuple('IsoCountry', ['name', 'alpha2'])
|
||||
|
||||
f = open(os.path.join(os.path.dirname(__file__), 'data/iso-3166-1.txt'))
|
||||
f.readline()
|
||||
for l in f:
|
||||
iso_country = IsoCountry(*l.decode('utf-8').strip().split(';'))
|
||||
COUNTRIES[iso_country.alpha2] = iso_country.name
|
||||
COUNTRY_MATRIX.append(iso_country)
|
||||
f.close()
|
||||
|
||||
|
||||
class CountryConverterManager(ConverterManager):
|
||||
""":class:`~babelfish.converters.ConverterManager` for country converters"""
|
||||
entry_point = 'babelfish.country_converters'
|
||||
internal_converters = ['name = babelfish.converters.countryname:CountryNameConverter']
|
||||
|
||||
country_converters = CountryConverterManager()
|
||||
|
||||
|
||||
class CountryMeta(type):
|
||||
"""The :class:`Country` metaclass
|
||||
|
||||
Dynamically redirect :meth:`Country.frommycode` to :meth:`Country.fromcode` with the ``mycode`` `converter`
|
||||
|
||||
"""
|
||||
def __getattr__(cls, name):
|
||||
if name.startswith('from'):
|
||||
return partial(cls.fromcode, converter=name[4:])
|
||||
return type.__getattribute__(cls, name)
|
||||
|
||||
|
||||
class Country(CountryMeta(str('CountryBase'), (object,), {})):
|
||||
"""A country on Earth
|
||||
|
||||
A country is represented by a 2-letter code from the ISO-3166 standard
|
||||
|
||||
:param string country: 2-letter ISO-3166 country code
|
||||
|
||||
"""
|
||||
def __init__(self, country):
|
||||
if country not in COUNTRIES:
|
||||
raise ValueError('%r is not a valid country' % country)
|
||||
|
||||
#: ISO-3166 2-letter country code
|
||||
self.alpha2 = country
|
||||
|
||||
@classmethod
|
||||
def fromcode(cls, code, converter):
|
||||
"""Create a :class:`Country` by its `code` using `converter` to
|
||||
:meth:`~babelfish.converters.CountryReverseConverter.reverse` it
|
||||
|
||||
:param string code: the code to reverse
|
||||
:param string converter: name of the :class:`~babelfish.converters.CountryReverseConverter` to use
|
||||
:return: the corresponding :class:`Country` instance
|
||||
:rtype: :class:`Country`
|
||||
|
||||
"""
|
||||
return cls(country_converters[converter].reverse(code))
|
||||
|
||||
def __getstate__(self):
|
||||
return self.alpha2
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.alpha2 = state
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
return country_converters[name].convert(self.alpha2)
|
||||
except KeyError:
|
||||
raise AttributeError(name)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.alpha2)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, basestr):
|
||||
return str(self) == other
|
||||
if not isinstance(other, Country):
|
||||
return False
|
||||
return self.alpha2 == other.alpha2
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def __repr__(self):
|
||||
return '<Country [%s]>' % self
|
||||
|
||||
def __str__(self):
|
||||
return self.alpha2
|
||||
Executable
+250
@@ -0,0 +1,250 @@
|
||||
Country Name;ISO 3166-1-alpha-2 code
|
||||
AFGHANISTAN;AF
|
||||
ÅLAND ISLANDS;AX
|
||||
ALBANIA;AL
|
||||
ALGERIA;DZ
|
||||
AMERICAN SAMOA;AS
|
||||
ANDORRA;AD
|
||||
ANGOLA;AO
|
||||
ANGUILLA;AI
|
||||
ANTARCTICA;AQ
|
||||
ANTIGUA AND BARBUDA;AG
|
||||
ARGENTINA;AR
|
||||
ARMENIA;AM
|
||||
ARUBA;AW
|
||||
AUSTRALIA;AU
|
||||
AUSTRIA;AT
|
||||
AZERBAIJAN;AZ
|
||||
BAHAMAS;BS
|
||||
BAHRAIN;BH
|
||||
BANGLADESH;BD
|
||||
BARBADOS;BB
|
||||
BELARUS;BY
|
||||
BELGIUM;BE
|
||||
BELIZE;BZ
|
||||
BENIN;BJ
|
||||
BERMUDA;BM
|
||||
BHUTAN;BT
|
||||
BOLIVIA, PLURINATIONAL STATE OF;BO
|
||||
BONAIRE, SINT EUSTATIUS AND SABA;BQ
|
||||
BOSNIA AND HERZEGOVINA;BA
|
||||
BOTSWANA;BW
|
||||
BOUVET ISLAND;BV
|
||||
BRAZIL;BR
|
||||
BRITISH INDIAN OCEAN TERRITORY;IO
|
||||
BRUNEI DARUSSALAM;BN
|
||||
BULGARIA;BG
|
||||
BURKINA FASO;BF
|
||||
BURUNDI;BI
|
||||
CAMBODIA;KH
|
||||
CAMEROON;CM
|
||||
CANADA;CA
|
||||
CAPE VERDE;CV
|
||||
CAYMAN ISLANDS;KY
|
||||
CENTRAL AFRICAN REPUBLIC;CF
|
||||
CHAD;TD
|
||||
CHILE;CL
|
||||
CHINA;CN
|
||||
CHRISTMAS ISLAND;CX
|
||||
COCOS (KEELING) ISLANDS;CC
|
||||
COLOMBIA;CO
|
||||
COMOROS;KM
|
||||
CONGO;CG
|
||||
CONGO, THE DEMOCRATIC REPUBLIC OF THE;CD
|
||||
COOK ISLANDS;CK
|
||||
COSTA RICA;CR
|
||||
CÔTE D'IVOIRE;CI
|
||||
CROATIA;HR
|
||||
CUBA;CU
|
||||
CURAÇAO;CW
|
||||
CYPRUS;CY
|
||||
CZECH REPUBLIC;CZ
|
||||
DENMARK;DK
|
||||
DJIBOUTI;DJ
|
||||
DOMINICA;DM
|
||||
DOMINICAN REPUBLIC;DO
|
||||
ECUADOR;EC
|
||||
EGYPT;EG
|
||||
EL SALVADOR;SV
|
||||
EQUATORIAL GUINEA;GQ
|
||||
ERITREA;ER
|
||||
ESTONIA;EE
|
||||
ETHIOPIA;ET
|
||||
FALKLAND ISLANDS (MALVINAS);FK
|
||||
FAROE ISLANDS;FO
|
||||
FIJI;FJ
|
||||
FINLAND;FI
|
||||
FRANCE;FR
|
||||
FRENCH GUIANA;GF
|
||||
FRENCH POLYNESIA;PF
|
||||
FRENCH SOUTHERN TERRITORIES;TF
|
||||
GABON;GA
|
||||
GAMBIA;GM
|
||||
GEORGIA;GE
|
||||
GERMANY;DE
|
||||
GHANA;GH
|
||||
GIBRALTAR;GI
|
||||
GREECE;GR
|
||||
GREENLAND;GL
|
||||
GRENADA;GD
|
||||
GUADELOUPE;GP
|
||||
GUAM;GU
|
||||
GUATEMALA;GT
|
||||
GUERNSEY;GG
|
||||
GUINEA;GN
|
||||
GUINEA-BISSAU;GW
|
||||
GUYANA;GY
|
||||
HAITI;HT
|
||||
HEARD ISLAND AND MCDONALD ISLANDS;HM
|
||||
HOLY SEE (VATICAN CITY STATE);VA
|
||||
HONDURAS;HN
|
||||
HONG KONG;HK
|
||||
HUNGARY;HU
|
||||
ICELAND;IS
|
||||
INDIA;IN
|
||||
INDONESIA;ID
|
||||
IRAN, ISLAMIC REPUBLIC OF;IR
|
||||
IRAQ;IQ
|
||||
IRELAND;IE
|
||||
ISLE OF MAN;IM
|
||||
ISRAEL;IL
|
||||
ITALY;IT
|
||||
JAMAICA;JM
|
||||
JAPAN;JP
|
||||
JERSEY;JE
|
||||
JORDAN;JO
|
||||
KAZAKHSTAN;KZ
|
||||
KENYA;KE
|
||||
KIRIBATI;KI
|
||||
KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF;KP
|
||||
KOREA, REPUBLIC OF;KR
|
||||
KUWAIT;KW
|
||||
KYRGYZSTAN;KG
|
||||
LAO PEOPLE'S DEMOCRATIC REPUBLIC;LA
|
||||
LATVIA;LV
|
||||
LEBANON;LB
|
||||
LESOTHO;LS
|
||||
LIBERIA;LR
|
||||
LIBYA;LY
|
||||
LIECHTENSTEIN;LI
|
||||
LITHUANIA;LT
|
||||
LUXEMBOURG;LU
|
||||
MACAO;MO
|
||||
MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF;MK
|
||||
MADAGASCAR;MG
|
||||
MALAWI;MW
|
||||
MALAYSIA;MY
|
||||
MALDIVES;MV
|
||||
MALI;ML
|
||||
MALTA;MT
|
||||
MARSHALL ISLANDS;MH
|
||||
MARTINIQUE;MQ
|
||||
MAURITANIA;MR
|
||||
MAURITIUS;MU
|
||||
MAYOTTE;YT
|
||||
MEXICO;MX
|
||||
MICRONESIA, FEDERATED STATES OF;FM
|
||||
MOLDOVA, REPUBLIC OF;MD
|
||||
MONACO;MC
|
||||
MONGOLIA;MN
|
||||
MONTENEGRO;ME
|
||||
MONTSERRAT;MS
|
||||
MOROCCO;MA
|
||||
MOZAMBIQUE;MZ
|
||||
MYANMAR;MM
|
||||
NAMIBIA;NA
|
||||
NAURU;NR
|
||||
NEPAL;NP
|
||||
NETHERLANDS;NL
|
||||
NEW CALEDONIA;NC
|
||||
NEW ZEALAND;NZ
|
||||
NICARAGUA;NI
|
||||
NIGER;NE
|
||||
NIGERIA;NG
|
||||
NIUE;NU
|
||||
NORFOLK ISLAND;NF
|
||||
NORTHERN MARIANA ISLANDS;MP
|
||||
NORWAY;NO
|
||||
OMAN;OM
|
||||
PAKISTAN;PK
|
||||
PALAU;PW
|
||||
PALESTINE, STATE OF;PS
|
||||
PANAMA;PA
|
||||
PAPUA NEW GUINEA;PG
|
||||
PARAGUAY;PY
|
||||
PERU;PE
|
||||
PHILIPPINES;PH
|
||||
PITCAIRN;PN
|
||||
POLAND;PL
|
||||
PORTUGAL;PT
|
||||
PUERTO RICO;PR
|
||||
QATAR;QA
|
||||
RÉUNION;RE
|
||||
ROMANIA;RO
|
||||
RUSSIAN FEDERATION;RU
|
||||
RWANDA;RW
|
||||
SAINT BARTHÉLEMY;BL
|
||||
SAINT HELENA, ASCENSION AND TRISTAN DA CUNHA;SH
|
||||
SAINT KITTS AND NEVIS;KN
|
||||
SAINT LUCIA;LC
|
||||
SAINT MARTIN (FRENCH PART);MF
|
||||
SAINT PIERRE AND MIQUELON;PM
|
||||
SAINT VINCENT AND THE GRENADINES;VC
|
||||
SAMOA;WS
|
||||
SAN MARINO;SM
|
||||
SAO TOME AND PRINCIPE;ST
|
||||
SAUDI ARABIA;SA
|
||||
SENEGAL;SN
|
||||
SERBIA;RS
|
||||
SEYCHELLES;SC
|
||||
SIERRA LEONE;SL
|
||||
SINGAPORE;SG
|
||||
SINT MAARTEN (DUTCH PART);SX
|
||||
SLOVAKIA;SK
|
||||
SLOVENIA;SI
|
||||
SOLOMON ISLANDS;SB
|
||||
SOMALIA;SO
|
||||
SOUTH AFRICA;ZA
|
||||
SOUTH GEORGIA AND THE SOUTH SANDWICH ISLANDS;GS
|
||||
SOUTH SUDAN;SS
|
||||
SPAIN;ES
|
||||
SRI LANKA;LK
|
||||
SUDAN;SD
|
||||
SURINAME;SR
|
||||
SVALBARD AND JAN MAYEN;SJ
|
||||
SWAZILAND;SZ
|
||||
SWEDEN;SE
|
||||
SWITZERLAND;CH
|
||||
SYRIAN ARAB REPUBLIC;SY
|
||||
TAIWAN, PROVINCE OF CHINA;TW
|
||||
TAJIKISTAN;TJ
|
||||
TANZANIA, UNITED REPUBLIC OF;TZ
|
||||
THAILAND;TH
|
||||
TIMOR-LESTE;TL
|
||||
TOGO;TG
|
||||
TOKELAU;TK
|
||||
TONGA;TO
|
||||
TRINIDAD AND TOBAGO;TT
|
||||
TUNISIA;TN
|
||||
TURKEY;TR
|
||||
TURKMENISTAN;TM
|
||||
TURKS AND CAICOS ISLANDS;TC
|
||||
TUVALU;TV
|
||||
UGANDA;UG
|
||||
UKRAINE;UA
|
||||
UNITED ARAB EMIRATES;AE
|
||||
UNITED KINGDOM;GB
|
||||
UNITED STATES;US
|
||||
UNITED STATES MINOR OUTLYING ISLANDS;UM
|
||||
URUGUAY;UY
|
||||
UZBEKISTAN;UZ
|
||||
VANUATU;VU
|
||||
VENEZUELA, BOLIVARIAN REPUBLIC OF;VE
|
||||
VIET NAM;VN
|
||||
VIRGIN ISLANDS, BRITISH;VG
|
||||
VIRGIN ISLANDS, U.S.;VI
|
||||
WALLIS AND FUTUNA;WF
|
||||
WESTERN SAHARA;EH
|
||||
YEMEN;YE
|
||||
ZAMBIA;ZM
|
||||
ZIMBABWE;ZW
|
||||
Executable
+7875
File diff suppressed because it is too large
Load Diff
+176
@@ -0,0 +1,176 @@
|
||||
#
|
||||
# ISO 15924 - Codes for the representation of names of scripts
|
||||
# Codes pour la représentation des noms d’écritures
|
||||
# Format:
|
||||
# Code;N°;English Name;Nom français;PVA;Date
|
||||
#
|
||||
|
||||
Afak;439;Afaka;afaka;;2010-12-21
|
||||
Aghb;239;Caucasian Albanian;aghbanien;;2012-10-16
|
||||
Ahom;338;Ahom, Tai Ahom;âhom;;2012-11-01
|
||||
Arab;160;Arabic;arabe;Arabic;2004-05-01
|
||||
Armi;124;Imperial Aramaic;araméen impérial;Imperial_Aramaic;2009-06-01
|
||||
Armn;230;Armenian;arménien;Armenian;2004-05-01
|
||||
Avst;134;Avestan;avestique;Avestan;2009-06-01
|
||||
Bali;360;Balinese;balinais;Balinese;2006-10-10
|
||||
Bamu;435;Bamum;bamoum;Bamum;2009-06-01
|
||||
Bass;259;Bassa Vah;bassa;;2010-03-26
|
||||
Batk;365;Batak;batik;Batak;2010-07-23
|
||||
Beng;325;Bengali;bengalî;Bengali;2004-05-01
|
||||
Blis;550;Blissymbols;symboles Bliss;;2004-05-01
|
||||
Bopo;285;Bopomofo;bopomofo;Bopomofo;2004-05-01
|
||||
Brah;300;Brahmi;brahma;Brahmi;2010-07-23
|
||||
Brai;570;Braille;braille;Braille;2004-05-01
|
||||
Bugi;367;Buginese;bouguis;Buginese;2006-06-21
|
||||
Buhd;372;Buhid;bouhide;Buhid;2004-05-01
|
||||
Cakm;349;Chakma;chakma;Chakma;2012-02-06
|
||||
Cans;440;Unified Canadian Aboriginal Syllabics;syllabaire autochtone canadien unifié;Canadian_Aboriginal;2004-05-29
|
||||
Cari;201;Carian;carien;Carian;2007-07-02
|
||||
Cham;358;Cham;cham (čam, tcham);Cham;2009-11-11
|
||||
Cher;445;Cherokee;tchérokî;Cherokee;2004-05-01
|
||||
Cirt;291;Cirth;cirth;;2004-05-01
|
||||
Copt;204;Coptic;copte;Coptic;2006-06-21
|
||||
Cprt;403;Cypriot;syllabaire chypriote;Cypriot;2004-05-01
|
||||
Cyrl;220;Cyrillic;cyrillique;Cyrillic;2004-05-01
|
||||
Cyrs;221;Cyrillic (Old Church Slavonic variant);cyrillique (variante slavonne);;2004-05-01
|
||||
Deva;315;Devanagari (Nagari);dévanâgarî;Devanagari;2004-05-01
|
||||
Dsrt;250;Deseret (Mormon);déseret (mormon);Deseret;2004-05-01
|
||||
Dupl;755;Duployan shorthand, Duployan stenography;sténographie Duployé;;2010-07-18
|
||||
Egyd;070;Egyptian demotic;démotique égyptien;;2004-05-01
|
||||
Egyh;060;Egyptian hieratic;hiératique égyptien;;2004-05-01
|
||||
Egyp;050;Egyptian hieroglyphs;hiéroglyphes égyptiens;Egyptian_Hieroglyphs;2009-06-01
|
||||
Elba;226;Elbasan;elbasan;;2010-07-18
|
||||
Ethi;430;Ethiopic (Geʻez);éthiopien (geʻez, guèze);Ethiopic;2004-10-25
|
||||
Geor;240;Georgian (Mkhedruli);géorgien (mkhédrouli);Georgian;2004-05-29
|
||||
Geok;241;Khutsuri (Asomtavruli and Nuskhuri);khoutsouri (assomtavrouli et nouskhouri);Georgian;2012-10-16
|
||||
Glag;225;Glagolitic;glagolitique;Glagolitic;2006-06-21
|
||||
Goth;206;Gothic;gotique;Gothic;2004-05-01
|
||||
Gran;343;Grantha;grantha;;2009-11-11
|
||||
Grek;200;Greek;grec;Greek;2004-05-01
|
||||
Gujr;320;Gujarati;goudjarâtî (gujrâtî);Gujarati;2004-05-01
|
||||
Guru;310;Gurmukhi;gourmoukhî;Gurmukhi;2004-05-01
|
||||
Hang;286;Hangul (Hangŭl, Hangeul);hangûl (hangŭl, hangeul);Hangul;2004-05-29
|
||||
Hani;500;Han (Hanzi, Kanji, Hanja);idéogrammes han (sinogrammes);Han;2009-02-23
|
||||
Hano;371;Hanunoo (Hanunóo);hanounóo;Hanunoo;2004-05-29
|
||||
Hans;501;Han (Simplified variant);idéogrammes han (variante simplifiée);;2004-05-29
|
||||
Hant;502;Han (Traditional variant);idéogrammes han (variante traditionnelle);;2004-05-29
|
||||
Hatr;127;Hatran;hatrénien;;2012-11-01
|
||||
Hebr;125;Hebrew;hébreu;Hebrew;2004-05-01
|
||||
Hira;410;Hiragana;hiragana;Hiragana;2004-05-01
|
||||
Hluw;080;Anatolian Hieroglyphs (Luwian Hieroglyphs, Hittite Hieroglyphs);hiéroglyphes anatoliens (hiéroglyphes louvites, hiéroglyphes hittites);;2011-12-09
|
||||
Hmng;450;Pahawh Hmong;pahawh hmong;;2004-05-01
|
||||
Hrkt;412;Japanese syllabaries (alias for Hiragana + Katakana);syllabaires japonais (alias pour hiragana + katakana);Katakana_Or_Hiragana;2011-06-21
|
||||
Hung;176;Old Hungarian (Hungarian Runic);runes hongroises (ancien hongrois);;2012-10-16
|
||||
Inds;610;Indus (Harappan);indus;;2004-05-01
|
||||
Ital;210;Old Italic (Etruscan, Oscan, etc.);ancien italique (étrusque, osque, etc.);Old_Italic;2004-05-29
|
||||
Java;361;Javanese;javanais;Javanese;2009-06-01
|
||||
Jpan;413;Japanese (alias for Han + Hiragana + Katakana);japonais (alias pour han + hiragana + katakana);;2006-06-21
|
||||
Jurc;510;Jurchen;jurchen;;2010-12-21
|
||||
Kali;357;Kayah Li;kayah li;Kayah_Li;2007-07-02
|
||||
Kana;411;Katakana;katakana;Katakana;2004-05-01
|
||||
Khar;305;Kharoshthi;kharochthî;Kharoshthi;2006-06-21
|
||||
Khmr;355;Khmer;khmer;Khmer;2004-05-29
|
||||
Khoj;322;Khojki;khojkî;;2011-06-21
|
||||
Knda;345;Kannada;kannara (canara);Kannada;2004-05-29
|
||||
Kore;287;Korean (alias for Hangul + Han);coréen (alias pour hangûl + han);;2007-06-13
|
||||
Kpel;436;Kpelle;kpèllé;;2010-03-26
|
||||
Kthi;317;Kaithi;kaithî;Kaithi;2009-06-01
|
||||
Lana;351;Tai Tham (Lanna);taï tham (lanna);Tai_Tham;2009-06-01
|
||||
Laoo;356;Lao;laotien;Lao;2004-05-01
|
||||
Latf;217;Latin (Fraktur variant);latin (variante brisée);;2004-05-01
|
||||
Latg;216;Latin (Gaelic variant);latin (variante gaélique);;2004-05-01
|
||||
Latn;215;Latin;latin;Latin;2004-05-01
|
||||
Lepc;335;Lepcha (Róng);lepcha (róng);Lepcha;2007-07-02
|
||||
Limb;336;Limbu;limbou;Limbu;2004-05-29
|
||||
Lina;400;Linear A;linéaire A;;2004-05-01
|
||||
Linb;401;Linear B;linéaire B;Linear_B;2004-05-29
|
||||
Lisu;399;Lisu (Fraser);lisu (Fraser);Lisu;2009-06-01
|
||||
Loma;437;Loma;loma;;2010-03-26
|
||||
Lyci;202;Lycian;lycien;Lycian;2007-07-02
|
||||
Lydi;116;Lydian;lydien;Lydian;2007-07-02
|
||||
Mahj;314;Mahajani;mahâjanî;;2012-10-16
|
||||
Mand;140;Mandaic, Mandaean;mandéen;Mandaic;2010-07-23
|
||||
Mani;139;Manichaean;manichéen;;2007-07-15
|
||||
Maya;090;Mayan hieroglyphs;hiéroglyphes mayas;;2004-05-01
|
||||
Mend;438;Mende Kikakui;mendé kikakui;;2013-10-12
|
||||
Merc;101;Meroitic Cursive;cursif méroïtique;Meroitic_Cursive;2012-02-06
|
||||
Mero;100;Meroitic Hieroglyphs;hiéroglyphes méroïtiques;Meroitic_Hieroglyphs;2012-02-06
|
||||
Mlym;347;Malayalam;malayâlam;Malayalam;2004-05-01
|
||||
Modi;323;Modi, Moḍī;modî;;2013-10-12
|
||||
Moon;218;Moon (Moon code, Moon script, Moon type);écriture Moon;;2006-12-11
|
||||
Mong;145;Mongolian;mongol;Mongolian;2004-05-01
|
||||
Mroo;199;Mro, Mru;mro;;2010-12-21
|
||||
Mtei;337;Meitei Mayek (Meithei, Meetei);meitei mayek;Meetei_Mayek;2009-06-01
|
||||
Mult;323; Multani;multanî;;2012-11-01
|
||||
Mymr;350;Myanmar (Burmese);birman;Myanmar;2004-05-01
|
||||
Narb;106;Old North Arabian (Ancient North Arabian);nord-arabique;;2010-03-26
|
||||
Nbat;159;Nabataean;nabatéen;;2010-03-26
|
||||
Nkgb;420;Nakhi Geba ('Na-'Khi ²Ggŏ-¹baw, Naxi Geba);nakhi géba;;2009-02-23
|
||||
Nkoo;165;N’Ko;n’ko;Nko;2006-10-10
|
||||
Nshu;499;Nüshu;nüshu;;2010-12-21
|
||||
Ogam;212;Ogham;ogam;Ogham;2004-05-01
|
||||
Olck;261;Ol Chiki (Ol Cemet’, Ol, Santali);ol tchiki;Ol_Chiki;2007-07-02
|
||||
Orkh;175;Old Turkic, Orkhon Runic;orkhon;Old_Turkic;2009-06-01
|
||||
Orya;327;Oriya;oriyâ;Oriya;2004-05-01
|
||||
Osma;260;Osmanya;osmanais;Osmanya;2004-05-01
|
||||
Palm;126;Palmyrene;palmyrénien;;2010-03-26
|
||||
Pauc;263;Pau Cin Hau;paou chin haou;;2013-10-12
|
||||
Perm;227;Old Permic;ancien permien;;2004-05-01
|
||||
Phag;331;Phags-pa;’phags pa;Phags_Pa;2006-10-10
|
||||
Phli;131;Inscriptional Pahlavi;pehlevi des inscriptions;Inscriptional_Pahlavi;2009-06-01
|
||||
Phlp;132;Psalter Pahlavi;pehlevi des psautiers;;2007-11-26
|
||||
Phlv;133;Book Pahlavi;pehlevi des livres;;2007-07-15
|
||||
Phnx;115;Phoenician;phénicien;Phoenician;2006-10-10
|
||||
Plrd;282;Miao (Pollard);miao (Pollard);Miao;2012-02-06
|
||||
Prti;130;Inscriptional Parthian;parthe des inscriptions;Inscriptional_Parthian;2009-06-01
|
||||
Qaaa;900;Reserved for private use (start);réservé à l’usage privé (début);;2004-05-29
|
||||
Qabx;949;Reserved for private use (end);réservé à l’usage privé (fin);;2004-05-29
|
||||
Rjng;363;Rejang (Redjang, Kaganga);redjang (kaganga);Rejang;2009-02-23
|
||||
Roro;620;Rongorongo;rongorongo;;2004-05-01
|
||||
Runr;211;Runic;runique;Runic;2004-05-01
|
||||
Samr;123;Samaritan;samaritain;Samaritan;2009-06-01
|
||||
Sara;292;Sarati;sarati;;2004-05-29
|
||||
Sarb;105;Old South Arabian;sud-arabique, himyarite;Old_South_Arabian;2009-06-01
|
||||
Saur;344;Saurashtra;saurachtra;Saurashtra;2007-07-02
|
||||
Sgnw;095;SignWriting;SignÉcriture, SignWriting;;2006-10-10
|
||||
Shaw;281;Shavian (Shaw);shavien (Shaw);Shavian;2004-05-01
|
||||
Shrd;319;Sharada, Śāradā;charada, shard;Sharada;2012-02-06
|
||||
Sidd;302;Siddham, Siddhaṃ, Siddhamātṛkā;siddham;;2013-10-12
|
||||
Sind;318;Khudawadi, Sindhi;khoudawadî, sindhî;;2010-12-21
|
||||
Sinh;348;Sinhala;singhalais;Sinhala;2004-05-01
|
||||
Sora;398;Sora Sompeng;sora sompeng;Sora_Sompeng;2012-02-06
|
||||
Sund;362;Sundanese;sundanais;Sundanese;2007-07-02
|
||||
Sylo;316;Syloti Nagri;sylotî nâgrî;Syloti_Nagri;2006-06-21
|
||||
Syrc;135;Syriac;syriaque;Syriac;2004-05-01
|
||||
Syre;138;Syriac (Estrangelo variant);syriaque (variante estranghélo);;2004-05-01
|
||||
Syrj;137;Syriac (Western variant);syriaque (variante occidentale);;2004-05-01
|
||||
Syrn;136;Syriac (Eastern variant);syriaque (variante orientale);;2004-05-01
|
||||
Tagb;373;Tagbanwa;tagbanoua;Tagbanwa;2004-05-01
|
||||
Takr;321;Takri, Ṭākrī, Ṭāṅkrī;tâkrî;Takri;2012-02-06
|
||||
Tale;353;Tai Le;taï-le;Tai_Le;2004-10-25
|
||||
Talu;354;New Tai Lue;nouveau taï-lue;New_Tai_Lue;2006-06-21
|
||||
Taml;346;Tamil;tamoul;Tamil;2004-05-01
|
||||
Tang;520;Tangut;tangoute;;2010-12-21
|
||||
Tavt;359;Tai Viet;taï viêt;Tai_Viet;2009-06-01
|
||||
Telu;340;Telugu;télougou;Telugu;2004-05-01
|
||||
Teng;290;Tengwar;tengwar;;2004-05-01
|
||||
Tfng;120;Tifinagh (Berber);tifinagh (berbère);Tifinagh;2006-06-21
|
||||
Tglg;370;Tagalog (Baybayin, Alibata);tagal (baybayin, alibata);Tagalog;2009-02-23
|
||||
Thaa;170;Thaana;thâna;Thaana;2004-05-01
|
||||
Thai;352;Thai;thaï;Thai;2004-05-01
|
||||
Tibt;330;Tibetan;tibétain;Tibetan;2004-05-01
|
||||
Tirh;326;Tirhuta;tirhouta;;2011-12-09
|
||||
Ugar;040;Ugaritic;ougaritique;Ugaritic;2004-05-01
|
||||
Vaii;470;Vai;vaï;Vai;2007-07-02
|
||||
Visp;280;Visible Speech;parole visible;;2004-05-01
|
||||
Wara;262;Warang Citi (Varang Kshiti);warang citi;;2009-11-11
|
||||
Wole;480;Woleai;woléaï;;2010-12-21
|
||||
Xpeo;030;Old Persian;cunéiforme persépolitain;Old_Persian;2006-06-21
|
||||
Xsux;020;Cuneiform, Sumero-Akkadian;cunéiforme suméro-akkadien;Cuneiform;2006-10-10
|
||||
Yiii;460;Yi;yi;Yi;2004-05-01
|
||||
Zinh;994;Code for inherited script;codet pour écriture héritée;Inherited;2009-02-23
|
||||
Zmth;995;Mathematical notation;notation mathématique;;2007-11-26
|
||||
Zsym;996;Symbols;symboles;;2007-11-26
|
||||
Zxxx;997;Code for unwritten documents;codet pour les documents non écrits;;2011-06-21
|
||||
Zyyy;998;Code for undetermined script;codet pour écriture indéterminée;Common;2004-05-29
|
||||
Zzzz;999;Code for uncoded script;codet pour écriture non codée;Unknown;2006-10-10
|
||||
+474
@@ -0,0 +1,474 @@
|
||||
IdSubLanguage ISO639 LanguageName UploadEnabled WebEnabled
|
||||
aar aa Afar, afar 0 0
|
||||
abk ab Abkhazian 0 0
|
||||
ace Achinese 0 0
|
||||
ach Acoli 0 0
|
||||
ada Adangme 0 0
|
||||
ady adyghé 0 0
|
||||
afa Afro-Asiatic (Other) 0 0
|
||||
afh Afrihili 0 0
|
||||
afr af Afrikaans 1 0
|
||||
ain Ainu 0 0
|
||||
aka ak Akan 0 0
|
||||
akk Akkadian 0 0
|
||||
alb sq Albanian 1 1
|
||||
ale Aleut 0 0
|
||||
alg Algonquian languages 0 0
|
||||
alt Southern Altai 0 0
|
||||
amh am Amharic 0 0
|
||||
ang English, Old (ca.450-1100) 0 0
|
||||
apa Apache languages 0 0
|
||||
ara ar Arabic 1 1
|
||||
arc Aramaic 0 0
|
||||
arg an Aragonese 0 0
|
||||
arm hy Armenian 1 0
|
||||
arn Araucanian 0 0
|
||||
arp Arapaho 0 0
|
||||
art Artificial (Other) 0 0
|
||||
arw Arawak 0 0
|
||||
asm as Assamese 0 0
|
||||
ast Asturian, Bable 0 0
|
||||
ath Athapascan languages 0 0
|
||||
aus Australian languages 0 0
|
||||
ava av Avaric 0 0
|
||||
ave ae Avestan 0 0
|
||||
awa Awadhi 0 0
|
||||
aym ay Aymara 0 0
|
||||
aze az Azerbaijani 0 0
|
||||
bad Banda 0 0
|
||||
bai Bamileke languages 0 0
|
||||
bak ba Bashkir 0 0
|
||||
bal Baluchi 0 0
|
||||
bam bm Bambara 0 0
|
||||
ban Balinese 0 0
|
||||
baq eu Basque 1 1
|
||||
bas Basa 0 0
|
||||
bat Baltic (Other) 0 0
|
||||
bej Beja 0 0
|
||||
bel be Belarusian 0 0
|
||||
bem Bemba 0 0
|
||||
ben bn Bengali 1 0
|
||||
ber Berber (Other) 0 0
|
||||
bho Bhojpuri 0 0
|
||||
bih bh Bihari 0 0
|
||||
bik Bikol 0 0
|
||||
bin Bini 0 0
|
||||
bis bi Bislama 0 0
|
||||
bla Siksika 0 0
|
||||
bnt Bantu (Other) 0 0
|
||||
bos bs Bosnian 1 0
|
||||
bra Braj 0 0
|
||||
bre br Breton 1 0
|
||||
btk Batak (Indonesia) 0 0
|
||||
bua Buriat 0 0
|
||||
bug Buginese 0 0
|
||||
bul bg Bulgarian 1 1
|
||||
bur my Burmese 1 0
|
||||
byn Blin 0 0
|
||||
cad Caddo 0 0
|
||||
cai Central American Indian (Other) 0 0
|
||||
car Carib 0 0
|
||||
cat ca Catalan 1 1
|
||||
cau Caucasian (Other) 0 0
|
||||
ceb Cebuano 0 0
|
||||
cel Celtic (Other) 0 0
|
||||
cha ch Chamorro 0 0
|
||||
chb Chibcha 0 0
|
||||
che ce Chechen 0 0
|
||||
chg Chagatai 0 0
|
||||
chi zh Chinese 1 1
|
||||
chk Chuukese 0 0
|
||||
chm Mari 0 0
|
||||
chn Chinook jargon 0 0
|
||||
cho Choctaw 0 0
|
||||
chp Chipewyan 0 0
|
||||
chr Cherokee 0 0
|
||||
chu cu Church Slavic 0 0
|
||||
chv cv Chuvash 0 0
|
||||
chy Cheyenne 0 0
|
||||
cmc Chamic languages 0 0
|
||||
cop Coptic 0 0
|
||||
cor kw Cornish 0 0
|
||||
cos co Corsican 0 0
|
||||
cpe Creoles and pidgins, English based (Other) 0 0
|
||||
cpf Creoles and pidgins, French-based (Other) 0 0
|
||||
cpp Creoles and pidgins, Portuguese-based (Other) 0 0
|
||||
cre cr Cree 0 0
|
||||
crh Crimean Tatar 0 0
|
||||
crp Creoles and pidgins (Other) 0 0
|
||||
csb Kashubian 0 0
|
||||
cus Cushitic (Other)' couchitiques, autres langues 0 0
|
||||
cze cs Czech 1 1
|
||||
dak Dakota 0 0
|
||||
dan da Danish 1 1
|
||||
dar Dargwa 0 0
|
||||
day Dayak 0 0
|
||||
del Delaware 0 0
|
||||
den Slave (Athapascan) 0 0
|
||||
dgr Dogrib 0 0
|
||||
din Dinka 0 0
|
||||
div dv Divehi 0 0
|
||||
doi Dogri 0 0
|
||||
dra Dravidian (Other) 0 0
|
||||
dua Duala 0 0
|
||||
dum Dutch, Middle (ca.1050-1350) 0 0
|
||||
dut nl Dutch 1 1
|
||||
dyu Dyula 0 0
|
||||
dzo dz Dzongkha 0 0
|
||||
efi Efik 0 0
|
||||
egy Egyptian (Ancient) 0 0
|
||||
eka Ekajuk 0 0
|
||||
elx Elamite 0 0
|
||||
eng en English 1 1
|
||||
enm English, Middle (1100-1500) 0 0
|
||||
epo eo Esperanto 1 0
|
||||
est et Estonian 1 1
|
||||
ewe ee Ewe 0 0
|
||||
ewo Ewondo 0 0
|
||||
fan Fang 0 0
|
||||
fao fo Faroese 0 0
|
||||
fat Fanti 0 0
|
||||
fij fj Fijian 0 0
|
||||
fil Filipino 0 0
|
||||
fin fi Finnish 1 1
|
||||
fiu Finno-Ugrian (Other) 0 0
|
||||
fon Fon 0 0
|
||||
fre fr French 1 1
|
||||
frm French, Middle (ca.1400-1600) 0 0
|
||||
fro French, Old (842-ca.1400) 0 0
|
||||
fry fy Frisian 0 0
|
||||
ful ff Fulah 0 0
|
||||
fur Friulian 0 0
|
||||
gaa Ga 0 0
|
||||
gay Gayo 0 0
|
||||
gba Gbaya 0 0
|
||||
gem Germanic (Other) 0 0
|
||||
geo ka Georgian 1 1
|
||||
ger de German 1 1
|
||||
gez Geez 0 0
|
||||
gil Gilbertese 0 0
|
||||
gla gd Gaelic 0 0
|
||||
gle ga Irish 0 0
|
||||
glg gl Galician 1 1
|
||||
glv gv Manx 0 0
|
||||
gmh German, Middle High (ca.1050-1500) 0 0
|
||||
goh German, Old High (ca.750-1050) 0 0
|
||||
gon Gondi 0 0
|
||||
gor Gorontalo 0 0
|
||||
got Gothic 0 0
|
||||
grb Grebo 0 0
|
||||
grc Greek, Ancient (to 1453) 0 0
|
||||
ell el Greek 1 1
|
||||
grn gn Guarani 0 0
|
||||
guj gu Gujarati 0 0
|
||||
gwi Gwich´in 0 0
|
||||
hai Haida 0 0
|
||||
hat ht Haitian 0 0
|
||||
hau ha Hausa 0 0
|
||||
haw Hawaiian 0 0
|
||||
heb he Hebrew 1 1
|
||||
her hz Herero 0 0
|
||||
hil Hiligaynon 0 0
|
||||
him Himachali 0 0
|
||||
hin hi Hindi 1 1
|
||||
hit Hittite 0 0
|
||||
hmn Hmong 0 0
|
||||
hmo ho Hiri Motu 0 0
|
||||
hrv hr Croatian 1 1
|
||||
hun hu Hungarian 1 1
|
||||
hup Hupa 0 0
|
||||
iba Iban 0 0
|
||||
ibo ig Igbo 0 0
|
||||
ice is Icelandic 1 1
|
||||
ido io Ido 0 0
|
||||
iii ii Sichuan Yi 0 0
|
||||
ijo Ijo 0 0
|
||||
iku iu Inuktitut 0 0
|
||||
ile ie Interlingue 0 0
|
||||
ilo Iloko 0 0
|
||||
ina ia Interlingua (International Auxiliary Language Asso 0 0
|
||||
inc Indic (Other) 0 0
|
||||
ind id Indonesian 1 1
|
||||
ine Indo-European (Other) 0 0
|
||||
inh Ingush 0 0
|
||||
ipk ik Inupiaq 0 0
|
||||
ira Iranian (Other) 0 0
|
||||
iro Iroquoian languages 0 0
|
||||
ita it Italian 1 1
|
||||
jav jv Javanese 0 0
|
||||
jpn ja Japanese 1 1
|
||||
jpr Judeo-Persian 0 0
|
||||
jrb Judeo-Arabic 0 0
|
||||
kaa Kara-Kalpak 0 0
|
||||
kab Kabyle 0 0
|
||||
kac Kachin 0 0
|
||||
kal kl Kalaallisut 0 0
|
||||
kam Kamba 0 0
|
||||
kan kn Kannada 0 0
|
||||
kar Karen 0 0
|
||||
kas ks Kashmiri 0 0
|
||||
kau kr Kanuri 0 0
|
||||
kaw Kawi 0 0
|
||||
kaz kk Kazakh 1 0
|
||||
kbd Kabardian 0 0
|
||||
kha Khasi 0 0
|
||||
khi Khoisan (Other) 0 0
|
||||
khm km Khmer 1 1
|
||||
kho Khotanese 0 0
|
||||
kik ki Kikuyu 0 0
|
||||
kin rw Kinyarwanda 0 0
|
||||
kir ky Kirghiz 0 0
|
||||
kmb Kimbundu 0 0
|
||||
kok Konkani 0 0
|
||||
kom kv Komi 0 0
|
||||
kon kg Kongo 0 0
|
||||
kor ko Korean 1 1
|
||||
kos Kosraean 0 0
|
||||
kpe Kpelle 0 0
|
||||
krc Karachay-Balkar 0 0
|
||||
kro Kru 0 0
|
||||
kru Kurukh 0 0
|
||||
kua kj Kuanyama 0 0
|
||||
kum Kumyk 0 0
|
||||
kur ku Kurdish 0 0
|
||||
kut Kutenai 0 0
|
||||
lad Ladino 0 0
|
||||
lah Lahnda 0 0
|
||||
lam Lamba 0 0
|
||||
lao lo Lao 0 0
|
||||
lat la Latin 0 0
|
||||
lav lv Latvian 1 0
|
||||
lez Lezghian 0 0
|
||||
lim li Limburgan 0 0
|
||||
lin ln Lingala 0 0
|
||||
lit lt Lithuanian 1 0
|
||||
lol Mongo 0 0
|
||||
loz Lozi 0 0
|
||||
ltz lb Luxembourgish 1 0
|
||||
lua Luba-Lulua 0 0
|
||||
lub lu Luba-Katanga 0 0
|
||||
lug lg Ganda 0 0
|
||||
lui Luiseno 0 0
|
||||
lun Lunda 0 0
|
||||
luo Luo (Kenya and Tanzania) 0 0
|
||||
lus lushai 0 0
|
||||
mac mk Macedonian 1 1
|
||||
mad Madurese 0 0
|
||||
mag Magahi 0 0
|
||||
mah mh Marshallese 0 0
|
||||
mai Maithili 0 0
|
||||
mak Makasar 0 0
|
||||
mal ml Malayalam 1 0
|
||||
man Mandingo 0 0
|
||||
mao mi Maori 0 0
|
||||
map Austronesian (Other) 0 0
|
||||
mar mr Marathi 0 0
|
||||
mas Masai 0 0
|
||||
may ms Malay 1 1
|
||||
mdf Moksha 0 0
|
||||
mdr Mandar 0 0
|
||||
men Mende 0 0
|
||||
mga Irish, Middle (900-1200) 0 0
|
||||
mic Mi'kmaq 0 0
|
||||
min Minangkabau 0 0
|
||||
mis Miscellaneous languages 0 0
|
||||
mkh Mon-Khmer (Other) 0 0
|
||||
mlg mg Malagasy 0 0
|
||||
mlt mt Maltese 0 0
|
||||
mnc Manchu 0 0
|
||||
mni Manipuri 0 0
|
||||
mno Manobo languages 0 0
|
||||
moh Mohawk 0 0
|
||||
mol mo Moldavian 0 0
|
||||
mon mn Mongolian 1 0
|
||||
mos Mossi 0 0
|
||||
mwl Mirandese 0 0
|
||||
mul Multiple languages 0 0
|
||||
mun Munda languages 0 0
|
||||
mus Creek 0 0
|
||||
mwr Marwari 0 0
|
||||
myn Mayan languages 0 0
|
||||
myv Erzya 0 0
|
||||
nah Nahuatl 0 0
|
||||
nai North American Indian 0 0
|
||||
nap Neapolitan 0 0
|
||||
nau na Nauru 0 0
|
||||
nav nv Navajo 0 0
|
||||
nbl nr Ndebele, South 0 0
|
||||
nde nd Ndebele, North 0 0
|
||||
ndo ng Ndonga 0 0
|
||||
nds Low German 0 0
|
||||
nep ne Nepali 0 0
|
||||
new Nepal Bhasa 0 0
|
||||
nia Nias 0 0
|
||||
nic Niger-Kordofanian (Other) 0 0
|
||||
niu Niuean 0 0
|
||||
nno nn Norwegian Nynorsk 0 0
|
||||
nob nb Norwegian Bokmal 0 0
|
||||
nog Nogai 0 0
|
||||
non Norse, Old 0 0
|
||||
nor no Norwegian 1 1
|
||||
nso Northern Sotho 0 0
|
||||
nub Nubian languages 0 0
|
||||
nwc Classical Newari 0 0
|
||||
nya ny Chichewa 0 0
|
||||
nym Nyamwezi 0 0
|
||||
nyn Nyankole 0 0
|
||||
nyo Nyoro 0 0
|
||||
nzi Nzima 0 0
|
||||
oci oc Occitan 1 1
|
||||
oji oj Ojibwa 0 0
|
||||
ori or Oriya 0 0
|
||||
orm om Oromo 0 0
|
||||
osa Osage 0 0
|
||||
oss os Ossetian 0 0
|
||||
ota Turkish, Ottoman (1500-1928) 0 0
|
||||
oto Otomian languages 0 0
|
||||
paa Papuan (Other) 0 0
|
||||
pag Pangasinan 0 0
|
||||
pal Pahlavi 0 0
|
||||
pam Pampanga 0 0
|
||||
pan pa Panjabi 0 0
|
||||
pap Papiamento 0 0
|
||||
pau Palauan 0 0
|
||||
peo Persian, Old (ca.600-400 B.C.) 0 0
|
||||
per fa Persian 1 1
|
||||
phi Philippine (Other) 0 0
|
||||
phn Phoenician 0 0
|
||||
pli pi Pali 0 0
|
||||
pol pl Polish 1 1
|
||||
pon Pohnpeian 0 0
|
||||
por pt Portuguese 1 1
|
||||
pra Prakrit languages 0 0
|
||||
pro Provençal, Old (to 1500) 0 0
|
||||
pus ps Pushto 0 0
|
||||
que qu Quechua 0 0
|
||||
raj Rajasthani 0 0
|
||||
rap Rapanui 0 0
|
||||
rar Rarotongan 0 0
|
||||
roa Romance (Other) 0 0
|
||||
roh rm Raeto-Romance 0 0
|
||||
rom Romany 0 0
|
||||
run rn Rundi 0 0
|
||||
rup Aromanian 0 0
|
||||
rus ru Russian 1 1
|
||||
sad Sandawe 0 0
|
||||
sag sg Sango 0 0
|
||||
sah Yakut 0 0
|
||||
sai South American Indian (Other) 0 0
|
||||
sal Salishan languages 0 0
|
||||
sam Samaritan Aramaic 0 0
|
||||
san sa Sanskrit 0 0
|
||||
sas Sasak 0 0
|
||||
sat Santali 0 0
|
||||
scc sr Serbian 1 1
|
||||
scn Sicilian 0 0
|
||||
sco Scots 0 0
|
||||
sel Selkup 0 0
|
||||
sem Semitic (Other) 0 0
|
||||
sga Irish, Old (to 900) 0 0
|
||||
sgn Sign Languages 0 0
|
||||
shn Shan 0 0
|
||||
sid Sidamo 0 0
|
||||
sin si Sinhalese 1 1
|
||||
sio Siouan languages 0 0
|
||||
sit Sino-Tibetan (Other) 0 0
|
||||
sla Slavic (Other) 0 0
|
||||
slo sk Slovak 1 1
|
||||
slv sl Slovenian 1 1
|
||||
sma Southern Sami 0 0
|
||||
sme se Northern Sami 0 0
|
||||
smi Sami languages (Other) 0 0
|
||||
smj Lule Sami 0 0
|
||||
smn Inari Sami 0 0
|
||||
smo sm Samoan 0 0
|
||||
sms Skolt Sami 0 0
|
||||
sna sn Shona 0 0
|
||||
snd sd Sindhi 0 0
|
||||
snk Soninke 0 0
|
||||
sog Sogdian 0 0
|
||||
som so Somali 0 0
|
||||
son Songhai 0 0
|
||||
sot st Sotho, Southern 0 0
|
||||
spa es Spanish 1 1
|
||||
srd sc Sardinian 0 0
|
||||
srr Serer 0 0
|
||||
ssa Nilo-Saharan (Other) 0 0
|
||||
ssw ss Swati 0 0
|
||||
suk Sukuma 0 0
|
||||
sun su Sundanese 0 0
|
||||
sus Susu 0 0
|
||||
sux Sumerian 0 0
|
||||
swa sw Swahili 1 0
|
||||
swe sv Swedish 1 1
|
||||
syr Syriac 1 0
|
||||
tah ty Tahitian 0 0
|
||||
tai Tai (Other) 0 0
|
||||
tam ta Tamil 1 0
|
||||
tat tt Tatar 0 0
|
||||
tel te Telugu 1 0
|
||||
tem Timne 0 0
|
||||
ter Tereno 0 0
|
||||
tet Tetum 0 0
|
||||
tgk tg Tajik 0 0
|
||||
tgl tl Tagalog 1 1
|
||||
tha th Thai 1 1
|
||||
tib bo Tibetan 0 0
|
||||
tig Tigre 0 0
|
||||
tir ti Tigrinya 0 0
|
||||
tiv Tiv 0 0
|
||||
tkl Tokelau 0 0
|
||||
tlh Klingon 0 0
|
||||
tli Tlingit 0 0
|
||||
tmh Tamashek 0 0
|
||||
tog Tonga (Nyasa) 0 0
|
||||
ton to Tonga (Tonga Islands) 0 0
|
||||
tpi Tok Pisin 0 0
|
||||
tsi Tsimshian 0 0
|
||||
tsn tn Tswana 0 0
|
||||
tso ts Tsonga 0 0
|
||||
tuk tk Turkmen 0 0
|
||||
tum Tumbuka 0 0
|
||||
tup Tupi languages 0 0
|
||||
tur tr Turkish 1 1
|
||||
tut Altaic (Other) 0 0
|
||||
tvl Tuvalu 0 0
|
||||
twi tw Twi 0 0
|
||||
tyv Tuvinian 0 0
|
||||
udm Udmurt 0 0
|
||||
uga Ugaritic 0 0
|
||||
uig ug Uighur 0 0
|
||||
ukr uk Ukrainian 1 1
|
||||
umb Umbundu 0 0
|
||||
und Undetermined 0 0
|
||||
urd ur Urdu 1 0
|
||||
uzb uz Uzbek 0 0
|
||||
vai Vai 0 0
|
||||
ven ve Venda 0 0
|
||||
vie vi Vietnamese 1 1
|
||||
vol vo Volapük 0 0
|
||||
vot Votic 0 0
|
||||
wak Wakashan languages 0 0
|
||||
wal Walamo 0 0
|
||||
war Waray 0 0
|
||||
was Washo 0 0
|
||||
wel cy Welsh 0 0
|
||||
wen Sorbian languages 0 0
|
||||
wln wa Walloon 0 0
|
||||
wol wo Wolof 0 0
|
||||
xal Kalmyk 0 0
|
||||
xho xh Xhosa 0 0
|
||||
yao Yao 0 0
|
||||
yap Yapese 0 0
|
||||
yid yi Yiddish 0 0
|
||||
yor yo Yoruba 0 0
|
||||
ypk Yupik languages 0 0
|
||||
zap Zapotec 0 0
|
||||
zen Zenaga 0 0
|
||||
zha za Zhuang 0 0
|
||||
znd Zande 0 0
|
||||
zul zu Zulu 0 0
|
||||
zun Zuni 0 0
|
||||
rum ro Romanian 1 1
|
||||
pob pb Brazilian 1 1
|
||||
mne Montenegrin 1 0
|
||||
Executable
+85
@@ -0,0 +1,85 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2013 the BabelFish authors. All rights reserved.
|
||||
# Use of this source code is governed by the 3-clause BSD license
|
||||
# that can be found in the LICENSE file.
|
||||
#
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
"""Base class for all exceptions in babelfish"""
|
||||
pass
|
||||
|
||||
|
||||
class LanguageError(Error, AttributeError):
|
||||
"""Base class for all language exceptions in babelfish"""
|
||||
pass
|
||||
|
||||
|
||||
class LanguageConvertError(LanguageError):
|
||||
"""Exception raised by converters when :meth:`~babelfish.converters.LanguageConverter.convert` fails
|
||||
|
||||
:param string alpha3: alpha3 code that failed conversion
|
||||
:param country: country code that failed conversion, if any
|
||||
:type country: string or None
|
||||
:param script: script code that failed conversion, if any
|
||||
:type script: string or None
|
||||
|
||||
"""
|
||||
def __init__(self, alpha3, country=None, script=None):
|
||||
self.alpha3 = alpha3
|
||||
self.country = country
|
||||
self.script = script
|
||||
|
||||
def __str__(self):
|
||||
s = self.alpha3
|
||||
if self.country is not None:
|
||||
s += '-' + self.country
|
||||
if self.script is not None:
|
||||
s += '-' + self.script
|
||||
return s
|
||||
|
||||
|
||||
class LanguageReverseError(LanguageError):
|
||||
"""Exception raised by converters when :meth:`~babelfish.converters.LanguageReverseConverter.reverse` fails
|
||||
|
||||
:param string code: code that failed reverse conversion
|
||||
|
||||
"""
|
||||
def __init__(self, code):
|
||||
self.code = code
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.code)
|
||||
|
||||
|
||||
class CountryError(Error, AttributeError):
|
||||
"""Base class for all country exceptions in babelfish"""
|
||||
pass
|
||||
|
||||
|
||||
class CountryConvertError(CountryError):
|
||||
"""Exception raised by converters when :meth:`~babelfish.converters.CountryConverter.convert` fails
|
||||
|
||||
:param string alpha2: alpha2 code that failed conversion
|
||||
|
||||
"""
|
||||
def __init__(self, alpha2):
|
||||
self.alpha2 = alpha2
|
||||
|
||||
def __str__(self):
|
||||
return self.alpha2
|
||||
|
||||
|
||||
class CountryReverseError(CountryError):
|
||||
"""Exception raised by converters when :meth:`~babelfish.converters.CountryReverseConverter.reverse` fails
|
||||
|
||||
:param string code: code that failed reverse conversion
|
||||
|
||||
"""
|
||||
def __init__(self, code):
|
||||
self.code = code
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.code)
|
||||
Executable
+186
@@ -0,0 +1,186 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2013 the BabelFish authors. All rights reserved.
|
||||
# Use of this source code is governed by the 3-clause BSD license
|
||||
# that can be found in the LICENSE file.
|
||||
#
|
||||
from __future__ import unicode_literals
|
||||
from collections import namedtuple
|
||||
from functools import partial
|
||||
import os
|
||||
# from pkg_resources import resource_stream # @UnresolvedImport
|
||||
from .converters import ConverterManager
|
||||
from .country import Country
|
||||
from .exceptions import LanguageConvertError
|
||||
from .script import Script
|
||||
from . import basestr
|
||||
|
||||
|
||||
LANGUAGES = set()
|
||||
LANGUAGE_MATRIX = []
|
||||
|
||||
#: The namedtuple used in the :data:`LANGUAGE_MATRIX`
|
||||
IsoLanguage = namedtuple('IsoLanguage', ['alpha3', 'alpha3b', 'alpha3t', 'alpha2', 'scope', 'type', 'name', 'comment'])
|
||||
|
||||
f = open(os.path.join(os.path.dirname(__file__), 'data/iso-639-3.tab'))
|
||||
f.readline()
|
||||
for l in f:
|
||||
iso_language = IsoLanguage(*l.decode('utf-8').split('\t'))
|
||||
LANGUAGES.add(iso_language.alpha3)
|
||||
LANGUAGE_MATRIX.append(iso_language)
|
||||
f.close()
|
||||
|
||||
|
||||
class LanguageConverterManager(ConverterManager):
|
||||
""":class:`~babelfish.converters.ConverterManager` for language converters"""
|
||||
entry_point = 'babelfish.language_converters'
|
||||
internal_converters = ['alpha2 = babelfish.converters.alpha2:Alpha2Converter',
|
||||
'alpha3b = babelfish.converters.alpha3b:Alpha3BConverter',
|
||||
'alpha3t = babelfish.converters.alpha3t:Alpha3TConverter',
|
||||
'name = babelfish.converters.name:NameConverter',
|
||||
'scope = babelfish.converters.scope:ScopeConverter',
|
||||
'type = babelfish.converters.type:LanguageTypeConverter',
|
||||
'opensubtitles = babelfish.converters.opensubtitles:OpenSubtitlesConverter']
|
||||
|
||||
language_converters = LanguageConverterManager()
|
||||
|
||||
|
||||
class LanguageMeta(type):
|
||||
"""The :class:`Language` metaclass
|
||||
|
||||
Dynamically redirect :meth:`Language.frommycode` to :meth:`Language.fromcode` with the ``mycode`` `converter`
|
||||
|
||||
"""
|
||||
def __getattr__(cls, name):
|
||||
if name.startswith('from'):
|
||||
return partial(cls.fromcode, converter=name[4:])
|
||||
return type.__getattribute__(cls, name)
|
||||
|
||||
|
||||
class Language(LanguageMeta(str('LanguageBase'), (object,), {})):
|
||||
"""A human language
|
||||
|
||||
A human language is composed of a language part following the ISO-639
|
||||
standard and can be country-specific when a :class:`~babelfish.country.Country`
|
||||
is specified.
|
||||
|
||||
The :class:`Language` is extensible with custom converters (see :ref:`custom_converters`)
|
||||
|
||||
:param string language: the language as a 3-letter ISO-639-3 code
|
||||
:param country: the country (if any) as a 2-letter ISO-3166 code or :class:`~babelfish.country.Country` instance
|
||||
:type country: string or :class:`~babelfish.country.Country` or None
|
||||
:param script: the script (if any) as a 4-letter ISO-15924 code or :class:`~babelfish.script.Script` instance
|
||||
:type script: string or :class:`~babelfish.script.Script` or None
|
||||
:param unknown: the unknown language as a three-letters ISO-639-3 code to use as fallback
|
||||
:type unknown: string or None
|
||||
:raise: ValueError if the language could not be recognized and `unknown` is ``None``
|
||||
|
||||
"""
|
||||
def __init__(self, language, country=None, script=None, unknown=None):
|
||||
if unknown is not None and language not in LANGUAGES:
|
||||
language = unknown
|
||||
if language not in LANGUAGES:
|
||||
raise ValueError('%r is not a valid language' % language)
|
||||
self.alpha3 = language
|
||||
self.country = None
|
||||
if isinstance(country, Country):
|
||||
self.country = country
|
||||
elif country is None:
|
||||
self.country = None
|
||||
else:
|
||||
self.country = Country(country)
|
||||
self.script = None
|
||||
if isinstance(script, Script):
|
||||
self.script = script
|
||||
elif script is None:
|
||||
self.script = None
|
||||
else:
|
||||
self.script = Script(script)
|
||||
|
||||
@classmethod
|
||||
def fromcode(cls, code, converter):
|
||||
"""Create a :class:`Language` by its `code` using `converter` to
|
||||
:meth:`~babelfish.converters.LanguageReverseConverter.reverse` it
|
||||
|
||||
:param string code: the code to reverse
|
||||
:param string converter: name of the :class:`~babelfish.converters.LanguageReverseConverter` to use
|
||||
:return: the corresponding :class:`Language` instance
|
||||
:rtype: :class:`Language`
|
||||
|
||||
"""
|
||||
return cls(*language_converters[converter].reverse(code))
|
||||
|
||||
@classmethod
|
||||
def fromietf(cls, ietf):
|
||||
"""Create a :class:`Language` by from an IETF language code
|
||||
|
||||
:param string ietf: the ietf code
|
||||
:return: the corresponding :class:`Language` instance
|
||||
:rtype: :class:`Language`
|
||||
|
||||
"""
|
||||
subtags = ietf.split('-')
|
||||
language_subtag = subtags.pop(0).lower()
|
||||
if len(language_subtag) == 2:
|
||||
language = cls.fromalpha2(language_subtag)
|
||||
else:
|
||||
language = cls(language_subtag)
|
||||
while subtags:
|
||||
subtag = subtags.pop(0)
|
||||
if len(subtag) == 2:
|
||||
language.country = Country(subtag.upper())
|
||||
else:
|
||||
language.script = Script(subtag.capitalize())
|
||||
if language.script is not None:
|
||||
if subtags:
|
||||
raise ValueError('Wrong IETF format. Unmatched subtags: %r' % subtags)
|
||||
break
|
||||
return language
|
||||
|
||||
def __getstate__(self):
|
||||
return self.alpha3, self.country, self.script
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.alpha3, self.country, self.script = state
|
||||
|
||||
def __getattr__(self, name):
|
||||
alpha3 = self.alpha3
|
||||
country = self.country.alpha2 if self.country is not None else None
|
||||
script = self.script.code if self.script is not None else None
|
||||
try:
|
||||
return language_converters[name].convert(alpha3, country, script)
|
||||
except KeyError:
|
||||
raise AttributeError(name)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(str(self))
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, basestr):
|
||||
return str(self) == other
|
||||
if not isinstance(other, Language):
|
||||
return False
|
||||
return (self.alpha3 == other.alpha3 and
|
||||
self.country == other.country and
|
||||
self.script == other.script)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def __bool__(self):
|
||||
return self.alpha3 != 'und'
|
||||
__nonzero__ = __bool__
|
||||
|
||||
def __repr__(self):
|
||||
return '<Language [%s]>' % self
|
||||
|
||||
def __str__(self):
|
||||
try:
|
||||
s = self.alpha2
|
||||
except LanguageConvertError:
|
||||
s = self.alpha3
|
||||
if self.country is not None:
|
||||
s += '-' + str(self.country)
|
||||
if self.script is not None:
|
||||
s += '-' + str(self.script)
|
||||
return s
|
||||
Executable
+78
@@ -0,0 +1,78 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2013 the BabelFish authors. All rights reserved.
|
||||
# Use of this source code is governed by the 3-clause BSD license
|
||||
# that can be found in the LICENSE file.
|
||||
#
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
from collections import namedtuple
|
||||
# from pkg_resources import resource_stream # @UnresolvedImport
|
||||
from . import basestr
|
||||
|
||||
#: Script code to script name mapping
|
||||
SCRIPTS = {}
|
||||
|
||||
#: List of countries in the ISO-15924 as namedtuple of code, number, name, french_name, pva and date
|
||||
SCRIPT_MATRIX = []
|
||||
|
||||
#: The namedtuple used in the :data:`SCRIPT_MATRIX`
|
||||
IsoScript = namedtuple('IsoScript', ['code', 'number', 'name', 'french_name', 'pva', 'date'])
|
||||
|
||||
f = open(os.path.join(os.path.dirname(__file__), 'data/iso15924-utf8-20131012.txt'))
|
||||
f.readline()
|
||||
for l in f:
|
||||
l = l.decode('utf-8').strip()
|
||||
if not l or l.startswith('#'):
|
||||
continue
|
||||
script = IsoScript._make(l.split(';'))
|
||||
SCRIPT_MATRIX.append(script)
|
||||
SCRIPTS[script.code] = script.name
|
||||
f.close()
|
||||
|
||||
|
||||
class Script(object):
|
||||
"""A human writing system
|
||||
|
||||
A script is represented by a 4-letter code from the ISO-15924 standard
|
||||
|
||||
:param string script: 4-letter ISO-15924 script code
|
||||
|
||||
"""
|
||||
def __init__(self, script):
|
||||
if script not in SCRIPTS:
|
||||
raise ValueError('%r is not a valid script' % script)
|
||||
|
||||
#: ISO-15924 4-letter script code
|
||||
self.code = script
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""English name of the script"""
|
||||
return SCRIPTS[self.code]
|
||||
|
||||
def __getstate__(self):
|
||||
return self.code
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.code = state
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.code)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, basestr):
|
||||
return self.code == other
|
||||
if not isinstance(other, Script):
|
||||
return False
|
||||
return self.code == other.code
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def __repr__(self):
|
||||
return '<Script [%s]>' % self
|
||||
|
||||
def __str__(self):
|
||||
return self.code
|
||||
@@ -1,5 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from client import Client
|
||||
|
||||
__all__ = ["Client"]
|
||||
@@ -1,92 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ------------------------------------------------------------
|
||||
# Gestiona el cache del servidor torrent:
|
||||
# Guarda los .torrent generado
|
||||
# Guarda los .resume de cada torrent
|
||||
# ------------------------------------------------------------
|
||||
import base64
|
||||
import os.path
|
||||
import re
|
||||
import traceback
|
||||
|
||||
try:
|
||||
import xbmc, xbmcgui
|
||||
except:
|
||||
pass
|
||||
|
||||
from platformcode import config
|
||||
LIBTORRENT_PATH = config.get_setting("libtorrent_path", server="torrent", default='')
|
||||
|
||||
from servers import torrent as torr
|
||||
lt, e, e1, e2 = torr.import_libtorrent(LIBTORRENT_PATH)
|
||||
|
||||
|
||||
class Cache(object):
|
||||
CACHE_DIR = '.cache'
|
||||
|
||||
def __init__(self, path):
|
||||
|
||||
if not os.path.isdir(path):
|
||||
os.makedirs(path)
|
||||
self.path = os.path.join(path, Cache.CACHE_DIR)
|
||||
if not os.path.isdir(self.path):
|
||||
os.makedirs(self.path)
|
||||
|
||||
def _tname(self, info_hash):
|
||||
return os.path.join(self.path, info_hash.upper() + '.torrent')
|
||||
|
||||
def _rname(self, info_hash):
|
||||
return os.path.join(self.path, info_hash.upper() + '.resume')
|
||||
|
||||
def save_resume(self, info_hash, data):
|
||||
f = open(self._rname(info_hash), 'wb')
|
||||
f.write(data)
|
||||
f.close()
|
||||
|
||||
def get_resume(self, url=None, info_hash=None):
|
||||
if url:
|
||||
info_hash = self._index.get(url)
|
||||
if not info_hash:
|
||||
return
|
||||
rname = self._rname(info_hash)
|
||||
if os.access(rname, os.R_OK):
|
||||
f = open(rname, 'rb')
|
||||
v = f.read()
|
||||
f.close()
|
||||
return v
|
||||
|
||||
def file_complete(self, torrent):
|
||||
info_hash = str(torrent.info_hash())
|
||||
nt = lt.create_torrent(torrent)
|
||||
tname = self._tname(info_hash)
|
||||
f = open(tname, 'wb')
|
||||
f.write(lt.bencode(nt.generate()))
|
||||
f.close()
|
||||
|
||||
def get_torrent(self, url=None, info_hash=None):
|
||||
if url:
|
||||
info_hash = self._index.get(url)
|
||||
if not info_hash:
|
||||
return
|
||||
tname = self._tname(info_hash)
|
||||
if os.access(tname, os.R_OK):
|
||||
return tname
|
||||
|
||||
magnet_re = re.compile('xt=urn:btih:([0-9A-Za-z]+)')
|
||||
hexa_chars = re.compile('^[0-9A-F]+$')
|
||||
|
||||
@staticmethod
|
||||
def hash_from_magnet(m):
|
||||
res = Cache.magnet_re.search(m)
|
||||
if res:
|
||||
ih = res.group(1).upper()
|
||||
if len(ih) == 40 and Cache.hexa_chars.match(ih):
|
||||
return res.group(1).upper()
|
||||
elif len(ih) == 32:
|
||||
s = base64.b32decode(ih)
|
||||
return "".join("{:02X}".format(ord(c)) for c in s)
|
||||
else:
|
||||
raise ValueError('Not BT magnet link')
|
||||
|
||||
else:
|
||||
raise ValueError('Not BT magnet link')
|
||||
@@ -1,669 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import pickle
|
||||
import random
|
||||
import time
|
||||
import urllib
|
||||
|
||||
try:
|
||||
import xbmc, xbmcgui
|
||||
except:
|
||||
pass
|
||||
|
||||
from platformcode import config, logger
|
||||
LIBTORRENT_PATH = config.get_setting("libtorrent_path", server="torrent", default='')
|
||||
|
||||
from servers import torrent as torr
|
||||
lt, e, e1, e2 = torr.import_libtorrent(LIBTORRENT_PATH)
|
||||
|
||||
from cache import Cache
|
||||
from dispatcher import Dispatcher
|
||||
from file import File
|
||||
from handler import Handler
|
||||
from monitor import Monitor
|
||||
from resume_data import ResumeData
|
||||
from server import Server
|
||||
|
||||
try:
|
||||
BUFFER = int(config.get_setting("bt_buffer", server="torrent", default="50"))
|
||||
except:
|
||||
BUFFER = 50
|
||||
config.set_setting("bt_buffer", "50", server="torrent")
|
||||
DOWNLOAD_PATH = config.get_setting("bt_download_path", server="torrent", default=config.get_setting("downloadpath"))
|
||||
BACKGROUND = config.get_setting("mct_background_download", server="torrent", default=True)
|
||||
RAR = config.get_setting("mct_rar_unpack", server="torrent", default=True)
|
||||
msg_header = 'Client Torrent BT'
|
||||
|
||||
|
||||
class Client(object):
|
||||
INITIAL_TRACKERS = ['udp://tracker.openbittorrent.com:80',
|
||||
'udp://tracker.istole.it:80',
|
||||
'udp://open.demonii.com:80',
|
||||
'udp://tracker.coppersurfer.tk:80',
|
||||
'udp://tracker.leechers-paradise.org:6969',
|
||||
'udp://exodus.desync.com:6969',
|
||||
'udp://tracker.publicbt.com:80',
|
||||
'http://tracker.torrentbay.to:6969/announce',
|
||||
'http://tracker.pow7.com/announce',
|
||||
'udp://tracker.ccc.de:80/announce',
|
||||
'udp://open.demonii.com:1337',
|
||||
'http://9.rarbg.com:2710/announce',
|
||||
'http://bt.careland.com.cn:6969/announce',
|
||||
'http://explodie.org:6969/announce',
|
||||
'http://mgtracker.org:2710/announce',
|
||||
'http://tracker.best-torrents.net:6969/announce',
|
||||
'http://tracker.tfile.me/announce',
|
||||
'http://tracker1.wasabii.com.tw:6969/announce',
|
||||
'udp://9.rarbg.com:2710/announce',
|
||||
'udp://9.rarbg.me:2710/announce',
|
||||
'udp://coppersurfer.tk:6969/announce',
|
||||
'http://www.spanishtracker.com:2710/announce',
|
||||
'http://www.todotorrents.com:2710/announce'
|
||||
] ### Added some trackers from MCT
|
||||
|
||||
VIDEO_EXTS = {'.avi': 'video/x-msvideo', '.mp4': 'video/mp4', '.mkv': 'video/x-matroska',
|
||||
'.m4v': 'video/mp4', '.mov': 'video/quicktime', '.mpg': 'video/mpeg', '.ogv': 'video/ogg',
|
||||
'.ogg': 'video/ogg', '.webm': 'video/webm', '.ts': 'video/mp2t', '.3gp': 'video/3gpp',
|
||||
'.rar': 'video/unrar'}
|
||||
|
||||
def __init__(self, url=None, port=None, ip=None, auto_shutdown=True, wait_time=20, timeout=5, auto_delete=True,
|
||||
temp_path=None, is_playing_fnc=None, print_status=False):
|
||||
|
||||
# server
|
||||
if port:
|
||||
self.port = port
|
||||
else:
|
||||
self.port = random.randint(8000, 8099)
|
||||
if ip:
|
||||
self.ip = ip
|
||||
else:
|
||||
self.ip = "127.0.0.1"
|
||||
self.server = Server((self.ip, self.port), Handler, client=self)
|
||||
|
||||
# Options
|
||||
if temp_path:
|
||||
self.temp_path = temp_path
|
||||
else:
|
||||
self.temp_path = DOWNLOAD_PATH
|
||||
self.is_playing_fnc = is_playing_fnc
|
||||
self.timeout = timeout
|
||||
self.auto_delete = auto_delete
|
||||
self.wait_time = wait_time
|
||||
self.auto_shutdown = auto_shutdown
|
||||
self.buffer_size = BUFFER
|
||||
self.first_pieces_priorize = BUFFER
|
||||
self.last_pieces_priorize = 5
|
||||
self.state_file = "state"
|
||||
try:
|
||||
self.torrent_paramss = {'save_path': self.temp_path, 'storage_mode': lt.storage_mode_t.storage_mode_allocate}
|
||||
except Exception as e:
|
||||
try:
|
||||
do = xbmcgui.Dialog()
|
||||
e = e1 or e2
|
||||
do.ok(config.get_localized_string(30035) + 'BT Libtorrent', config.get_localized_string(30036), config.get_localized_string(60015), str(e))
|
||||
except:
|
||||
pass
|
||||
return
|
||||
|
||||
# State
|
||||
self.has_meta = False
|
||||
self.meta = None
|
||||
self.start_time = None
|
||||
self.last_connect = 0
|
||||
self.connected = False
|
||||
self.closed = False
|
||||
self.file = None
|
||||
self.files = None
|
||||
self._th = None
|
||||
self.seleccion = 0
|
||||
self.index = 0
|
||||
|
||||
# Sesion
|
||||
self._cache = Cache(self.temp_path)
|
||||
self._ses = lt.session()
|
||||
#self._ses.listen_on(0, 0) ### ALFA: it blocks repro of some .torrents
|
||||
# Cargamos el archivo de estado (si existe)
|
||||
""" ### ALFA: it blocks repro of some .torrents
|
||||
if os.path.exists(os.path.join(self.temp_path, self.state_file)):
|
||||
try:
|
||||
f = open(os.path.join(self.temp_path, self.state_file), "rb")
|
||||
state = pickle.load(f)
|
||||
self._ses.load_state(state)
|
||||
f.close()
|
||||
except:
|
||||
pass
|
||||
"""
|
||||
|
||||
self._start_services()
|
||||
|
||||
# Monitor & Dispatcher
|
||||
self._monitor = Monitor(self)
|
||||
if print_status:
|
||||
self._monitor.add_listener(self.print_status)
|
||||
self._monitor.add_listener(self._check_meta)
|
||||
self._monitor.add_listener(self.save_state)
|
||||
self._monitor.add_listener(self.priorize_start_file)
|
||||
self._monitor.add_listener(self.announce_torrent)
|
||||
|
||||
if self.auto_shutdown:
|
||||
self._monitor.add_listener(self._auto_shutdown)
|
||||
|
||||
self._dispatcher = Dispatcher(self)
|
||||
self._dispatcher.add_listener(self._update_ready_pieces)
|
||||
|
||||
# Iniciamos la URL
|
||||
if url:
|
||||
self.start_url(url)
|
||||
|
||||
def set_speed_limits(self, download=0, upload=0):
|
||||
"""
|
||||
Función encargada de poner límites a la velocidad de descarga o subida
|
||||
"""
|
||||
if isinstance(download, int) and download > 0:
|
||||
self._th.set_download_limit(download * 1024)
|
||||
if isinstance(upload, int) and download > 0:
|
||||
self._th.set_upload_limit(upload * 1024)
|
||||
|
||||
def get_play_list(self):
|
||||
"""
|
||||
Función encargada de generar el playlist
|
||||
"""
|
||||
# Esperamos a lo metadatos
|
||||
while not self.has_meta:
|
||||
time.sleep(1)
|
||||
|
||||
# Comprobamos que haya archivos de video
|
||||
if self.files:
|
||||
if len(self.files) > 1:
|
||||
return "http://" + self.ip + ":" + str(self.port) + "/playlist.pls"
|
||||
else:
|
||||
return "http://" + self.ip + ":" + str(self.port) + "/" + urllib.quote(self.files[0].path)
|
||||
|
||||
def get_files(self):
|
||||
"""
|
||||
Función encargada de genera el listado de archivos
|
||||
"""
|
||||
# Esperamos a lo metadatos
|
||||
while not self.has_meta:
|
||||
time.sleep(1)
|
||||
files = []
|
||||
|
||||
# Comprobamos que haya archivos de video
|
||||
if self.files:
|
||||
# Creamos el dict con los archivos
|
||||
for file in self.files:
|
||||
n = file.path
|
||||
u = "http://" + self.ip + ":" + str(self.port) + "/" + urllib.quote(n)
|
||||
s = file.size
|
||||
files.append({"name": n, "url": u, "size": s})
|
||||
|
||||
return files
|
||||
|
||||
def _find_files(self, files, search=None):
|
||||
"""
|
||||
Función encargada de buscar los archivos reproducibles del torrent
|
||||
"""
|
||||
self.total_size = 0
|
||||
# Obtenemos los archivos que la extension este en la lista
|
||||
videos = filter(lambda f: self.VIDEO_EXTS.has_key(os.path.splitext(f.path)[1]), files)
|
||||
|
||||
if not videos:
|
||||
raise Exception('No video files in torrent')
|
||||
for v in videos:
|
||||
self.total_size += v.size ### ALFA
|
||||
videos[videos.index(v)].index = files.index(v)
|
||||
return videos
|
||||
|
||||
def set_file(self, f):
|
||||
"""
|
||||
Función encargada de seleccionar el archivo que vamos a servir y por tanto, priorizar su descarga
|
||||
"""
|
||||
# Seleccionamos el archivo que vamos a servir
|
||||
fmap = self.meta.map_file(f.index, 0, 1)
|
||||
self.file = File(f.path, self.temp_path, f.index, f.size, fmap, self.meta.piece_length(), self)
|
||||
if self.seleccion < 0: ### ALFA
|
||||
self.file.first_piece = 0 ### ALFA
|
||||
self.file.last_piece = self.meta.num_pieces() ### ALFA
|
||||
self.file.size = self.total_size ### ALFA
|
||||
self.prioritize_file()
|
||||
|
||||
def prioritize_piece(self, pc, idx):
|
||||
"""
|
||||
Función encargada de priorizar una determinada pieza
|
||||
"""
|
||||
piece_duration = 1000
|
||||
min_deadline = 2000
|
||||
dl = idx * piece_duration + min_deadline
|
||||
""" ### ALFA
|
||||
try:
|
||||
self._th.set_piece_deadline(pc, dl, lt.deadline_flags.alert_when_available)
|
||||
except:
|
||||
pass
|
||||
"""
|
||||
|
||||
if idx == 0:
|
||||
tail_pieces = 9
|
||||
# Piezas anteriores a la primera se desactivan
|
||||
if (self.file.last_piece - pc) > tail_pieces:
|
||||
for i in xrange(self.file.first_piece, pc):
|
||||
self._th.piece_priority(i, 0)
|
||||
self._th.reset_piece_deadline(i)
|
||||
|
||||
# Piezas siguientes a la primera se activan
|
||||
for i in xrange(pc + 1, self.file.last_piece + 1):
|
||||
#self._th.piece_priority(i, 0)
|
||||
self._th.piece_priority(i, 1)
|
||||
|
||||
def prioritize_file(self):
|
||||
"""
|
||||
Función encargada de priorizar las piezas correspondientes al archivo seleccionado en la funcion set_file()
|
||||
"""
|
||||
priorities = []
|
||||
for i in xrange(self.meta.num_pieces()):
|
||||
if i >= self.file.first_piece and i <= self.file.last_piece:
|
||||
priorities.append(1)
|
||||
else:
|
||||
if self.index < 0:
|
||||
priorities.append(1) ### ALFA
|
||||
else:
|
||||
priorities.append(0) ### ALFA
|
||||
|
||||
self._th.prioritize_pieces(priorities)
|
||||
|
||||
x = 0
|
||||
for i, _set in enumerate(self._th.piece_priorities()):
|
||||
if _set > 0: x += 1
|
||||
#logger.info("***** Nº Pieza: %s: %s" % (i, str(_set)))
|
||||
logger.info("***** Piezas %s : Activas: %s" % (str(i+1), str(x)))
|
||||
logger.info("***** first_piece %s : last_piece: %s" % (str(self.file.first_piece), str(self.file.last_piece)))
|
||||
|
||||
def download_torrent(self, url):
|
||||
"""
|
||||
Función encargada de descargar un archivo .torrent
|
||||
"""
|
||||
from core import httptools
|
||||
|
||||
data = httptools.downloadpage(url).data
|
||||
return data
|
||||
|
||||
def start_url(self, uri):
|
||||
"""
|
||||
Función encargada iniciar la descarga del torrent desde la url, permite:
|
||||
- Url apuntando a un .torrent
|
||||
- Url magnet
|
||||
- Archivo .torrent local
|
||||
"""
|
||||
|
||||
if self._th:
|
||||
raise Exception('Torrent is already started')
|
||||
|
||||
if uri.startswith('http://') or uri.startswith('https://'):
|
||||
torrent_data = self.download_torrent(uri)
|
||||
info = lt.torrent_info(lt.bdecode(torrent_data))
|
||||
tp = {'ti': info}
|
||||
resume_data = self._cache.get_resume(info_hash=str(info.info_hash()))
|
||||
if resume_data:
|
||||
tp['resume_data'] = resume_data
|
||||
|
||||
elif uri.startswith('magnet:'):
|
||||
tp = {'url': uri}
|
||||
resume_data = self._cache.get_resume(info_hash=Cache.hash_from_magnet(uri))
|
||||
if resume_data:
|
||||
tp['resume_data'] = resume_data
|
||||
|
||||
elif os.path.isfile(uri):
|
||||
if os.access(uri, os.R_OK):
|
||||
info = lt.torrent_info(uri)
|
||||
tp = {'ti': info}
|
||||
resume_data = self._cache.get_resume(info_hash=str(info.info_hash()))
|
||||
if resume_data:
|
||||
tp['resume_data'] = resume_data
|
||||
else:
|
||||
raise ValueError('Invalid torrent path %s' % uri)
|
||||
else:
|
||||
raise ValueError("Invalid torrent %s" % uri)
|
||||
|
||||
tp.update(self.torrent_paramss)
|
||||
self._th = self._ses.add_torrent(tp)
|
||||
|
||||
for tr in self.INITIAL_TRACKERS:
|
||||
self._th.add_tracker({'url': tr})
|
||||
|
||||
self._th.set_sequential_download(True)
|
||||
self._th.force_reannounce()
|
||||
self._th.force_dht_announce()
|
||||
|
||||
self._monitor.start()
|
||||
self._dispatcher.do_start(self._th, self._ses)
|
||||
self.server.run()
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Función encargada de de detener el torrent y salir
|
||||
"""
|
||||
self._dispatcher.stop()
|
||||
self._dispatcher.join()
|
||||
self._monitor.stop()
|
||||
self.server.stop()
|
||||
self._dispatcher.stop()
|
||||
if self._ses:
|
||||
self._ses.pause()
|
||||
if self._th:
|
||||
self.save_resume()
|
||||
self.save_state()
|
||||
self._stop_services()
|
||||
self._ses.remove_torrent(self._th, self.auto_delete)
|
||||
del self._ses
|
||||
self.closed = True
|
||||
|
||||
def pause(self):
|
||||
"""
|
||||
Función encargada de de pausar el torrent
|
||||
"""
|
||||
self._ses.pause()
|
||||
|
||||
def _start_services(self):
|
||||
"""
|
||||
Función encargada de iniciar los servicios de libtorrent: dht, lsd, upnp, natpnp
|
||||
"""
|
||||
self._ses.add_dht_router("router.bittorrent.com", 6881)
|
||||
self._ses.add_dht_router("router.bitcomet.com", 554)
|
||||
self._ses.add_dht_router("router.utorrent.com", 6881)
|
||||
self._ses.add_dht_router("dht.transmissionbt.com",6881) ### from MCT
|
||||
self._ses.start_dht()
|
||||
self._ses.start_lsd()
|
||||
self._ses.start_upnp()
|
||||
self._ses.start_natpmp()
|
||||
|
||||
def _stop_services(self):
|
||||
"""
|
||||
Función encargada de detener los servicios de libtorrent: dht, lsd, upnp, natpnp
|
||||
"""
|
||||
self._ses.stop_natpmp()
|
||||
self._ses.stop_upnp()
|
||||
self._ses.stop_lsd()
|
||||
self._ses.stop_dht()
|
||||
|
||||
def save_resume(self):
|
||||
"""
|
||||
Función encargada guardar los metadatos para continuar una descarga mas rapidamente
|
||||
"""
|
||||
if self._th.need_save_resume_data() and self._th.is_valid() and self.meta:
|
||||
r = ResumeData(self)
|
||||
start = time.time()
|
||||
while (time.time() - start) <= 5:
|
||||
if r.data or r.failed:
|
||||
break
|
||||
time.sleep(0.1)
|
||||
if r.data:
|
||||
self._cache.save_resume(self.unique_file_id, lt.bencode(r.data))
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
"""
|
||||
Función encargada de devolver el estado del torrent
|
||||
"""
|
||||
if self._th:
|
||||
s = self._th.status()
|
||||
# Download Rate
|
||||
s._download_rate = s.download_rate / 1024
|
||||
|
||||
# Progreso del archivo
|
||||
if self.file:
|
||||
pieces = s.pieces[self.file.first_piece:self.file.last_piece] ### ALFA
|
||||
progress = float(sum(pieces)) / len(pieces)
|
||||
s.pieces_len = len(pieces) ### ALFA
|
||||
s.pieces_sum = sum(pieces) ### ALFA
|
||||
#logger.info('***** Estado piezas: %s' % pieces)
|
||||
else:
|
||||
progress = 0
|
||||
s.pieces_len = 0 ### ALFA
|
||||
s.pieces_sum = 0 ### ALFA
|
||||
|
||||
s.progress_file = progress * 100
|
||||
|
||||
# Tamaño del archivo
|
||||
s.file_name = '' ### ALFA
|
||||
s.seleccion = '' ### ALFA
|
||||
|
||||
if self.file:
|
||||
s.seleccion = self.seleccion ### ALFA
|
||||
s.file_name = self.file.path ### ALFA
|
||||
s.file_size = self.file.size / 1048576.0
|
||||
else:
|
||||
s.file_size = 0
|
||||
|
||||
# Estado del buffer
|
||||
if self.file and self.file.cursor: # Con una conexion activa: Disponible vs Posicion del reproductor
|
||||
percent = len(self.file.cursor.cache)
|
||||
percent = percent * 100 / self.buffer_size
|
||||
s.buffer = int(percent)
|
||||
|
||||
elif self.file: # Sin una conexion activa: Pre-buffer antes de iniciar
|
||||
# El Pre-buffer consta de dos partes_
|
||||
# 1. Buffer al inicio del archivo para que el reproductor empieze sin cortes
|
||||
# 2. Buffer al final del archivo (en algunos archivos el reproductor mira el final del archivo antes de comenzar)
|
||||
bp = []
|
||||
|
||||
# El tamaño del buffer de inicio es el tamaño del buffer menos el tamaño del buffer del final
|
||||
first_pieces_priorize = self.buffer_size - self.last_pieces_priorize
|
||||
|
||||
# Comprobamos qué partes del buffer del inicio estan disponibles
|
||||
for x in range(first_pieces_priorize):
|
||||
if self._th.have_piece(self.file.first_piece + x):
|
||||
bp.append(True)
|
||||
else:
|
||||
bp.append(False)
|
||||
|
||||
# Comprobamos qué partes del buffer del final estan disponibles
|
||||
for x in range(self.last_pieces_priorize):
|
||||
if self._th.have_piece(self.file.last_piece - x):
|
||||
bp.append(True)
|
||||
else:
|
||||
bp.append(False)
|
||||
|
||||
s.buffer = int(sum(bp) * 100 / self.buffer_size)
|
||||
|
||||
else: # Si no hay ningun archivo seleccionado: No hay buffer
|
||||
s.buffer = 0
|
||||
|
||||
# Tiempo restante para cerrar en caso de tener el timeout activo
|
||||
if self.auto_shutdown:
|
||||
if self.connected:
|
||||
if self.timeout:
|
||||
s.timeout = int(self.timeout - (time.time() - self.last_connect - 1))
|
||||
if self.file and self.file.cursor:
|
||||
s.timeout = self.timeout
|
||||
if s.timeout < 0: s.timeout = "Cerrando"
|
||||
else:
|
||||
s.timeout = "---"
|
||||
else:
|
||||
if self.start_time and self.wait_time:
|
||||
s.timeout = int(self.wait_time - (time.time() - self.start_time - 1))
|
||||
if s.timeout < 0: s.timeout = "Cerrando"
|
||||
else:
|
||||
s.timeout = "---"
|
||||
|
||||
else:
|
||||
s.timeout = "Off"
|
||||
|
||||
# Estado de la descarga
|
||||
STATE_STR = ['Queued', 'Checking', 'Downloading Metadata', \
|
||||
'Downloading', 'Finalized', 'Seeding', 'Allocating', 'Checking Fastresume']
|
||||
s.str_state = STATE_STR[s.state]
|
||||
|
||||
# Estado DHT
|
||||
if self._ses.dht_state() is not None:
|
||||
s.dht_state = "On"
|
||||
s.dht_nodes = self._ses.status().dht_nodes
|
||||
else:
|
||||
s.dht_state = "Off"
|
||||
s.dht_nodes = 0
|
||||
|
||||
# Cantidad de Trackers
|
||||
s.trackers = len(self._th.trackers())
|
||||
|
||||
# Origen de los peers
|
||||
s.dht_peers = 0
|
||||
s.trk_peers = 0
|
||||
s.pex_peers = 0
|
||||
s.lsd_peers = 0
|
||||
|
||||
for peer in self._th.get_peer_info():
|
||||
if peer.source & 1:
|
||||
s.trk_peers += 1
|
||||
if peer.source & 2:
|
||||
s.dht_peers += 1
|
||||
if peer.source & 4:
|
||||
s.pex_peers += 1
|
||||
if peer.source & 8:
|
||||
s.lsd_peers += 1
|
||||
|
||||
return s
|
||||
|
||||
"""
|
||||
Servicios:
|
||||
- Estas funciones se ejecutan de forma automatica cada x tiempo en otro Thread.
|
||||
- Estas funciones son ejecutadas mientras el torrent esta activo algunas pueden desactivarse
|
||||
segun la configuracion como por ejemplo la escritura en el log
|
||||
"""
|
||||
|
||||
def _auto_shutdown(self, *args, **kwargs):
|
||||
"""
|
||||
Servicio encargado de autoapagar el servidor
|
||||
"""
|
||||
if self.file and self.file.cursor:
|
||||
self.last_connect = time.time()
|
||||
self.connected = True
|
||||
|
||||
if self.is_playing_fnc and self.is_playing_fnc():
|
||||
self.last_connect = time.time()
|
||||
self.connected = True
|
||||
|
||||
if self.auto_shutdown:
|
||||
# shudown por haber cerrado el reproductor
|
||||
if self.connected and self.is_playing_fnc and not self.is_playing_fnc():
|
||||
if time.time() - self.last_connect - 1 > self.timeout:
|
||||
self.stop()
|
||||
|
||||
# shutdown por no realizar ninguna conexion
|
||||
if (not self.file or not self.file.cursor) and self.start_time and self.wait_time and not self.connected:
|
||||
if time.time() - self.start_time - 1 > self.wait_time:
|
||||
self.stop()
|
||||
|
||||
# shutdown tras la ultima conexion
|
||||
if (not self.file or not self.file.cursor) and self.timeout and self.connected and not self.is_playing_fnc:
|
||||
if time.time() - self.last_connect - 1 > self.timeout:
|
||||
self.stop()
|
||||
|
||||
def announce_torrent(self):
|
||||
"""
|
||||
Servicio encargado de anunciar el torrent
|
||||
"""
|
||||
self._th.force_reannounce()
|
||||
self._th.force_dht_announce()
|
||||
|
||||
def save_state(self):
|
||||
"""
|
||||
Servicio encargado de guardar el estado
|
||||
"""
|
||||
state = self._ses.save_state()
|
||||
f = open(os.path.join(self.temp_path, self.state_file), 'wb')
|
||||
pickle.dump(state, f)
|
||||
f.close()
|
||||
|
||||
def _update_ready_pieces(self, alert_type, alert):
|
||||
"""
|
||||
Servicio encargado de informar que hay una pieza disponible
|
||||
"""
|
||||
if alert_type == 'read_piece_alert' and self.file:
|
||||
self.file.update_piece(alert.piece, alert.buffer)
|
||||
|
||||
def _check_meta(self):
|
||||
"""
|
||||
Servicio encargado de comprobar si los metadatos se han descargado
|
||||
"""
|
||||
if self.status.state >= 3 and self.status.state <= 5 and not self.has_meta:
|
||||
|
||||
# Guardamos los metadatos
|
||||
self.meta = self._th.get_torrent_info()
|
||||
|
||||
# Obtenemos la lista de archivos del meta
|
||||
fs = self.meta.files()
|
||||
if isinstance(fs, list):
|
||||
files = fs
|
||||
else:
|
||||
files = [fs.at(i) for i in xrange(fs.num_files())]
|
||||
|
||||
# Guardamos la lista de archivos
|
||||
self.files = self._find_files(files)
|
||||
|
||||
# Si hay varios vídeos (no RAR), se selecciona el vídeo o "todos"
|
||||
lista = []
|
||||
seleccion = 0
|
||||
for file in self.files:
|
||||
if '.rar' in str(file.path):
|
||||
seleccion = -9
|
||||
lista += [os.path.split(str(file.path))[1]]
|
||||
if len(lista) > 1 and seleccion >= 0:
|
||||
d = xbmcgui.Dialog()
|
||||
seleccion = d.select(msg_header + config.get_localized_string(30034), lista)
|
||||
|
||||
if seleccion < 0:
|
||||
index = 0
|
||||
self.index = seleccion
|
||||
else:
|
||||
index = seleccion
|
||||
self.index = self.files[index].index
|
||||
self.seleccion = seleccion
|
||||
|
||||
# Marcamos el primer archivo como activo
|
||||
self.set_file(self.files[index])
|
||||
|
||||
# Damos por iniciada la descarga
|
||||
self.start_time = time.time()
|
||||
|
||||
# Guardamos el .torrent en el cache
|
||||
self._cache.file_complete(self._th.get_torrent_info())
|
||||
|
||||
self.has_meta = True
|
||||
|
||||
def priorize_start_file(self):
|
||||
'''
|
||||
Servicio encargado de priorizar el principio y final de archivo cuando no hay conexion
|
||||
'''
|
||||
if self.file and not self.file.cursor:
|
||||
num_start_pieces = self.buffer_size - self.last_pieces_priorize # Cantidad de piezas a priorizar al inicio
|
||||
num_end_pieces = self.last_pieces_priorize # Cantidad de piezas a priorizar al final
|
||||
|
||||
pieces_count = 0
|
||||
# Priorizamos las ultimas piezas
|
||||
for y in range(self.file.last_piece - num_end_pieces, self.file.last_piece + 1):
|
||||
if not self._th.have_piece(y):
|
||||
self.prioritize_piece(y, pieces_count)
|
||||
pieces_count += 1
|
||||
|
||||
# Priorizamos las primeras piezas
|
||||
for y in range(self.file.first_piece, self.file.last_piece + 1):
|
||||
if not self._th.have_piece(y):
|
||||
if pieces_count == self.buffer_size:
|
||||
break
|
||||
self.prioritize_piece(y, pieces_count)
|
||||
pieces_count += 1
|
||||
|
||||
def print_status(self):
|
||||
'''
|
||||
Servicio encargado de mostrar en el log el estado de la descarga
|
||||
'''
|
||||
s = self.status ### ALFA
|
||||
if self.seleccion >= 0:
|
||||
archivo = self.seleccion + 1
|
||||
else:
|
||||
archivo = self.seleccion
|
||||
|
||||
logger.info(
|
||||
'%.2f%% de %.1fMB %s | %.1f kB/s | #%s %d%% | AutoClose: %s | S: %d(%d) P: %d(%d)) | TRK: %d DHT: %d PEX: %d LSD %d | DHT:%s (%d) | Trakers: %d | Pieces: %d (%d)' % \
|
||||
(s.progress_file, s.file_size, s.str_state, s._download_rate, archivo, s.buffer, s.timeout, s.num_seeds, \
|
||||
s.num_complete, s.num_peers, s.num_incomplete, s.trk_peers, s.dht_peers, s.pex_peers, s.lsd_peers,
|
||||
s.dht_state, s.dht_nodes, s.trackers, s.pieces_sum, s.pieces_len)) ### ALFA
|
||||
@@ -1,106 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from threading import Lock, Event
|
||||
|
||||
|
||||
class Cursor(object):
|
||||
def __init__(self, file):
|
||||
self._file = file
|
||||
self.pos = 0
|
||||
self.timeout = 30
|
||||
self.cache_size = self._file._client.buffer_size
|
||||
self.cache = {}
|
||||
self.lock = Lock()
|
||||
self.event = Event()
|
||||
self.cache_first = 0
|
||||
|
||||
def fill_cache(self, first):
|
||||
self.cache_first = first
|
||||
|
||||
with self.lock:
|
||||
for p in sorted(self.cache):
|
||||
if p < first: del self.cache[p]
|
||||
|
||||
self.event.clear()
|
||||
for i in xrange(first, first + self.cache_size):
|
||||
if i <= self._file.last_piece:
|
||||
self._file._client.prioritize_piece(i, i - first)
|
||||
|
||||
def has_piece(self, n):
|
||||
with self.lock:
|
||||
return n in self.cache
|
||||
|
||||
def _wait_piece(self, pc_no):
|
||||
while not self.has_piece(pc_no):
|
||||
self.fill_cache(pc_no)
|
||||
self.event.wait(self.timeout)
|
||||
|
||||
def _get_piece(self, n):
|
||||
with self.lock:
|
||||
if not n in self.cache:
|
||||
raise ValueError('index of of scope of current cache')
|
||||
return self.cache[n]
|
||||
|
||||
def get_piece(self, n):
|
||||
self._wait_piece(n)
|
||||
return self._get_piece(n)
|
||||
|
||||
def close(self):
|
||||
self._file.cursor = None
|
||||
|
||||
def read(self, size=None):
|
||||
data = ""
|
||||
max_size = self._file.size - self.pos
|
||||
if not size:
|
||||
size = max_size
|
||||
else:
|
||||
size = min(size, max_size)
|
||||
|
||||
if size:
|
||||
pc_no, ofs = self._file.map_piece(self.pos)
|
||||
data = self.get_piece(pc_no)[ofs: ofs + size]
|
||||
|
||||
if len(data) < size:
|
||||
remains = size - len(data)
|
||||
pc_no += 1
|
||||
self.fill_cache(pc_no)
|
||||
while remains and self.has_piece(pc_no):
|
||||
sz = min(remains, self._file.piece_size)
|
||||
data += self.get_piece(pc_no)[:sz]
|
||||
remains -= sz
|
||||
if remains:
|
||||
pc_no += 1
|
||||
self.fill_cache(pc_no)
|
||||
|
||||
self.pos += len(data)
|
||||
|
||||
return data
|
||||
|
||||
def seek(self, n):
|
||||
if n > self._file.size:
|
||||
n = self._file.size
|
||||
elif n < 0:
|
||||
raise ValueError('Seeking negative')
|
||||
self.pos = n
|
||||
|
||||
def tell(self):
|
||||
return self.pos
|
||||
|
||||
def update_piece(self, n, data):
|
||||
with self.lock:
|
||||
pcs = sorted(self.cache)
|
||||
if len(pcs) < self.cache_size:
|
||||
if len(pcs):
|
||||
new = max(pcs) + 1
|
||||
else:
|
||||
new = self.cache_first
|
||||
if n == new:
|
||||
self.cache[n] = data
|
||||
if n == self.cache_first:
|
||||
self.event.set()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.close()
|
||||
@@ -1,39 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from monitor import Monitor
|
||||
|
||||
import traceback
|
||||
|
||||
try:
|
||||
import xbmc, xbmcgui
|
||||
except:
|
||||
pass
|
||||
|
||||
from platformcode import config
|
||||
LIBTORRENT_PATH = config.get_setting("libtorrent_path", server="torrent", default='')
|
||||
|
||||
from servers import torrent as torr
|
||||
lt, e, e1, e2 = torr.import_libtorrent(LIBTORRENT_PATH)
|
||||
|
||||
|
||||
class Dispatcher(Monitor):
|
||||
def __init__(self, client):
|
||||
super(Dispatcher, self).__init__(client)
|
||||
|
||||
def do_start(self, th, ses):
|
||||
self._th = th
|
||||
self._ses = ses
|
||||
self.start()
|
||||
|
||||
def run(self):
|
||||
if not self._ses:
|
||||
raise Exception('Invalid state, session is not initialized')
|
||||
|
||||
while self.running:
|
||||
a = self._ses.wait_for_alert(1000)
|
||||
if a:
|
||||
alerts = self._ses.pop_alerts()
|
||||
for alert in alerts:
|
||||
with self.lock:
|
||||
for cb in self.listeners:
|
||||
cb(lt.alert.what(alert), alert)
|
||||
@@ -1,39 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
|
||||
from cursor import Cursor
|
||||
|
||||
|
||||
class File(object):
|
||||
def __init__(self, path, base, index, size, fmap, piece_size, client):
|
||||
self._client = client
|
||||
self.path = path
|
||||
self.base = base
|
||||
self.index = index
|
||||
self.size = size
|
||||
|
||||
self.piece_size = piece_size
|
||||
|
||||
self.full_path = os.path.join(base, path)
|
||||
self.first_piece = fmap.piece
|
||||
self.offset = fmap.start
|
||||
self.last_piece = self.first_piece + max((size - 1 + fmap.start), 0) // piece_size
|
||||
|
||||
self.cursor = None
|
||||
|
||||
def create_cursor(self, offset=None):
|
||||
self.cursor = Cursor(self)
|
||||
if offset:
|
||||
self.cursor.seek(offset)
|
||||
return self.cursor
|
||||
|
||||
def map_piece(self, ofs):
|
||||
return self.first_piece + (ofs + self.offset) // self.piece_size, (ofs + self.offset) % self.piece_size
|
||||
|
||||
def update_piece(self, n, data):
|
||||
if self.cursor:
|
||||
self.cursor.update_piece(n, data)
|
||||
|
||||
def __str__(self):
|
||||
return self.path
|
||||
@@ -1,135 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import BaseHTTPServer
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import types
|
||||
import urllib
|
||||
import urlparse
|
||||
|
||||
RANGE_RE = re.compile(r'bytes=(\d+)-')
|
||||
|
||||
|
||||
def parse_range(range): # @ReservedAssignment
|
||||
if range:
|
||||
m = RANGE_RE.match(range)
|
||||
if m:
|
||||
try:
|
||||
return int(m.group(1))
|
||||
except:
|
||||
pass
|
||||
return 0
|
||||
|
||||
|
||||
class Handler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
protocol_version = 'HTTP/1.1'
|
||||
|
||||
def log_message(self, format, *args):
|
||||
pass
|
||||
|
||||
def do_GET(self):
|
||||
if self.server.request:
|
||||
self.server.request.wfile.close()
|
||||
self.server.request = self
|
||||
|
||||
if self.do_HEAD():
|
||||
f = self.server.file.create_cursor(self.offset)
|
||||
while f == self.server.file.cursor:
|
||||
buf = f.read(1024)
|
||||
if buf:
|
||||
try:
|
||||
self.wfile.write(buf)
|
||||
except:
|
||||
break
|
||||
else:
|
||||
break
|
||||
f.close()
|
||||
|
||||
def send_pls(self, files):
|
||||
playlist = "[playlist]\n\n"
|
||||
for x, f in enumerate(files):
|
||||
playlist += "File" + str(x + 1) + "=http://127.0.0.1:" + str(self.server._client.port) + "/" + urllib.quote(
|
||||
f.path) + "\n"
|
||||
playlist += "Title" + str(x + 1) + "=" + f.path + "\n"
|
||||
playlist += "NumberOfEntries=" + str(len(files))
|
||||
playlist += "Version=2"
|
||||
self.send_response(200, 'OK')
|
||||
self.send_header("Content-Length", str(len(playlist)))
|
||||
self.finish_header()
|
||||
self.wfile.write(playlist)
|
||||
|
||||
def do_HEAD(self):
|
||||
url = urlparse.urlparse(self.path).path
|
||||
|
||||
'''Whait to list of files '''
|
||||
while not self.server._client.files:
|
||||
time.sleep(1)
|
||||
|
||||
files = self.server._client.files
|
||||
self.server.file = self.server._client.file
|
||||
|
||||
'''Creates PLS playlist '''
|
||||
if url == "/playlist.pls":
|
||||
self.send_pls(files)
|
||||
return False
|
||||
|
||||
'''Change File to download '''
|
||||
if not self.server.file or urllib.unquote(url) != '/' + self.server.file.path:
|
||||
file = urllib.unquote(url)
|
||||
client = self.server._client
|
||||
for f in client.files:
|
||||
if file == '/' + f.path:
|
||||
client.set_file(f)
|
||||
self.server.file = client.file
|
||||
break
|
||||
|
||||
while not self.server._client.has_meta:
|
||||
time.sleep(1)
|
||||
if self.server.file and urllib.unquote(url) == '/' + self.server.file.path:
|
||||
self.offset = 0
|
||||
size, mime = self._file_info()
|
||||
range = parse_range(self.headers.get('Range', None))
|
||||
if range:
|
||||
self.offset = range
|
||||
range = (range, size - 1, size)
|
||||
|
||||
self.send_resp_header(mime, size, range)
|
||||
return True
|
||||
|
||||
else:
|
||||
self.send_error(404, 'Not Found')
|
||||
|
||||
def _file_info(self):
|
||||
size = self.server.file.size
|
||||
ext = os.path.splitext(self.server.file.path)[1]
|
||||
mime = self.server._client.VIDEO_EXTS.get(ext)
|
||||
if not mime:
|
||||
mime = 'application/octet-stream'
|
||||
return size, mime
|
||||
|
||||
def send_resp_header(self, cont_type, cont_length, range=False): # @ReservedAssignment
|
||||
if range:
|
||||
self.send_response(206, 'Partial Content')
|
||||
else:
|
||||
self.send_response(200, 'OK')
|
||||
|
||||
self.send_header('Content-Type', cont_type)
|
||||
self.send_header('transferMode.dlna.org', 'Streaming')
|
||||
self.send_header('contentFeatures.dlna.org',
|
||||
'DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01700000000000000000000000000000')
|
||||
self.send_header('Accept-Ranges', 'bytes')
|
||||
|
||||
if range:
|
||||
if isinstance(range, (types.TupleType, types.ListType)) and len(range) == 3:
|
||||
self.send_header('Content-Range', 'bytes %d-%d/%d' % range)
|
||||
self.send_header('Content-Length', range[1] - range[0] + 1)
|
||||
else:
|
||||
raise ValueError('Invalid range value')
|
||||
else:
|
||||
self.send_header('Content-Length', cont_length)
|
||||
self.finish_header()
|
||||
|
||||
def finish_header(self):
|
||||
self.send_header('Connection', 'close')
|
||||
self.end_headers()
|
||||
@@ -1,44 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from threading import Thread, Lock, Event
|
||||
|
||||
|
||||
class Monitor(Thread):
|
||||
def __init__(self, client):
|
||||
Thread.__init__(self)
|
||||
self.daemon = True
|
||||
self.listeners = []
|
||||
self.lock = Lock()
|
||||
self.wait_event = Event()
|
||||
self.running = True
|
||||
self.client = client
|
||||
self.ses = None
|
||||
self.client = client
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
self.wait_event.set()
|
||||
|
||||
def add_listener(self, cb):
|
||||
with self.lock:
|
||||
if not cb in self.listeners:
|
||||
self.listeners.append(cb)
|
||||
|
||||
def remove_listener(self, cb):
|
||||
with self.lock:
|
||||
try:
|
||||
self.listeners.remove(cb)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def remove_all_listeners(self):
|
||||
with self.lock:
|
||||
self.listeners = []
|
||||
|
||||
def run(self):
|
||||
while (self.running):
|
||||
with self.lock:
|
||||
for cb in self.listeners:
|
||||
cb()
|
||||
|
||||
self.wait_event.wait(1.0)
|
||||
@@ -1,15 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
class ResumeData(object):
|
||||
def __init__(self, client):
|
||||
self.data = None
|
||||
self.failed = False
|
||||
client._dispatcher.add_listener(self._process_alert)
|
||||
client._th.save_resume_data()
|
||||
|
||||
def _process_alert(self, t, alert):
|
||||
if t == 'save_resume_data_failed_alert':
|
||||
self.failed = True
|
||||
|
||||
elif t == 'save_resume_data_alert':
|
||||
self.data = alert.resume_data
|
||||
@@ -1,37 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import BaseHTTPServer
|
||||
import traceback
|
||||
from SocketServer import ThreadingMixIn
|
||||
from threading import Thread
|
||||
|
||||
|
||||
class Server(ThreadingMixIn, BaseHTTPServer.HTTPServer):
|
||||
daemon_threads = True
|
||||
timeout = 1
|
||||
|
||||
def __init__(self, address, handler, client):
|
||||
BaseHTTPServer.HTTPServer.__init__(self, address, handler)
|
||||
self._client = client
|
||||
self.file = None
|
||||
self.running = True
|
||||
self.request = None
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
|
||||
def serve(self):
|
||||
while self.running:
|
||||
try:
|
||||
self.handle_request()
|
||||
except:
|
||||
print traceback.format_exc()
|
||||
|
||||
def run(self):
|
||||
t = Thread(target=self.serve, name='HTTP Server')
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
||||
def handle_error(self, request, client_address):
|
||||
if not "socket.py" in traceback.format_exc():
|
||||
print traceback.format_exc()
|
||||
@@ -55,7 +55,7 @@ except ImportError:
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
__version__ = '1.2.35'
|
||||
__version__ = '1.2.36'
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
@@ -78,6 +78,7 @@ class CipherSuiteAdapter(HTTPAdapter):
|
||||
if not self.ssl_context:
|
||||
self.ssl_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
|
||||
self.ssl_context.set_ciphers(self.cipherSuite)
|
||||
self.ssl_context.set_ecdh_curve('prime256v1')
|
||||
self.ssl_context.options |= (ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1)
|
||||
|
||||
super(CipherSuiteAdapter, self).__init__(**kwargs)
|
||||
@@ -256,7 +257,7 @@ class CloudScraper(Session):
|
||||
resp.headers.get('Server', '').startswith('cloudflare')
|
||||
and resp.status_code in [429, 503]
|
||||
and re.search(
|
||||
r'<form id="challenge-form" action="/.*?__cf_chl_jschl_tk__=\S+"',
|
||||
r'<form .*?="challenge-form" action="/.*?__cf_chl_jschl_tk__=\S+"',
|
||||
resp.text,
|
||||
re.M | re.DOTALL
|
||||
)
|
||||
@@ -347,6 +348,8 @@ class CloudScraper(Session):
|
||||
)
|
||||
|
||||
if self.is_reCaptcha_Challenge(resp) or self.is_IUAM_Challenge(resp):
|
||||
if self.debug:
|
||||
print('Detected Challenge.')
|
||||
return True
|
||||
|
||||
return False
|
||||
@@ -358,7 +361,8 @@ class CloudScraper(Session):
|
||||
def IUAM_Challenge_Response(self, body, url, interpreter):
|
||||
try:
|
||||
formPayload = re.search(
|
||||
r'<form (?P<form>id="challenge-form" action="(?P<challengeUUID>.*?'
|
||||
r'<form (?P<form>.*?="challenge-form" '
|
||||
r'action="(?P<challengeUUID>.*?'
|
||||
r'__cf_chl_jschl_tk__=\S+)"(.*?)</form>)',
|
||||
body,
|
||||
re.M | re.DOTALL
|
||||
@@ -412,7 +416,7 @@ class CloudScraper(Session):
|
||||
def reCaptcha_Challenge_Response(self, provider, provider_params, body, url):
|
||||
try:
|
||||
formPayload = re.search(
|
||||
r'<form class="challenge-form" (?P<form>id="challenge-form" '
|
||||
r'<form (?P<form>.*?="challenge-form" '
|
||||
r'action="(?P<challengeUUID>.*?__cf_chl_captcha_tk__=\S+)"(.*?)</form>)',
|
||||
body,
|
||||
re.M | re.DOTALL
|
||||
|
||||
@@ -9,15 +9,13 @@ def template(body, domain):
|
||||
|
||||
try:
|
||||
js = re.search(
|
||||
r'setTimeout\(function\(\){\s+(var s,t,o,p,b,r,e,a,k,i,n,g,f.+?\r?\n[\s\S]+?a\.value =.+?)\r?\n',
|
||||
body
|
||||
r'setTimeout\(function\(\){\s+(.*?a\.value = \S+)',
|
||||
body,
|
||||
re.M | re.S
|
||||
).group(1)
|
||||
except Exception:
|
||||
raise ValueError('Unable to identify Cloudflare IUAM Javascript on website. {}'.format(BUG_REPORT))
|
||||
|
||||
js = re.sub(r'\s{2,}', ' ', js, flags=re.MULTILINE | re.DOTALL).replace('\'; 121\'', '')
|
||||
js += '\na.value;'
|
||||
|
||||
jsEnv = '''
|
||||
String.prototype.italics=function(str) {{return "<i>" + this + "</i>";}};
|
||||
var document = {{
|
||||
|
||||
@@ -1,21 +1,95 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import ast
|
||||
import re
|
||||
import operator as op
|
||||
import pyparsing
|
||||
|
||||
from ..exceptions import CloudflareSolveError
|
||||
from . import JavaScriptInterpreter
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
_OP_MAP = {
|
||||
ast.Add: op.add,
|
||||
ast.Sub: op.sub,
|
||||
ast.Mult: op.mul,
|
||||
ast.Div: op.truediv,
|
||||
ast.Invert: op.neg,
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
class Calc(ast.NodeVisitor):
|
||||
|
||||
def visit_BinOp(self, node):
|
||||
return _OP_MAP[type(node.op)](self.visit(node.left), self.visit(node.right))
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def visit_Num(self, node):
|
||||
return node.n
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def visit_Expr(self, node):
|
||||
return self.visit(node.value)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
@classmethod
|
||||
def doMath(cls, expression):
|
||||
tree = ast.parse(expression)
|
||||
calc = cls()
|
||||
return calc.visit(tree.body[0])
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
class Parentheses(object):
|
||||
|
||||
def fix(self, s):
|
||||
res = []
|
||||
self.visited = set([s])
|
||||
self.dfs(s, self.invalid(s), res)
|
||||
return res
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def dfs(self, s, n, res):
|
||||
if n == 0:
|
||||
res.append(s)
|
||||
return
|
||||
for i in range(len(s)):
|
||||
if s[i] in ['(', ')']:
|
||||
s_new = s[:i] + s[i + 1:]
|
||||
if s_new not in self.visited and self.invalid(s_new) < n:
|
||||
self.visited.add(s_new)
|
||||
self.dfs(s_new, self.invalid(s_new), res)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def invalid(self, s):
|
||||
plus = minus = 0
|
||||
memo = {"(": 1, ")": -1}
|
||||
for c in s:
|
||||
plus += memo.get(c, 0)
|
||||
minus += 1 if plus < 0 else 0
|
||||
plus = max(0, plus)
|
||||
return plus + minus
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
class ChallengeInterpreter(JavaScriptInterpreter):
|
||||
|
||||
def __init__(self):
|
||||
super(ChallengeInterpreter, self).__init__('native')
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def eval(self, body, domain):
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
operators = {
|
||||
'+': op.add,
|
||||
@@ -26,18 +100,29 @@ class ChallengeInterpreter(JavaScriptInterpreter):
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def flatten(l):
|
||||
return sum(map(flatten, l), []) if isinstance(l, list) else [l]
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def jsfuckToNumber(jsFuck):
|
||||
t = ''
|
||||
# "Clean Up" JSFuck
|
||||
jsFuck = jsFuck.replace('!+[]', '1').replace('!![]', '1').replace('[]', '0')
|
||||
jsFuck = jsFuck.lstrip('+').replace('(+', '(').replace(' ', '')
|
||||
jsFuck = Parentheses().fix(jsFuck)[0]
|
||||
|
||||
split_numbers = re.compile(r'-?\d+').findall
|
||||
# Hackery Parser for Math
|
||||
stack = []
|
||||
bstack = []
|
||||
for i in flatten(pyparsing.nestedExpr().parseString(jsFuck).asList()):
|
||||
if i == '+':
|
||||
stack.append(bstack)
|
||||
bstack = []
|
||||
continue
|
||||
bstack.append(i)
|
||||
stack.append(bstack)
|
||||
|
||||
for i in re.findall(
|
||||
r'\((?:\d|\+|\-)*\)',
|
||||
jsFuck.replace('!+[]', '1').replace('!![]', '1').replace('[]', '0').lstrip('+').replace('(+', '(')
|
||||
):
|
||||
t = '{}{}'.format(t, sum(int(x) for x in split_numbers(i)))
|
||||
|
||||
return int(t)
|
||||
return int(''.join([str(Calc.doMath(''.join(i))) for i in stack]))
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
@@ -45,6 +130,7 @@ class ChallengeInterpreter(JavaScriptInterpreter):
|
||||
jsfuckMath = payload.split('/')
|
||||
if needle in jsfuckMath[1]:
|
||||
expression = re.findall(r"^(.*?)(.)\(function", jsfuckMath[1])[0]
|
||||
|
||||
expression_value = operators[expression[1]](
|
||||
float(jsfuckToNumber(expression[0])),
|
||||
float(ord(domain[jsfuckToNumber(jsfuckMath[1][
|
||||
@@ -118,7 +204,7 @@ class ChallengeInterpreter(JavaScriptInterpreter):
|
||||
|
||||
return challengeSolve(body, domain)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
ChallengeInterpreter()
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
"""
|
||||
Copyright (c) 2003-2010 Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||
|
||||
This module offers extensions to the standard python 2.3+
|
||||
datetime module.
|
||||
"""
|
||||
__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
|
||||
__license__ = "PSF License"
|
||||
__version__ = "1.5.0.1"
|
||||
@@ -0,0 +1,92 @@
|
||||
"""
|
||||
Copyright (c) 2003-2007 Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||
|
||||
This module offers extensions to the standard python 2.3+
|
||||
datetime module.
|
||||
"""
|
||||
__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
|
||||
__license__ = "PSF License"
|
||||
|
||||
import datetime
|
||||
|
||||
__all__ = ["easter", "EASTER_JULIAN", "EASTER_ORTHODOX", "EASTER_WESTERN"]
|
||||
|
||||
EASTER_JULIAN = 1
|
||||
EASTER_ORTHODOX = 2
|
||||
EASTER_WESTERN = 3
|
||||
|
||||
def easter(year, method=EASTER_WESTERN):
|
||||
"""
|
||||
This method was ported from the work done by GM Arts,
|
||||
on top of the algorithm by Claus Tondering, which was
|
||||
based in part on the algorithm of Ouding (1940), as
|
||||
quoted in "Explanatory Supplement to the Astronomical
|
||||
Almanac", P. Kenneth Seidelmann, editor.
|
||||
|
||||
This algorithm implements three different easter
|
||||
calculation methods:
|
||||
|
||||
1 - Original calculation in Julian calendar, valid in
|
||||
dates after 326 AD
|
||||
2 - Original method, with date converted to Gregorian
|
||||
calendar, valid in years 1583 to 4099
|
||||
3 - Revised method, in Gregorian calendar, valid in
|
||||
years 1583 to 4099 as well
|
||||
|
||||
These methods are represented by the constants:
|
||||
|
||||
EASTER_JULIAN = 1
|
||||
EASTER_ORTHODOX = 2
|
||||
EASTER_WESTERN = 3
|
||||
|
||||
The default method is method 3.
|
||||
|
||||
More about the algorithm may be found at:
|
||||
|
||||
http://users.chariot.net.au/~gmarts/eastalg.htm
|
||||
|
||||
and
|
||||
|
||||
http://www.tondering.dk/claus/calendar.html
|
||||
|
||||
"""
|
||||
|
||||
if not (1 <= method <= 3):
|
||||
raise ValueError, "invalid method"
|
||||
|
||||
# g - Golden year - 1
|
||||
# c - Century
|
||||
# h - (23 - Epact) mod 30
|
||||
# i - Number of days from March 21 to Paschal Full Moon
|
||||
# j - Weekday for PFM (0=Sunday, etc)
|
||||
# p - Number of days from March 21 to Sunday on or before PFM
|
||||
# (-6 to 28 methods 1 & 3, to 56 for method 2)
|
||||
# e - Extra days to add for method 2 (converting Julian
|
||||
# date to Gregorian date)
|
||||
|
||||
y = year
|
||||
g = y % 19
|
||||
e = 0
|
||||
if method < 3:
|
||||
# Old method
|
||||
i = (19*g+15)%30
|
||||
j = (y+y//4+i)%7
|
||||
if method == 2:
|
||||
# Extra dates to convert Julian to Gregorian date
|
||||
e = 10
|
||||
if y > 1600:
|
||||
e = e+y//100-16-(y//100-16)//4
|
||||
else:
|
||||
# New method
|
||||
c = y//100
|
||||
h = (c-c//4-(8*c+13)//25+19*g+15)%30
|
||||
i = h-(h//28)*(1-(h//28)*(29//(h+1))*((21-g)//11))
|
||||
j = (y+y//4+i+2-c+c//4)%7
|
||||
|
||||
# p can be from -6 to 56 corresponding to dates 22 March to 23 May
|
||||
# (later dates apply to method 2, although 23 May never actually occurs)
|
||||
p = i-j+e
|
||||
d = 1+(p+27+(p+6)//40)%31
|
||||
m = 3+(p+26)//30
|
||||
return datetime.date(int(y),int(m),int(d))
|
||||
|
||||
@@ -0,0 +1,886 @@
|
||||
# -*- coding:iso-8859-1 -*-
|
||||
"""
|
||||
Copyright (c) 2003-2007 Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||
|
||||
This module offers extensions to the standard python 2.3+
|
||||
datetime module.
|
||||
"""
|
||||
__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
|
||||
__license__ = "PSF License"
|
||||
|
||||
import datetime
|
||||
import string
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
|
||||
import relativedelta
|
||||
import tz
|
||||
|
||||
|
||||
__all__ = ["parse", "parserinfo"]
|
||||
|
||||
|
||||
# Some pointers:
|
||||
#
|
||||
# http://www.cl.cam.ac.uk/~mgk25/iso-time.html
|
||||
# http://www.iso.ch/iso/en/prods-services/popstds/datesandtime.html
|
||||
# http://www.w3.org/TR/NOTE-datetime
|
||||
# http://ringmaster.arc.nasa.gov/tools/time_formats.html
|
||||
# http://search.cpan.org/author/MUIR/Time-modules-2003.0211/lib/Time/ParseDate.pm
|
||||
# http://stein.cshl.org/jade/distrib/docs/java.text.SimpleDateFormat.html
|
||||
|
||||
|
||||
class _timelex(object):
|
||||
|
||||
def __init__(self, instream):
|
||||
if isinstance(instream, basestring):
|
||||
instream = StringIO(instream)
|
||||
self.instream = instream
|
||||
self.wordchars = ('abcdfeghijklmnopqrstuvwxyz'
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ_'
|
||||
'ßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ'
|
||||
'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ')
|
||||
self.numchars = '0123456789'
|
||||
self.whitespace = ' \t\r\n'
|
||||
self.charstack = []
|
||||
self.tokenstack = []
|
||||
self.eof = False
|
||||
|
||||
def get_token(self):
|
||||
if self.tokenstack:
|
||||
return self.tokenstack.pop(0)
|
||||
seenletters = False
|
||||
token = None
|
||||
state = None
|
||||
wordchars = self.wordchars
|
||||
numchars = self.numchars
|
||||
whitespace = self.whitespace
|
||||
while not self.eof:
|
||||
if self.charstack:
|
||||
nextchar = self.charstack.pop(0)
|
||||
else:
|
||||
nextchar = self.instream.read(1)
|
||||
while nextchar == '\x00':
|
||||
nextchar = self.instream.read(1)
|
||||
if not nextchar:
|
||||
self.eof = True
|
||||
break
|
||||
elif not state:
|
||||
token = nextchar
|
||||
if nextchar in wordchars:
|
||||
state = 'a'
|
||||
elif nextchar in numchars:
|
||||
state = '0'
|
||||
elif nextchar in whitespace:
|
||||
token = ' '
|
||||
break # emit token
|
||||
else:
|
||||
break # emit token
|
||||
elif state == 'a':
|
||||
seenletters = True
|
||||
if nextchar in wordchars:
|
||||
token += nextchar
|
||||
elif nextchar == '.':
|
||||
token += nextchar
|
||||
state = 'a.'
|
||||
else:
|
||||
self.charstack.append(nextchar)
|
||||
break # emit token
|
||||
elif state == '0':
|
||||
if nextchar in numchars:
|
||||
token += nextchar
|
||||
elif nextchar == '.':
|
||||
token += nextchar
|
||||
state = '0.'
|
||||
else:
|
||||
self.charstack.append(nextchar)
|
||||
break # emit token
|
||||
elif state == 'a.':
|
||||
seenletters = True
|
||||
if nextchar == '.' or nextchar in wordchars:
|
||||
token += nextchar
|
||||
elif nextchar in numchars and token[-1] == '.':
|
||||
token += nextchar
|
||||
state = '0.'
|
||||
else:
|
||||
self.charstack.append(nextchar)
|
||||
break # emit token
|
||||
elif state == '0.':
|
||||
if nextchar == '.' or nextchar in numchars:
|
||||
token += nextchar
|
||||
elif nextchar in wordchars and token[-1] == '.':
|
||||
token += nextchar
|
||||
state = 'a.'
|
||||
else:
|
||||
self.charstack.append(nextchar)
|
||||
break # emit token
|
||||
if (state in ('a.', '0.') and
|
||||
(seenletters or token.count('.') > 1 or token[-1] == '.')):
|
||||
l = token.split('.')
|
||||
token = l[0]
|
||||
for tok in l[1:]:
|
||||
self.tokenstack.append('.')
|
||||
if tok:
|
||||
self.tokenstack.append(tok)
|
||||
return token
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def next(self):
|
||||
token = self.get_token()
|
||||
if token is None:
|
||||
raise StopIteration
|
||||
return token
|
||||
|
||||
def split(cls, s):
|
||||
return list(cls(s))
|
||||
split = classmethod(split)
|
||||
|
||||
|
||||
class _resultbase(object):
|
||||
|
||||
def __init__(self):
|
||||
for attr in self.__slots__:
|
||||
setattr(self, attr, None)
|
||||
|
||||
def _repr(self, classname):
|
||||
l = []
|
||||
for attr in self.__slots__:
|
||||
value = getattr(self, attr)
|
||||
if value is not None:
|
||||
l.append("%s=%s" % (attr, `value`))
|
||||
return "%s(%s)" % (classname, ", ".join(l))
|
||||
|
||||
def __repr__(self):
|
||||
return self._repr(self.__class__.__name__)
|
||||
|
||||
|
||||
class parserinfo(object):
|
||||
|
||||
# m from a.m/p.m, t from ISO T separator
|
||||
JUMP = [" ", ".", ",", ";", "-", "/", "'",
|
||||
"at", "on", "and", "ad", "m", "t", "of",
|
||||
"st", "nd", "rd", "th"]
|
||||
|
||||
WEEKDAYS = [("Mon", "Monday"),
|
||||
("Tue", "Tuesday"),
|
||||
("Wed", "Wednesday"),
|
||||
("Thu", "Thursday"),
|
||||
("Fri", "Friday"),
|
||||
("Sat", "Saturday"),
|
||||
("Sun", "Sunday")]
|
||||
MONTHS = [("Jan", "January"),
|
||||
("Feb", "February"),
|
||||
("Mar", "March"),
|
||||
("Apr", "April"),
|
||||
("May", "May"),
|
||||
("Jun", "June"),
|
||||
("Jul", "July"),
|
||||
("Aug", "August"),
|
||||
("Sep", "September"),
|
||||
("Oct", "October"),
|
||||
("Nov", "November"),
|
||||
("Dec", "December")]
|
||||
HMS = [("h", "hour", "hours"),
|
||||
("m", "minute", "minutes"),
|
||||
("s", "second", "seconds")]
|
||||
AMPM = [("am", "a"),
|
||||
("pm", "p")]
|
||||
UTCZONE = ["UTC", "GMT", "Z"]
|
||||
PERTAIN = ["of"]
|
||||
TZOFFSET = {}
|
||||
|
||||
def __init__(self, dayfirst=False, yearfirst=False):
|
||||
self._jump = self._convert(self.JUMP)
|
||||
self._weekdays = self._convert(self.WEEKDAYS)
|
||||
self._months = self._convert(self.MONTHS)
|
||||
self._hms = self._convert(self.HMS)
|
||||
self._ampm = self._convert(self.AMPM)
|
||||
self._utczone = self._convert(self.UTCZONE)
|
||||
self._pertain = self._convert(self.PERTAIN)
|
||||
|
||||
self.dayfirst = dayfirst
|
||||
self.yearfirst = yearfirst
|
||||
|
||||
self._year = time.localtime().tm_year
|
||||
self._century = self._year//100*100
|
||||
|
||||
def _convert(self, lst):
|
||||
dct = {}
|
||||
for i in range(len(lst)):
|
||||
v = lst[i]
|
||||
if isinstance(v, tuple):
|
||||
for v in v:
|
||||
dct[v.lower()] = i
|
||||
else:
|
||||
dct[v.lower()] = i
|
||||
return dct
|
||||
|
||||
def jump(self, name):
|
||||
return name.lower() in self._jump
|
||||
|
||||
def weekday(self, name):
|
||||
if len(name) >= 3:
|
||||
try:
|
||||
return self._weekdays[name.lower()]
|
||||
except KeyError:
|
||||
pass
|
||||
return None
|
||||
|
||||
def month(self, name):
|
||||
if len(name) >= 3:
|
||||
try:
|
||||
return self._months[name.lower()]+1
|
||||
except KeyError:
|
||||
pass
|
||||
return None
|
||||
|
||||
def hms(self, name):
|
||||
try:
|
||||
return self._hms[name.lower()]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def ampm(self, name):
|
||||
try:
|
||||
return self._ampm[name.lower()]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def pertain(self, name):
|
||||
return name.lower() in self._pertain
|
||||
|
||||
def utczone(self, name):
|
||||
return name.lower() in self._utczone
|
||||
|
||||
def tzoffset(self, name):
|
||||
if name in self._utczone:
|
||||
return 0
|
||||
return self.TZOFFSET.get(name)
|
||||
|
||||
def convertyear(self, year):
|
||||
if year < 100:
|
||||
year += self._century
|
||||
if abs(year-self._year) >= 50:
|
||||
if year < self._year:
|
||||
year += 100
|
||||
else:
|
||||
year -= 100
|
||||
return year
|
||||
|
||||
def validate(self, res):
|
||||
# move to info
|
||||
if res.year is not None:
|
||||
res.year = self.convertyear(res.year)
|
||||
if res.tzoffset == 0 and not res.tzname or res.tzname == 'Z':
|
||||
res.tzname = "UTC"
|
||||
res.tzoffset = 0
|
||||
elif res.tzoffset != 0 and res.tzname and self.utczone(res.tzname):
|
||||
res.tzoffset = 0
|
||||
return True
|
||||
|
||||
|
||||
class parser(object):
|
||||
|
||||
def __init__(self, info=None):
|
||||
self.info = info or parserinfo()
|
||||
|
||||
def parse(self, timestr, default=None,
|
||||
ignoretz=False, tzinfos=None,
|
||||
**kwargs):
|
||||
if not default:
|
||||
default = datetime.datetime.now().replace(hour=0, minute=0,
|
||||
second=0, microsecond=0)
|
||||
res = self._parse(timestr, **kwargs)
|
||||
if res is None:
|
||||
raise ValueError, "unknown string format"
|
||||
repl = {}
|
||||
for attr in ["year", "month", "day", "hour",
|
||||
"minute", "second", "microsecond"]:
|
||||
value = getattr(res, attr)
|
||||
if value is not None:
|
||||
repl[attr] = value
|
||||
ret = default.replace(**repl)
|
||||
if res.weekday is not None and not res.day:
|
||||
ret = ret+relativedelta.relativedelta(weekday=res.weekday)
|
||||
if not ignoretz:
|
||||
if callable(tzinfos) or tzinfos and res.tzname in tzinfos:
|
||||
if callable(tzinfos):
|
||||
tzdata = tzinfos(res.tzname, res.tzoffset)
|
||||
else:
|
||||
tzdata = tzinfos.get(res.tzname)
|
||||
if isinstance(tzdata, datetime.tzinfo):
|
||||
tzinfo = tzdata
|
||||
elif isinstance(tzdata, basestring):
|
||||
tzinfo = tz.tzstr(tzdata)
|
||||
elif isinstance(tzdata, int):
|
||||
tzinfo = tz.tzoffset(res.tzname, tzdata)
|
||||
else:
|
||||
raise ValueError, "offset must be tzinfo subclass, " \
|
||||
"tz string, or int offset"
|
||||
ret = ret.replace(tzinfo=tzinfo)
|
||||
elif res.tzname and res.tzname in time.tzname:
|
||||
ret = ret.replace(tzinfo=tz.tzlocal())
|
||||
elif res.tzoffset == 0:
|
||||
ret = ret.replace(tzinfo=tz.tzutc())
|
||||
elif res.tzoffset:
|
||||
ret = ret.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset))
|
||||
return ret
|
||||
|
||||
class _result(_resultbase):
|
||||
__slots__ = ["year", "month", "day", "weekday",
|
||||
"hour", "minute", "second", "microsecond",
|
||||
"tzname", "tzoffset"]
|
||||
|
||||
def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False):
|
||||
info = self.info
|
||||
if dayfirst is None:
|
||||
dayfirst = info.dayfirst
|
||||
if yearfirst is None:
|
||||
yearfirst = info.yearfirst
|
||||
res = self._result()
|
||||
l = _timelex.split(timestr)
|
||||
try:
|
||||
|
||||
# year/month/day list
|
||||
ymd = []
|
||||
|
||||
# Index of the month string in ymd
|
||||
mstridx = -1
|
||||
|
||||
len_l = len(l)
|
||||
i = 0
|
||||
while i < len_l:
|
||||
|
||||
# Check if it's a number
|
||||
try:
|
||||
value_repr = l[i]
|
||||
value = float(value_repr)
|
||||
except ValueError:
|
||||
value = None
|
||||
|
||||
if value is not None:
|
||||
# Token is a number
|
||||
len_li = len(l[i])
|
||||
i += 1
|
||||
if (len(ymd) == 3 and len_li in (2, 4)
|
||||
and (i >= len_l or (l[i] != ':' and
|
||||
info.hms(l[i]) is None))):
|
||||
# 19990101T23[59]
|
||||
s = l[i-1]
|
||||
res.hour = int(s[:2])
|
||||
if len_li == 4:
|
||||
res.minute = int(s[2:])
|
||||
elif len_li == 6 or (len_li > 6 and l[i-1].find('.') == 6):
|
||||
# YYMMDD or HHMMSS[.ss]
|
||||
s = l[i-1]
|
||||
if not ymd and l[i-1].find('.') == -1:
|
||||
ymd.append(info.convertyear(int(s[:2])))
|
||||
ymd.append(int(s[2:4]))
|
||||
ymd.append(int(s[4:]))
|
||||
else:
|
||||
# 19990101T235959[.59]
|
||||
res.hour = int(s[:2])
|
||||
res.minute = int(s[2:4])
|
||||
res.second, res.microsecond = _parsems(s[4:])
|
||||
elif len_li == 8:
|
||||
# YYYYMMDD
|
||||
s = l[i-1]
|
||||
ymd.append(int(s[:4]))
|
||||
ymd.append(int(s[4:6]))
|
||||
ymd.append(int(s[6:]))
|
||||
elif len_li in (12, 14):
|
||||
# YYYYMMDDhhmm[ss]
|
||||
s = l[i-1]
|
||||
ymd.append(int(s[:4]))
|
||||
ymd.append(int(s[4:6]))
|
||||
ymd.append(int(s[6:8]))
|
||||
res.hour = int(s[8:10])
|
||||
res.minute = int(s[10:12])
|
||||
if len_li == 14:
|
||||
res.second = int(s[12:])
|
||||
elif ((i < len_l and info.hms(l[i]) is not None) or
|
||||
(i+1 < len_l and l[i] == ' ' and
|
||||
info.hms(l[i+1]) is not None)):
|
||||
# HH[ ]h or MM[ ]m or SS[.ss][ ]s
|
||||
if l[i] == ' ':
|
||||
i += 1
|
||||
idx = info.hms(l[i])
|
||||
while True:
|
||||
if idx == 0:
|
||||
res.hour = int(value)
|
||||
if value%1:
|
||||
res.minute = int(60*(value%1))
|
||||
elif idx == 1:
|
||||
res.minute = int(value)
|
||||
if value%1:
|
||||
res.second = int(60*(value%1))
|
||||
elif idx == 2:
|
||||
res.second, res.microsecond = \
|
||||
_parsems(value_repr)
|
||||
i += 1
|
||||
if i >= len_l or idx == 2:
|
||||
break
|
||||
# 12h00
|
||||
try:
|
||||
value_repr = l[i]
|
||||
value = float(value_repr)
|
||||
except ValueError:
|
||||
break
|
||||
else:
|
||||
i += 1
|
||||
idx += 1
|
||||
if i < len_l:
|
||||
newidx = info.hms(l[i])
|
||||
if newidx is not None:
|
||||
idx = newidx
|
||||
elif i+1 < len_l and l[i] == ':':
|
||||
# HH:MM[:SS[.ss]]
|
||||
res.hour = int(value)
|
||||
i += 1
|
||||
value = float(l[i])
|
||||
res.minute = int(value)
|
||||
if value%1:
|
||||
res.second = int(60*(value%1))
|
||||
i += 1
|
||||
if i < len_l and l[i] == ':':
|
||||
res.second, res.microsecond = _parsems(l[i+1])
|
||||
i += 2
|
||||
elif i < len_l and l[i] in ('-', '/', '.'):
|
||||
sep = l[i]
|
||||
ymd.append(int(value))
|
||||
i += 1
|
||||
if i < len_l and not info.jump(l[i]):
|
||||
try:
|
||||
# 01-01[-01]
|
||||
ymd.append(int(l[i]))
|
||||
except ValueError:
|
||||
# 01-Jan[-01]
|
||||
value = info.month(l[i])
|
||||
if value is not None:
|
||||
ymd.append(value)
|
||||
assert mstridx == -1
|
||||
mstridx = len(ymd)-1
|
||||
else:
|
||||
return None
|
||||
i += 1
|
||||
if i < len_l and l[i] == sep:
|
||||
# We have three members
|
||||
i += 1
|
||||
value = info.month(l[i])
|
||||
if value is not None:
|
||||
ymd.append(value)
|
||||
mstridx = len(ymd)-1
|
||||
assert mstridx == -1
|
||||
else:
|
||||
ymd.append(int(l[i]))
|
||||
i += 1
|
||||
elif i >= len_l or info.jump(l[i]):
|
||||
if i+1 < len_l and info.ampm(l[i+1]) is not None:
|
||||
# 12 am
|
||||
res.hour = int(value)
|
||||
if res.hour < 12 and info.ampm(l[i+1]) == 1:
|
||||
res.hour += 12
|
||||
elif res.hour == 12 and info.ampm(l[i+1]) == 0:
|
||||
res.hour = 0
|
||||
i += 1
|
||||
else:
|
||||
# Year, month or day
|
||||
ymd.append(int(value))
|
||||
i += 1
|
||||
elif info.ampm(l[i]) is not None:
|
||||
# 12am
|
||||
res.hour = int(value)
|
||||
if res.hour < 12 and info.ampm(l[i]) == 1:
|
||||
res.hour += 12
|
||||
elif res.hour == 12 and info.ampm(l[i]) == 0:
|
||||
res.hour = 0
|
||||
i += 1
|
||||
elif not fuzzy:
|
||||
return None
|
||||
else:
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Check weekday
|
||||
value = info.weekday(l[i])
|
||||
if value is not None:
|
||||
res.weekday = value
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Check month name
|
||||
value = info.month(l[i])
|
||||
if value is not None:
|
||||
ymd.append(value)
|
||||
assert mstridx == -1
|
||||
mstridx = len(ymd)-1
|
||||
i += 1
|
||||
if i < len_l:
|
||||
if l[i] in ('-', '/'):
|
||||
# Jan-01[-99]
|
||||
sep = l[i]
|
||||
i += 1
|
||||
ymd.append(int(l[i]))
|
||||
i += 1
|
||||
if i < len_l and l[i] == sep:
|
||||
# Jan-01-99
|
||||
i += 1
|
||||
ymd.append(int(l[i]))
|
||||
i += 1
|
||||
elif (i+3 < len_l and l[i] == l[i+2] == ' '
|
||||
and info.pertain(l[i+1])):
|
||||
# Jan of 01
|
||||
# In this case, 01 is clearly year
|
||||
try:
|
||||
value = int(l[i+3])
|
||||
except ValueError:
|
||||
# Wrong guess
|
||||
pass
|
||||
else:
|
||||
# Convert it here to become unambiguous
|
||||
ymd.append(info.convertyear(value))
|
||||
i += 4
|
||||
continue
|
||||
|
||||
# Check am/pm
|
||||
value = info.ampm(l[i])
|
||||
if value is not None:
|
||||
if value == 1 and res.hour < 12:
|
||||
res.hour += 12
|
||||
elif value == 0 and res.hour == 12:
|
||||
res.hour = 0
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Check for a timezone name
|
||||
if (res.hour is not None and len(l[i]) <= 5 and
|
||||
res.tzname is None and res.tzoffset is None and
|
||||
not [x for x in l[i] if x not in string.ascii_uppercase]):
|
||||
res.tzname = l[i]
|
||||
res.tzoffset = info.tzoffset(res.tzname)
|
||||
i += 1
|
||||
|
||||
# Check for something like GMT+3, or BRST+3. Notice
|
||||
# that it doesn't mean "I am 3 hours after GMT", but
|
||||
# "my time +3 is GMT". If found, we reverse the
|
||||
# logic so that timezone parsing code will get it
|
||||
# right.
|
||||
if i < len_l and l[i] in ('+', '-'):
|
||||
l[i] = ('+', '-')[l[i] == '+']
|
||||
res.tzoffset = None
|
||||
if info.utczone(res.tzname):
|
||||
# With something like GMT+3, the timezone
|
||||
# is *not* GMT.
|
||||
res.tzname = None
|
||||
|
||||
continue
|
||||
|
||||
# Check for a numbered timezone
|
||||
if res.hour is not None and l[i] in ('+', '-'):
|
||||
signal = (-1,1)[l[i] == '+']
|
||||
i += 1
|
||||
len_li = len(l[i])
|
||||
if len_li == 4:
|
||||
# -0300
|
||||
res.tzoffset = int(l[i][:2])*3600+int(l[i][2:])*60
|
||||
elif i+1 < len_l and l[i+1] == ':':
|
||||
# -03:00
|
||||
res.tzoffset = int(l[i])*3600+int(l[i+2])*60
|
||||
i += 2
|
||||
elif len_li <= 2:
|
||||
# -[0]3
|
||||
res.tzoffset = int(l[i][:2])*3600
|
||||
else:
|
||||
return None
|
||||
i += 1
|
||||
res.tzoffset *= signal
|
||||
|
||||
# Look for a timezone name between parenthesis
|
||||
if (i+3 < len_l and
|
||||
info.jump(l[i]) and l[i+1] == '(' and l[i+3] == ')' and
|
||||
3 <= len(l[i+2]) <= 5 and
|
||||
not [x for x in l[i+2]
|
||||
if x not in string.ascii_uppercase]):
|
||||
# -0300 (BRST)
|
||||
res.tzname = l[i+2]
|
||||
i += 4
|
||||
continue
|
||||
|
||||
# Check jumps
|
||||
if not (info.jump(l[i]) or fuzzy):
|
||||
return None
|
||||
|
||||
i += 1
|
||||
|
||||
# Process year/month/day
|
||||
len_ymd = len(ymd)
|
||||
if len_ymd > 3:
|
||||
# More than three members!?
|
||||
return None
|
||||
elif len_ymd == 1 or (mstridx != -1 and len_ymd == 2):
|
||||
# One member, or two members with a month string
|
||||
if mstridx != -1:
|
||||
res.month = ymd[mstridx]
|
||||
del ymd[mstridx]
|
||||
if len_ymd > 1 or mstridx == -1:
|
||||
if ymd[0] > 31:
|
||||
res.year = ymd[0]
|
||||
else:
|
||||
res.day = ymd[0]
|
||||
elif len_ymd == 2:
|
||||
# Two members with numbers
|
||||
if ymd[0] > 31:
|
||||
# 99-01
|
||||
res.year, res.month = ymd
|
||||
elif ymd[1] > 31:
|
||||
# 01-99
|
||||
res.month, res.year = ymd
|
||||
elif dayfirst and ymd[1] <= 12:
|
||||
# 13-01
|
||||
res.day, res.month = ymd
|
||||
else:
|
||||
# 01-13
|
||||
res.month, res.day = ymd
|
||||
if len_ymd == 3:
|
||||
# Three members
|
||||
if mstridx == 0:
|
||||
res.month, res.day, res.year = ymd
|
||||
elif mstridx == 1:
|
||||
if ymd[0] > 31 or (yearfirst and ymd[2] <= 31):
|
||||
# 99-Jan-01
|
||||
res.year, res.month, res.day = ymd
|
||||
else:
|
||||
# 01-Jan-01
|
||||
# Give precendence to day-first, since
|
||||
# two-digit years is usually hand-written.
|
||||
res.day, res.month, res.year = ymd
|
||||
elif mstridx == 2:
|
||||
# WTF!?
|
||||
if ymd[1] > 31:
|
||||
# 01-99-Jan
|
||||
res.day, res.year, res.month = ymd
|
||||
else:
|
||||
# 99-01-Jan
|
||||
res.year, res.day, res.month = ymd
|
||||
else:
|
||||
if ymd[0] > 31 or \
|
||||
(yearfirst and ymd[1] <= 12 and ymd[2] <= 31):
|
||||
# 99-01-01
|
||||
res.year, res.month, res.day = ymd
|
||||
elif ymd[0] > 12 or (dayfirst and ymd[1] <= 12):
|
||||
# 13-01-01
|
||||
res.day, res.month, res.year = ymd
|
||||
else:
|
||||
# 01-13-01
|
||||
res.month, res.day, res.year = ymd
|
||||
|
||||
except (IndexError, ValueError, AssertionError):
|
||||
return None
|
||||
|
||||
if not info.validate(res):
|
||||
return None
|
||||
return res
|
||||
|
||||
DEFAULTPARSER = parser()
|
||||
def parse(timestr, parserinfo=None, **kwargs):
|
||||
if parserinfo:
|
||||
return parser(parserinfo).parse(timestr, **kwargs)
|
||||
else:
|
||||
return DEFAULTPARSER.parse(timestr, **kwargs)
|
||||
|
||||
|
||||
class _tzparser(object):
|
||||
|
||||
class _result(_resultbase):
|
||||
|
||||
__slots__ = ["stdabbr", "stdoffset", "dstabbr", "dstoffset",
|
||||
"start", "end"]
|
||||
|
||||
class _attr(_resultbase):
|
||||
__slots__ = ["month", "week", "weekday",
|
||||
"yday", "jyday", "day", "time"]
|
||||
|
||||
def __repr__(self):
|
||||
return self._repr("")
|
||||
|
||||
def __init__(self):
|
||||
_resultbase.__init__(self)
|
||||
self.start = self._attr()
|
||||
self.end = self._attr()
|
||||
|
||||
def parse(self, tzstr):
|
||||
res = self._result()
|
||||
l = _timelex.split(tzstr)
|
||||
try:
|
||||
|
||||
len_l = len(l)
|
||||
|
||||
i = 0
|
||||
while i < len_l:
|
||||
# BRST+3[BRDT[+2]]
|
||||
j = i
|
||||
while j < len_l and not [x for x in l[j]
|
||||
if x in "0123456789:,-+"]:
|
||||
j += 1
|
||||
if j != i:
|
||||
if not res.stdabbr:
|
||||
offattr = "stdoffset"
|
||||
res.stdabbr = "".join(l[i:j])
|
||||
else:
|
||||
offattr = "dstoffset"
|
||||
res.dstabbr = "".join(l[i:j])
|
||||
i = j
|
||||
if (i < len_l and
|
||||
(l[i] in ('+', '-') or l[i][0] in "0123456789")):
|
||||
if l[i] in ('+', '-'):
|
||||
# Yes, that's right. See the TZ variable
|
||||
# documentation.
|
||||
signal = (1,-1)[l[i] == '+']
|
||||
i += 1
|
||||
else:
|
||||
signal = -1
|
||||
len_li = len(l[i])
|
||||
if len_li == 4:
|
||||
# -0300
|
||||
setattr(res, offattr,
|
||||
(int(l[i][:2])*3600+int(l[i][2:])*60)*signal)
|
||||
elif i+1 < len_l and l[i+1] == ':':
|
||||
# -03:00
|
||||
setattr(res, offattr,
|
||||
(int(l[i])*3600+int(l[i+2])*60)*signal)
|
||||
i += 2
|
||||
elif len_li <= 2:
|
||||
# -[0]3
|
||||
setattr(res, offattr,
|
||||
int(l[i][:2])*3600*signal)
|
||||
else:
|
||||
return None
|
||||
i += 1
|
||||
if res.dstabbr:
|
||||
break
|
||||
else:
|
||||
break
|
||||
|
||||
if i < len_l:
|
||||
for j in range(i, len_l):
|
||||
if l[j] == ';': l[j] = ','
|
||||
|
||||
assert l[i] == ','
|
||||
|
||||
i += 1
|
||||
|
||||
if i >= len_l:
|
||||
pass
|
||||
elif (8 <= l.count(',') <= 9 and
|
||||
not [y for x in l[i:] if x != ','
|
||||
for y in x if y not in "0123456789"]):
|
||||
# GMT0BST,3,0,30,3600,10,0,26,7200[,3600]
|
||||
for x in (res.start, res.end):
|
||||
x.month = int(l[i])
|
||||
i += 2
|
||||
if l[i] == '-':
|
||||
value = int(l[i+1])*-1
|
||||
i += 1
|
||||
else:
|
||||
value = int(l[i])
|
||||
i += 2
|
||||
if value:
|
||||
x.week = value
|
||||
x.weekday = (int(l[i])-1)%7
|
||||
else:
|
||||
x.day = int(l[i])
|
||||
i += 2
|
||||
x.time = int(l[i])
|
||||
i += 2
|
||||
if i < len_l:
|
||||
if l[i] in ('-','+'):
|
||||
signal = (-1,1)[l[i] == "+"]
|
||||
i += 1
|
||||
else:
|
||||
signal = 1
|
||||
res.dstoffset = (res.stdoffset+int(l[i]))*signal
|
||||
elif (l.count(',') == 2 and l[i:].count('/') <= 2 and
|
||||
not [y for x in l[i:] if x not in (',','/','J','M',
|
||||
'.','-',':')
|
||||
for y in x if y not in "0123456789"]):
|
||||
for x in (res.start, res.end):
|
||||
if l[i] == 'J':
|
||||
# non-leap year day (1 based)
|
||||
i += 1
|
||||
x.jyday = int(l[i])
|
||||
elif l[i] == 'M':
|
||||
# month[-.]week[-.]weekday
|
||||
i += 1
|
||||
x.month = int(l[i])
|
||||
i += 1
|
||||
assert l[i] in ('-', '.')
|
||||
i += 1
|
||||
x.week = int(l[i])
|
||||
if x.week == 5:
|
||||
x.week = -1
|
||||
i += 1
|
||||
assert l[i] in ('-', '.')
|
||||
i += 1
|
||||
x.weekday = (int(l[i])-1)%7
|
||||
else:
|
||||
# year day (zero based)
|
||||
x.yday = int(l[i])+1
|
||||
|
||||
i += 1
|
||||
|
||||
if i < len_l and l[i] == '/':
|
||||
i += 1
|
||||
# start time
|
||||
len_li = len(l[i])
|
||||
if len_li == 4:
|
||||
# -0300
|
||||
x.time = (int(l[i][:2])*3600+int(l[i][2:])*60)
|
||||
elif i+1 < len_l and l[i+1] == ':':
|
||||
# -03:00
|
||||
x.time = int(l[i])*3600+int(l[i+2])*60
|
||||
i += 2
|
||||
if i+1 < len_l and l[i+1] == ':':
|
||||
i += 2
|
||||
x.time += int(l[i])
|
||||
elif len_li <= 2:
|
||||
# -[0]3
|
||||
x.time = (int(l[i][:2])*3600)
|
||||
else:
|
||||
return None
|
||||
i += 1
|
||||
|
||||
assert i == len_l or l[i] == ','
|
||||
|
||||
i += 1
|
||||
|
||||
assert i >= len_l
|
||||
|
||||
except (IndexError, ValueError, AssertionError):
|
||||
return None
|
||||
|
||||
return res
|
||||
|
||||
|
||||
DEFAULTTZPARSER = _tzparser()
|
||||
def _parsetz(tzstr):
|
||||
return DEFAULTTZPARSER.parse(tzstr)
|
||||
|
||||
|
||||
def _parsems(value):
|
||||
"""Parse a I[.F] seconds value into (seconds, microseconds)."""
|
||||
if "." not in value:
|
||||
return int(value), 0
|
||||
else:
|
||||
i, f = value.split(".")
|
||||
return int(i), int(f.ljust(6, "0")[:6])
|
||||
|
||||
|
||||
# vim:ts=4:sw=4:et
|
||||
@@ -0,0 +1,432 @@
|
||||
"""
|
||||
Copyright (c) 2003-2010 Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||
|
||||
This module offers extensions to the standard python 2.3+
|
||||
datetime module.
|
||||
"""
|
||||
__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
|
||||
__license__ = "PSF License"
|
||||
|
||||
import datetime
|
||||
import calendar
|
||||
|
||||
__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
|
||||
|
||||
class weekday(object):
|
||||
__slots__ = ["weekday", "n"]
|
||||
|
||||
def __init__(self, weekday, n=None):
|
||||
self.weekday = weekday
|
||||
self.n = n
|
||||
|
||||
def __call__(self, n):
|
||||
if n == self.n:
|
||||
return self
|
||||
else:
|
||||
return self.__class__(self.weekday, n)
|
||||
|
||||
def __eq__(self, other):
|
||||
try:
|
||||
if self.weekday != other.weekday or self.n != other.n:
|
||||
return False
|
||||
except AttributeError:
|
||||
return False
|
||||
return True
|
||||
|
||||
def __repr__(self):
|
||||
s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday]
|
||||
if not self.n:
|
||||
return s
|
||||
else:
|
||||
return "%s(%+d)" % (s, self.n)
|
||||
|
||||
MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)])
|
||||
|
||||
class relativedelta:
|
||||
"""
|
||||
The relativedelta type is based on the specification of the excelent
|
||||
work done by M.-A. Lemburg in his mx.DateTime extension. However,
|
||||
notice that this type does *NOT* implement the same algorithm as
|
||||
his work. Do *NOT* expect it to behave like mx.DateTime's counterpart.
|
||||
|
||||
There's two different ways to build a relativedelta instance. The
|
||||
first one is passing it two date/datetime classes:
|
||||
|
||||
relativedelta(datetime1, datetime2)
|
||||
|
||||
And the other way is to use the following keyword arguments:
|
||||
|
||||
year, month, day, hour, minute, second, microsecond:
|
||||
Absolute information.
|
||||
|
||||
years, months, weeks, days, hours, minutes, seconds, microseconds:
|
||||
Relative information, may be negative.
|
||||
|
||||
weekday:
|
||||
One of the weekday instances (MO, TU, etc). These instances may
|
||||
receive a parameter N, specifying the Nth weekday, which could
|
||||
be positive or negative (like MO(+1) or MO(-2). Not specifying
|
||||
it is the same as specifying +1. You can also use an integer,
|
||||
where 0=MO.
|
||||
|
||||
leapdays:
|
||||
Will add given days to the date found, if year is a leap
|
||||
year, and the date found is post 28 of february.
|
||||
|
||||
yearday, nlyearday:
|
||||
Set the yearday or the non-leap year day (jump leap days).
|
||||
These are converted to day/month/leapdays information.
|
||||
|
||||
Here is the behavior of operations with relativedelta:
|
||||
|
||||
1) Calculate the absolute year, using the 'year' argument, or the
|
||||
original datetime year, if the argument is not present.
|
||||
|
||||
2) Add the relative 'years' argument to the absolute year.
|
||||
|
||||
3) Do steps 1 and 2 for month/months.
|
||||
|
||||
4) Calculate the absolute day, using the 'day' argument, or the
|
||||
original datetime day, if the argument is not present. Then,
|
||||
subtract from the day until it fits in the year and month
|
||||
found after their operations.
|
||||
|
||||
5) Add the relative 'days' argument to the absolute day. Notice
|
||||
that the 'weeks' argument is multiplied by 7 and added to
|
||||
'days'.
|
||||
|
||||
6) Do steps 1 and 2 for hour/hours, minute/minutes, second/seconds,
|
||||
microsecond/microseconds.
|
||||
|
||||
7) If the 'weekday' argument is present, calculate the weekday,
|
||||
with the given (wday, nth) tuple. wday is the index of the
|
||||
weekday (0-6, 0=Mon), and nth is the number of weeks to add
|
||||
forward or backward, depending on its signal. Notice that if
|
||||
the calculated date is already Monday, for example, using
|
||||
(0, 1) or (0, -1) won't change the day.
|
||||
"""
|
||||
|
||||
def __init__(self, dt1=None, dt2=None,
|
||||
years=0, months=0, days=0, leapdays=0, weeks=0,
|
||||
hours=0, minutes=0, seconds=0, microseconds=0,
|
||||
year=None, month=None, day=None, weekday=None,
|
||||
yearday=None, nlyearday=None,
|
||||
hour=None, minute=None, second=None, microsecond=None):
|
||||
if dt1 and dt2:
|
||||
if not isinstance(dt1, datetime.date) or \
|
||||
not isinstance(dt2, datetime.date):
|
||||
raise TypeError, "relativedelta only diffs datetime/date"
|
||||
if type(dt1) is not type(dt2):
|
||||
if not isinstance(dt1, datetime.datetime):
|
||||
dt1 = datetime.datetime.fromordinal(dt1.toordinal())
|
||||
elif not isinstance(dt2, datetime.datetime):
|
||||
dt2 = datetime.datetime.fromordinal(dt2.toordinal())
|
||||
self.years = 0
|
||||
self.months = 0
|
||||
self.days = 0
|
||||
self.leapdays = 0
|
||||
self.hours = 0
|
||||
self.minutes = 0
|
||||
self.seconds = 0
|
||||
self.microseconds = 0
|
||||
self.year = None
|
||||
self.month = None
|
||||
self.day = None
|
||||
self.weekday = None
|
||||
self.hour = None
|
||||
self.minute = None
|
||||
self.second = None
|
||||
self.microsecond = None
|
||||
self._has_time = 0
|
||||
|
||||
months = (dt1.year*12+dt1.month)-(dt2.year*12+dt2.month)
|
||||
self._set_months(months)
|
||||
dtm = self.__radd__(dt2)
|
||||
if dt1 < dt2:
|
||||
while dt1 > dtm:
|
||||
months += 1
|
||||
self._set_months(months)
|
||||
dtm = self.__radd__(dt2)
|
||||
else:
|
||||
while dt1 < dtm:
|
||||
months -= 1
|
||||
self._set_months(months)
|
||||
dtm = self.__radd__(dt2)
|
||||
delta = dt1 - dtm
|
||||
self.seconds = delta.seconds+delta.days*86400
|
||||
self.microseconds = delta.microseconds
|
||||
else:
|
||||
self.years = years
|
||||
self.months = months
|
||||
self.days = days+weeks*7
|
||||
self.leapdays = leapdays
|
||||
self.hours = hours
|
||||
self.minutes = minutes
|
||||
self.seconds = seconds
|
||||
self.microseconds = microseconds
|
||||
self.year = year
|
||||
self.month = month
|
||||
self.day = day
|
||||
self.hour = hour
|
||||
self.minute = minute
|
||||
self.second = second
|
||||
self.microsecond = microsecond
|
||||
|
||||
if type(weekday) is int:
|
||||
self.weekday = weekdays[weekday]
|
||||
else:
|
||||
self.weekday = weekday
|
||||
|
||||
yday = 0
|
||||
if nlyearday:
|
||||
yday = nlyearday
|
||||
elif yearday:
|
||||
yday = yearday
|
||||
if yearday > 59:
|
||||
self.leapdays = -1
|
||||
if yday:
|
||||
ydayidx = [31,59,90,120,151,181,212,243,273,304,334,366]
|
||||
for idx, ydays in enumerate(ydayidx):
|
||||
if yday <= ydays:
|
||||
self.month = idx+1
|
||||
if idx == 0:
|
||||
self.day = yday
|
||||
else:
|
||||
self.day = yday-ydayidx[idx-1]
|
||||
break
|
||||
else:
|
||||
raise ValueError, "invalid year day (%d)" % yday
|
||||
|
||||
self._fix()
|
||||
|
||||
def _fix(self):
|
||||
if abs(self.microseconds) > 999999:
|
||||
s = self.microseconds//abs(self.microseconds)
|
||||
div, mod = divmod(self.microseconds*s, 1000000)
|
||||
self.microseconds = mod*s
|
||||
self.seconds += div*s
|
||||
if abs(self.seconds) > 59:
|
||||
s = self.seconds//abs(self.seconds)
|
||||
div, mod = divmod(self.seconds*s, 60)
|
||||
self.seconds = mod*s
|
||||
self.minutes += div*s
|
||||
if abs(self.minutes) > 59:
|
||||
s = self.minutes//abs(self.minutes)
|
||||
div, mod = divmod(self.minutes*s, 60)
|
||||
self.minutes = mod*s
|
||||
self.hours += div*s
|
||||
if abs(self.hours) > 23:
|
||||
s = self.hours//abs(self.hours)
|
||||
div, mod = divmod(self.hours*s, 24)
|
||||
self.hours = mod*s
|
||||
self.days += div*s
|
||||
if abs(self.months) > 11:
|
||||
s = self.months//abs(self.months)
|
||||
div, mod = divmod(self.months*s, 12)
|
||||
self.months = mod*s
|
||||
self.years += div*s
|
||||
if (self.hours or self.minutes or self.seconds or self.microseconds or
|
||||
self.hour is not None or self.minute is not None or
|
||||
self.second is not None or self.microsecond is not None):
|
||||
self._has_time = 1
|
||||
else:
|
||||
self._has_time = 0
|
||||
|
||||
def _set_months(self, months):
|
||||
self.months = months
|
||||
if abs(self.months) > 11:
|
||||
s = self.months//abs(self.months)
|
||||
div, mod = divmod(self.months*s, 12)
|
||||
self.months = mod*s
|
||||
self.years = div*s
|
||||
else:
|
||||
self.years = 0
|
||||
|
||||
def __radd__(self, other):
|
||||
if not isinstance(other, datetime.date):
|
||||
raise TypeError, "unsupported type for add operation"
|
||||
elif self._has_time and not isinstance(other, datetime.datetime):
|
||||
other = datetime.datetime.fromordinal(other.toordinal())
|
||||
year = (self.year or other.year)+self.years
|
||||
month = self.month or other.month
|
||||
if self.months:
|
||||
assert 1 <= abs(self.months) <= 12
|
||||
month += self.months
|
||||
if month > 12:
|
||||
year += 1
|
||||
month -= 12
|
||||
elif month < 1:
|
||||
year -= 1
|
||||
month += 12
|
||||
day = min(calendar.monthrange(year, month)[1],
|
||||
self.day or other.day)
|
||||
repl = {"year": year, "month": month, "day": day}
|
||||
for attr in ["hour", "minute", "second", "microsecond"]:
|
||||
value = getattr(self, attr)
|
||||
if value is not None:
|
||||
repl[attr] = value
|
||||
days = self.days
|
||||
if self.leapdays and month > 2 and calendar.isleap(year):
|
||||
days += self.leapdays
|
||||
ret = (other.replace(**repl)
|
||||
+ datetime.timedelta(days=days,
|
||||
hours=self.hours,
|
||||
minutes=self.minutes,
|
||||
seconds=self.seconds,
|
||||
microseconds=self.microseconds))
|
||||
if self.weekday:
|
||||
weekday, nth = self.weekday.weekday, self.weekday.n or 1
|
||||
jumpdays = (abs(nth)-1)*7
|
||||
if nth > 0:
|
||||
jumpdays += (7-ret.weekday()+weekday)%7
|
||||
else:
|
||||
jumpdays += (ret.weekday()-weekday)%7
|
||||
jumpdays *= -1
|
||||
ret += datetime.timedelta(days=jumpdays)
|
||||
return ret
|
||||
|
||||
def __rsub__(self, other):
|
||||
return self.__neg__().__radd__(other)
|
||||
|
||||
def __add__(self, other):
|
||||
if not isinstance(other, relativedelta):
|
||||
raise TypeError, "unsupported type for add operation"
|
||||
return relativedelta(years=other.years+self.years,
|
||||
months=other.months+self.months,
|
||||
days=other.days+self.days,
|
||||
hours=other.hours+self.hours,
|
||||
minutes=other.minutes+self.minutes,
|
||||
seconds=other.seconds+self.seconds,
|
||||
microseconds=other.microseconds+self.microseconds,
|
||||
leapdays=other.leapdays or self.leapdays,
|
||||
year=other.year or self.year,
|
||||
month=other.month or self.month,
|
||||
day=other.day or self.day,
|
||||
weekday=other.weekday or self.weekday,
|
||||
hour=other.hour or self.hour,
|
||||
minute=other.minute or self.minute,
|
||||
second=other.second or self.second,
|
||||
microsecond=other.second or self.microsecond)
|
||||
|
||||
def __sub__(self, other):
|
||||
if not isinstance(other, relativedelta):
|
||||
raise TypeError, "unsupported type for sub operation"
|
||||
return relativedelta(years=other.years-self.years,
|
||||
months=other.months-self.months,
|
||||
days=other.days-self.days,
|
||||
hours=other.hours-self.hours,
|
||||
minutes=other.minutes-self.minutes,
|
||||
seconds=other.seconds-self.seconds,
|
||||
microseconds=other.microseconds-self.microseconds,
|
||||
leapdays=other.leapdays or self.leapdays,
|
||||
year=other.year or self.year,
|
||||
month=other.month or self.month,
|
||||
day=other.day or self.day,
|
||||
weekday=other.weekday or self.weekday,
|
||||
hour=other.hour or self.hour,
|
||||
minute=other.minute or self.minute,
|
||||
second=other.second or self.second,
|
||||
microsecond=other.second or self.microsecond)
|
||||
|
||||
def __neg__(self):
|
||||
return relativedelta(years=-self.years,
|
||||
months=-self.months,
|
||||
days=-self.days,
|
||||
hours=-self.hours,
|
||||
minutes=-self.minutes,
|
||||
seconds=-self.seconds,
|
||||
microseconds=-self.microseconds,
|
||||
leapdays=self.leapdays,
|
||||
year=self.year,
|
||||
month=self.month,
|
||||
day=self.day,
|
||||
weekday=self.weekday,
|
||||
hour=self.hour,
|
||||
minute=self.minute,
|
||||
second=self.second,
|
||||
microsecond=self.microsecond)
|
||||
|
||||
def __nonzero__(self):
|
||||
return not (not self.years and
|
||||
not self.months and
|
||||
not self.days and
|
||||
not self.hours and
|
||||
not self.minutes and
|
||||
not self.seconds and
|
||||
not self.microseconds and
|
||||
not self.leapdays and
|
||||
self.year is None and
|
||||
self.month is None and
|
||||
self.day is None and
|
||||
self.weekday is None and
|
||||
self.hour is None and
|
||||
self.minute is None and
|
||||
self.second is None and
|
||||
self.microsecond is None)
|
||||
|
||||
def __mul__(self, other):
|
||||
f = float(other)
|
||||
return relativedelta(years = int(round(self.years*f)),
|
||||
months = int(round(self.months*f)),
|
||||
days = int(round(self.days*f)),
|
||||
hours = int(round(self.hours*f)),
|
||||
minutes = int(round(self.minutes*f)),
|
||||
seconds = int(round(self.seconds*f)),
|
||||
microseconds = self.microseconds*f,
|
||||
leapdays = self.leapdays,
|
||||
year = self.year,
|
||||
month = self.month,
|
||||
day = self.day,
|
||||
weekday = self.weekday,
|
||||
hour = self.hour,
|
||||
minute = self.minute,
|
||||
second = self.second,
|
||||
microsecond = self.microsecond)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, relativedelta):
|
||||
return False
|
||||
if self.weekday or other.weekday:
|
||||
if not self.weekday or not other.weekday:
|
||||
return False
|
||||
if self.weekday.weekday != other.weekday.weekday:
|
||||
return False
|
||||
n1, n2 = self.weekday.n, other.weekday.n
|
||||
if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)):
|
||||
return False
|
||||
return (self.years == other.years and
|
||||
self.months == other.months and
|
||||
self.days == other.days and
|
||||
self.hours == other.hours and
|
||||
self.minutes == other.minutes and
|
||||
self.seconds == other.seconds and
|
||||
self.leapdays == other.leapdays and
|
||||
self.year == other.year and
|
||||
self.month == other.month and
|
||||
self.day == other.day and
|
||||
self.hour == other.hour and
|
||||
self.minute == other.minute and
|
||||
self.second == other.second and
|
||||
self.microsecond == other.microsecond)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __div__(self, other):
|
||||
return self.__mul__(1/float(other))
|
||||
|
||||
def __repr__(self):
|
||||
l = []
|
||||
for attr in ["years", "months", "days", "leapdays",
|
||||
"hours", "minutes", "seconds", "microseconds"]:
|
||||
value = getattr(self, attr)
|
||||
if value:
|
||||
l.append("%s=%+d" % (attr, value))
|
||||
for attr in ["year", "month", "day", "weekday",
|
||||
"hour", "minute", "second", "microsecond"]:
|
||||
value = getattr(self, attr)
|
||||
if value is not None:
|
||||
l.append("%s=%s" % (attr, `value`))
|
||||
return "%s(%s)" % (self.__class__.__name__, ", ".join(l))
|
||||
|
||||
# vim:ts=4:sw=4:et
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,958 @@
|
||||
"""
|
||||
Copyright (c) 2003-2007 Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||
|
||||
This module offers extensions to the standard python 2.3+
|
||||
datetime module.
|
||||
"""
|
||||
__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
|
||||
__license__ = "PSF License"
|
||||
|
||||
import datetime
|
||||
import struct
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
|
||||
relativedelta = None
|
||||
parser = None
|
||||
rrule = None
|
||||
|
||||
__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange",
|
||||
"tzstr", "tzical", "tzwin", "tzwinlocal", "gettz"]
|
||||
|
||||
try:
|
||||
from dateutil.tzwin import tzwin, tzwinlocal
|
||||
except (ImportError, OSError):
|
||||
tzwin, tzwinlocal = None, None
|
||||
|
||||
ZERO = datetime.timedelta(0)
|
||||
EPOCHORDINAL = datetime.datetime.utcfromtimestamp(0).toordinal()
|
||||
|
||||
class tzutc(datetime.tzinfo):
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return ZERO
|
||||
|
||||
def dst(self, dt):
|
||||
return ZERO
|
||||
|
||||
def tzname(self, dt):
|
||||
return "UTC"
|
||||
|
||||
def __eq__(self, other):
|
||||
return (isinstance(other, tzutc) or
|
||||
(isinstance(other, tzoffset) and other._offset == ZERO))
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s()" % self.__class__.__name__
|
||||
|
||||
__reduce__ = object.__reduce__
|
||||
|
||||
class tzoffset(datetime.tzinfo):
|
||||
|
||||
def __init__(self, name, offset):
|
||||
self._name = name
|
||||
self._offset = datetime.timedelta(seconds=offset)
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return self._offset
|
||||
|
||||
def dst(self, dt):
|
||||
return ZERO
|
||||
|
||||
def tzname(self, dt):
|
||||
return self._name
|
||||
|
||||
def __eq__(self, other):
|
||||
return (isinstance(other, tzoffset) and
|
||||
self._offset == other._offset)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%s, %s)" % (self.__class__.__name__,
|
||||
`self._name`,
|
||||
self._offset.days*86400+self._offset.seconds)
|
||||
|
||||
__reduce__ = object.__reduce__
|
||||
|
||||
class tzlocal(datetime.tzinfo):
|
||||
|
||||
_std_offset = datetime.timedelta(seconds=-time.timezone)
|
||||
if time.daylight:
|
||||
_dst_offset = datetime.timedelta(seconds=-time.altzone)
|
||||
else:
|
||||
_dst_offset = _std_offset
|
||||
|
||||
def utcoffset(self, dt):
|
||||
if self._isdst(dt):
|
||||
return self._dst_offset
|
||||
else:
|
||||
return self._std_offset
|
||||
|
||||
def dst(self, dt):
|
||||
if self._isdst(dt):
|
||||
return self._dst_offset-self._std_offset
|
||||
else:
|
||||
return ZERO
|
||||
|
||||
def tzname(self, dt):
|
||||
return time.tzname[self._isdst(dt)]
|
||||
|
||||
def _isdst(self, dt):
|
||||
# We can't use mktime here. It is unstable when deciding if
|
||||
# the hour near to a change is DST or not.
|
||||
#
|
||||
# timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour,
|
||||
# dt.minute, dt.second, dt.weekday(), 0, -1))
|
||||
# return time.localtime(timestamp).tm_isdst
|
||||
#
|
||||
# The code above yields the following result:
|
||||
#
|
||||
#>>> import tz, datetime
|
||||
#>>> t = tz.tzlocal()
|
||||
#>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
||||
#'BRDT'
|
||||
#>>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname()
|
||||
#'BRST'
|
||||
#>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
||||
#'BRST'
|
||||
#>>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname()
|
||||
#'BRDT'
|
||||
#>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
||||
#'BRDT'
|
||||
#
|
||||
# Here is a more stable implementation:
|
||||
#
|
||||
timestamp = ((dt.toordinal() - EPOCHORDINAL) * 86400
|
||||
+ dt.hour * 3600
|
||||
+ dt.minute * 60
|
||||
+ dt.second)
|
||||
return time.localtime(timestamp+time.timezone).tm_isdst
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, tzlocal):
|
||||
return False
|
||||
return (self._std_offset == other._std_offset and
|
||||
self._dst_offset == other._dst_offset)
|
||||
return True
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s()" % self.__class__.__name__
|
||||
|
||||
__reduce__ = object.__reduce__
|
||||
|
||||
class _ttinfo(object):
|
||||
__slots__ = ["offset", "delta", "isdst", "abbr", "isstd", "isgmt"]
|
||||
|
||||
def __init__(self):
|
||||
for attr in self.__slots__:
|
||||
setattr(self, attr, None)
|
||||
|
||||
def __repr__(self):
|
||||
l = []
|
||||
for attr in self.__slots__:
|
||||
value = getattr(self, attr)
|
||||
if value is not None:
|
||||
l.append("%s=%s" % (attr, `value`))
|
||||
return "%s(%s)" % (self.__class__.__name__, ", ".join(l))
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, _ttinfo):
|
||||
return False
|
||||
return (self.offset == other.offset and
|
||||
self.delta == other.delta and
|
||||
self.isdst == other.isdst and
|
||||
self.abbr == other.abbr and
|
||||
self.isstd == other.isstd and
|
||||
self.isgmt == other.isgmt)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __getstate__(self):
|
||||
state = {}
|
||||
for name in self.__slots__:
|
||||
state[name] = getattr(self, name, None)
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
for name in self.__slots__:
|
||||
if name in state:
|
||||
setattr(self, name, state[name])
|
||||
|
||||
class tzfile(datetime.tzinfo):
|
||||
|
||||
# http://www.twinsun.com/tz/tz-link.htm
|
||||
# ftp://elsie.nci.nih.gov/pub/tz*.tar.gz
|
||||
|
||||
def __init__(self, fileobj):
|
||||
if isinstance(fileobj, basestring):
|
||||
self._filename = fileobj
|
||||
fileobj = open(fileobj)
|
||||
elif hasattr(fileobj, "name"):
|
||||
self._filename = fileobj.name
|
||||
else:
|
||||
self._filename = `fileobj`
|
||||
|
||||
# From tzfile(5):
|
||||
#
|
||||
# The time zone information files used by tzset(3)
|
||||
# begin with the magic characters "TZif" to identify
|
||||
# them as time zone information files, followed by
|
||||
# sixteen bytes reserved for future use, followed by
|
||||
# six four-byte values of type long, written in a
|
||||
# ``standard'' byte order (the high-order byte
|
||||
# of the value is written first).
|
||||
|
||||
if fileobj.read(4) != "TZif":
|
||||
raise ValueError, "magic not found"
|
||||
|
||||
fileobj.read(16)
|
||||
|
||||
(
|
||||
# The number of UTC/local indicators stored in the file.
|
||||
ttisgmtcnt,
|
||||
|
||||
# The number of standard/wall indicators stored in the file.
|
||||
ttisstdcnt,
|
||||
|
||||
# The number of leap seconds for which data is
|
||||
# stored in the file.
|
||||
leapcnt,
|
||||
|
||||
# The number of "transition times" for which data
|
||||
# is stored in the file.
|
||||
timecnt,
|
||||
|
||||
# The number of "local time types" for which data
|
||||
# is stored in the file (must not be zero).
|
||||
typecnt,
|
||||
|
||||
# The number of characters of "time zone
|
||||
# abbreviation strings" stored in the file.
|
||||
charcnt,
|
||||
|
||||
) = struct.unpack(">6l", fileobj.read(24))
|
||||
|
||||
# The above header is followed by tzh_timecnt four-byte
|
||||
# values of type long, sorted in ascending order.
|
||||
# These values are written in ``standard'' byte order.
|
||||
# Each is used as a transition time (as returned by
|
||||
# time(2)) at which the rules for computing local time
|
||||
# change.
|
||||
|
||||
if timecnt:
|
||||
self._trans_list = struct.unpack(">%dl" % timecnt,
|
||||
fileobj.read(timecnt*4))
|
||||
else:
|
||||
self._trans_list = []
|
||||
|
||||
# Next come tzh_timecnt one-byte values of type unsigned
|
||||
# char; each one tells which of the different types of
|
||||
# ``local time'' types described in the file is associated
|
||||
# with the same-indexed transition time. These values
|
||||
# serve as indices into an array of ttinfo structures that
|
||||
# appears next in the file.
|
||||
|
||||
if timecnt:
|
||||
self._trans_idx = struct.unpack(">%dB" % timecnt,
|
||||
fileobj.read(timecnt))
|
||||
else:
|
||||
self._trans_idx = []
|
||||
|
||||
# Each ttinfo structure is written as a four-byte value
|
||||
# for tt_gmtoff of type long, in a standard byte
|
||||
# order, followed by a one-byte value for tt_isdst
|
||||
# and a one-byte value for tt_abbrind. In each
|
||||
# structure, tt_gmtoff gives the number of
|
||||
# seconds to be added to UTC, tt_isdst tells whether
|
||||
# tm_isdst should be set by localtime(3), and
|
||||
# tt_abbrind serves as an index into the array of
|
||||
# time zone abbreviation characters that follow the
|
||||
# ttinfo structure(s) in the file.
|
||||
|
||||
ttinfo = []
|
||||
|
||||
for i in range(typecnt):
|
||||
ttinfo.append(struct.unpack(">lbb", fileobj.read(6)))
|
||||
|
||||
abbr = fileobj.read(charcnt)
|
||||
|
||||
# Then there are tzh_leapcnt pairs of four-byte
|
||||
# values, written in standard byte order; the
|
||||
# first value of each pair gives the time (as
|
||||
# returned by time(2)) at which a leap second
|
||||
# occurs; the second gives the total number of
|
||||
# leap seconds to be applied after the given time.
|
||||
# The pairs of values are sorted in ascending order
|
||||
# by time.
|
||||
|
||||
# Not used, for now
|
||||
if leapcnt:
|
||||
leap = struct.unpack(">%dl" % (leapcnt*2),
|
||||
fileobj.read(leapcnt*8))
|
||||
|
||||
# Then there are tzh_ttisstdcnt standard/wall
|
||||
# indicators, each stored as a one-byte value;
|
||||
# they tell whether the transition times associated
|
||||
# with local time types were specified as standard
|
||||
# time or wall clock time, and are used when
|
||||
# a time zone file is used in handling POSIX-style
|
||||
# time zone environment variables.
|
||||
|
||||
if ttisstdcnt:
|
||||
isstd = struct.unpack(">%db" % ttisstdcnt,
|
||||
fileobj.read(ttisstdcnt))
|
||||
|
||||
# Finally, there are tzh_ttisgmtcnt UTC/local
|
||||
# indicators, each stored as a one-byte value;
|
||||
# they tell whether the transition times associated
|
||||
# with local time types were specified as UTC or
|
||||
# local time, and are used when a time zone file
|
||||
# is used in handling POSIX-style time zone envi-
|
||||
# ronment variables.
|
||||
|
||||
if ttisgmtcnt:
|
||||
isgmt = struct.unpack(">%db" % ttisgmtcnt,
|
||||
fileobj.read(ttisgmtcnt))
|
||||
|
||||
# ** Everything has been read **
|
||||
|
||||
# Build ttinfo list
|
||||
self._ttinfo_list = []
|
||||
for i in range(typecnt):
|
||||
gmtoff, isdst, abbrind = ttinfo[i]
|
||||
# Round to full-minutes if that's not the case. Python's
|
||||
# datetime doesn't accept sub-minute timezones. Check
|
||||
# http://python.org/sf/1447945 for some information.
|
||||
gmtoff = (gmtoff+30)//60*60
|
||||
tti = _ttinfo()
|
||||
tti.offset = gmtoff
|
||||
tti.delta = datetime.timedelta(seconds=gmtoff)
|
||||
tti.isdst = isdst
|
||||
tti.abbr = abbr[abbrind:abbr.find('\x00', abbrind)]
|
||||
tti.isstd = (ttisstdcnt > i and isstd[i] != 0)
|
||||
tti.isgmt = (ttisgmtcnt > i and isgmt[i] != 0)
|
||||
self._ttinfo_list.append(tti)
|
||||
|
||||
# Replace ttinfo indexes for ttinfo objects.
|
||||
trans_idx = []
|
||||
for idx in self._trans_idx:
|
||||
trans_idx.append(self._ttinfo_list[idx])
|
||||
self._trans_idx = tuple(trans_idx)
|
||||
|
||||
# Set standard, dst, and before ttinfos. before will be
|
||||
# used when a given time is before any transitions,
|
||||
# and will be set to the first non-dst ttinfo, or to
|
||||
# the first dst, if all of them are dst.
|
||||
self._ttinfo_std = None
|
||||
self._ttinfo_dst = None
|
||||
self._ttinfo_before = None
|
||||
if self._ttinfo_list:
|
||||
if not self._trans_list:
|
||||
self._ttinfo_std = self._ttinfo_first = self._ttinfo_list[0]
|
||||
else:
|
||||
for i in range(timecnt-1,-1,-1):
|
||||
tti = self._trans_idx[i]
|
||||
if not self._ttinfo_std and not tti.isdst:
|
||||
self._ttinfo_std = tti
|
||||
elif not self._ttinfo_dst and tti.isdst:
|
||||
self._ttinfo_dst = tti
|
||||
if self._ttinfo_std and self._ttinfo_dst:
|
||||
break
|
||||
else:
|
||||
if self._ttinfo_dst and not self._ttinfo_std:
|
||||
self._ttinfo_std = self._ttinfo_dst
|
||||
|
||||
for tti in self._ttinfo_list:
|
||||
if not tti.isdst:
|
||||
self._ttinfo_before = tti
|
||||
break
|
||||
else:
|
||||
self._ttinfo_before = self._ttinfo_list[0]
|
||||
|
||||
# Now fix transition times to become relative to wall time.
|
||||
#
|
||||
# I'm not sure about this. In my tests, the tz source file
|
||||
# is setup to wall time, and in the binary file isstd and
|
||||
# isgmt are off, so it should be in wall time. OTOH, it's
|
||||
# always in gmt time. Let me know if you have comments
|
||||
# about this.
|
||||
laststdoffset = 0
|
||||
self._trans_list = list(self._trans_list)
|
||||
for i in range(len(self._trans_list)):
|
||||
tti = self._trans_idx[i]
|
||||
if not tti.isdst:
|
||||
# This is std time.
|
||||
self._trans_list[i] += tti.offset
|
||||
laststdoffset = tti.offset
|
||||
else:
|
||||
# This is dst time. Convert to std.
|
||||
self._trans_list[i] += laststdoffset
|
||||
self._trans_list = tuple(self._trans_list)
|
||||
|
||||
def _find_ttinfo(self, dt, laststd=0):
|
||||
timestamp = ((dt.toordinal() - EPOCHORDINAL) * 86400
|
||||
+ dt.hour * 3600
|
||||
+ dt.minute * 60
|
||||
+ dt.second)
|
||||
idx = 0
|
||||
for trans in self._trans_list:
|
||||
if timestamp < trans:
|
||||
break
|
||||
idx += 1
|
||||
else:
|
||||
return self._ttinfo_std
|
||||
if idx == 0:
|
||||
return self._ttinfo_before
|
||||
if laststd:
|
||||
while idx > 0:
|
||||
tti = self._trans_idx[idx-1]
|
||||
if not tti.isdst:
|
||||
return tti
|
||||
idx -= 1
|
||||
else:
|
||||
return self._ttinfo_std
|
||||
else:
|
||||
return self._trans_idx[idx-1]
|
||||
|
||||
def utcoffset(self, dt):
|
||||
if not self._ttinfo_std:
|
||||
return ZERO
|
||||
return self._find_ttinfo(dt).delta
|
||||
|
||||
def dst(self, dt):
|
||||
if not self._ttinfo_dst:
|
||||
return ZERO
|
||||
tti = self._find_ttinfo(dt)
|
||||
if not tti.isdst:
|
||||
return ZERO
|
||||
|
||||
# The documentation says that utcoffset()-dst() must
|
||||
# be constant for every dt.
|
||||
return tti.delta-self._find_ttinfo(dt, laststd=1).delta
|
||||
|
||||
# An alternative for that would be:
|
||||
#
|
||||
# return self._ttinfo_dst.offset-self._ttinfo_std.offset
|
||||
#
|
||||
# However, this class stores historical changes in the
|
||||
# dst offset, so I belive that this wouldn't be the right
|
||||
# way to implement this.
|
||||
|
||||
def tzname(self, dt):
|
||||
if not self._ttinfo_std:
|
||||
return None
|
||||
return self._find_ttinfo(dt).abbr
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, tzfile):
|
||||
return False
|
||||
return (self._trans_list == other._trans_list and
|
||||
self._trans_idx == other._trans_idx and
|
||||
self._ttinfo_list == other._ttinfo_list)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%s)" % (self.__class__.__name__, `self._filename`)
|
||||
|
||||
def __reduce__(self):
|
||||
if not os.path.isfile(self._filename):
|
||||
raise ValueError, "Unpickable %s class" % self.__class__.__name__
|
||||
return (self.__class__, (self._filename,))
|
||||
|
||||
class tzrange(datetime.tzinfo):
|
||||
|
||||
def __init__(self, stdabbr, stdoffset=None,
|
||||
dstabbr=None, dstoffset=None,
|
||||
start=None, end=None):
|
||||
global relativedelta
|
||||
if not relativedelta:
|
||||
from dateutil import relativedelta
|
||||
self._std_abbr = stdabbr
|
||||
self._dst_abbr = dstabbr
|
||||
if stdoffset is not None:
|
||||
self._std_offset = datetime.timedelta(seconds=stdoffset)
|
||||
else:
|
||||
self._std_offset = ZERO
|
||||
if dstoffset is not None:
|
||||
self._dst_offset = datetime.timedelta(seconds=dstoffset)
|
||||
elif dstabbr and stdoffset is not None:
|
||||
self._dst_offset = self._std_offset+datetime.timedelta(hours=+1)
|
||||
else:
|
||||
self._dst_offset = ZERO
|
||||
if dstabbr and start is None:
|
||||
self._start_delta = relativedelta.relativedelta(
|
||||
hours=+2, month=4, day=1, weekday=relativedelta.SU(+1))
|
||||
else:
|
||||
self._start_delta = start
|
||||
if dstabbr and end is None:
|
||||
self._end_delta = relativedelta.relativedelta(
|
||||
hours=+1, month=10, day=31, weekday=relativedelta.SU(-1))
|
||||
else:
|
||||
self._end_delta = end
|
||||
|
||||
def utcoffset(self, dt):
|
||||
if self._isdst(dt):
|
||||
return self._dst_offset
|
||||
else:
|
||||
return self._std_offset
|
||||
|
||||
def dst(self, dt):
|
||||
if self._isdst(dt):
|
||||
return self._dst_offset-self._std_offset
|
||||
else:
|
||||
return ZERO
|
||||
|
||||
def tzname(self, dt):
|
||||
if self._isdst(dt):
|
||||
return self._dst_abbr
|
||||
else:
|
||||
return self._std_abbr
|
||||
|
||||
def _isdst(self, dt):
|
||||
if not self._start_delta:
|
||||
return False
|
||||
year = datetime.datetime(dt.year,1,1)
|
||||
start = year+self._start_delta
|
||||
end = year+self._end_delta
|
||||
dt = dt.replace(tzinfo=None)
|
||||
if start < end:
|
||||
return dt >= start and dt < end
|
||||
else:
|
||||
return dt >= start or dt < end
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, tzrange):
|
||||
return False
|
||||
return (self._std_abbr == other._std_abbr and
|
||||
self._dst_abbr == other._dst_abbr and
|
||||
self._std_offset == other._std_offset and
|
||||
self._dst_offset == other._dst_offset and
|
||||
self._start_delta == other._start_delta and
|
||||
self._end_delta == other._end_delta)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(...)" % self.__class__.__name__
|
||||
|
||||
__reduce__ = object.__reduce__
|
||||
|
||||
class tzstr(tzrange):
|
||||
|
||||
def __init__(self, s):
|
||||
global parser
|
||||
if not parser:
|
||||
from dateutil import parser
|
||||
self._s = s
|
||||
|
||||
res = parser._parsetz(s)
|
||||
if res is None:
|
||||
raise ValueError, "unknown string format"
|
||||
|
||||
# Here we break the compatibility with the TZ variable handling.
|
||||
# GMT-3 actually *means* the timezone -3.
|
||||
if res.stdabbr in ("GMT", "UTC"):
|
||||
res.stdoffset *= -1
|
||||
|
||||
# We must initialize it first, since _delta() needs
|
||||
# _std_offset and _dst_offset set. Use False in start/end
|
||||
# to avoid building it two times.
|
||||
tzrange.__init__(self, res.stdabbr, res.stdoffset,
|
||||
res.dstabbr, res.dstoffset,
|
||||
start=False, end=False)
|
||||
|
||||
if not res.dstabbr:
|
||||
self._start_delta = None
|
||||
self._end_delta = None
|
||||
else:
|
||||
self._start_delta = self._delta(res.start)
|
||||
if self._start_delta:
|
||||
self._end_delta = self._delta(res.end, isend=1)
|
||||
|
||||
def _delta(self, x, isend=0):
|
||||
kwargs = {}
|
||||
if x.month is not None:
|
||||
kwargs["month"] = x.month
|
||||
if x.weekday is not None:
|
||||
kwargs["weekday"] = relativedelta.weekday(x.weekday, x.week)
|
||||
if x.week > 0:
|
||||
kwargs["day"] = 1
|
||||
else:
|
||||
kwargs["day"] = 31
|
||||
elif x.day:
|
||||
kwargs["day"] = x.day
|
||||
elif x.yday is not None:
|
||||
kwargs["yearday"] = x.yday
|
||||
elif x.jyday is not None:
|
||||
kwargs["nlyearday"] = x.jyday
|
||||
if not kwargs:
|
||||
# Default is to start on first sunday of april, and end
|
||||
# on last sunday of october.
|
||||
if not isend:
|
||||
kwargs["month"] = 4
|
||||
kwargs["day"] = 1
|
||||
kwargs["weekday"] = relativedelta.SU(+1)
|
||||
else:
|
||||
kwargs["month"] = 10
|
||||
kwargs["day"] = 31
|
||||
kwargs["weekday"] = relativedelta.SU(-1)
|
||||
if x.time is not None:
|
||||
kwargs["seconds"] = x.time
|
||||
else:
|
||||
# Default is 2AM.
|
||||
kwargs["seconds"] = 7200
|
||||
if isend:
|
||||
# Convert to standard time, to follow the documented way
|
||||
# of working with the extra hour. See the documentation
|
||||
# of the tzinfo class.
|
||||
delta = self._dst_offset-self._std_offset
|
||||
kwargs["seconds"] -= delta.seconds+delta.days*86400
|
||||
return relativedelta.relativedelta(**kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%s)" % (self.__class__.__name__, `self._s`)
|
||||
|
||||
class _tzicalvtzcomp:
|
||||
def __init__(self, tzoffsetfrom, tzoffsetto, isdst,
|
||||
tzname=None, rrule=None):
|
||||
self.tzoffsetfrom = datetime.timedelta(seconds=tzoffsetfrom)
|
||||
self.tzoffsetto = datetime.timedelta(seconds=tzoffsetto)
|
||||
self.tzoffsetdiff = self.tzoffsetto-self.tzoffsetfrom
|
||||
self.isdst = isdst
|
||||
self.tzname = tzname
|
||||
self.rrule = rrule
|
||||
|
||||
class _tzicalvtz(datetime.tzinfo):
|
||||
def __init__(self, tzid, comps=[]):
|
||||
self._tzid = tzid
|
||||
self._comps = comps
|
||||
self._cachedate = []
|
||||
self._cachecomp = []
|
||||
|
||||
def _find_comp(self, dt):
|
||||
if len(self._comps) == 1:
|
||||
return self._comps[0]
|
||||
dt = dt.replace(tzinfo=None)
|
||||
try:
|
||||
return self._cachecomp[self._cachedate.index(dt)]
|
||||
except ValueError:
|
||||
pass
|
||||
lastcomp = None
|
||||
lastcompdt = None
|
||||
for comp in self._comps:
|
||||
if not comp.isdst:
|
||||
# Handle the extra hour in DST -> STD
|
||||
compdt = comp.rrule.before(dt-comp.tzoffsetdiff, inc=True)
|
||||
else:
|
||||
compdt = comp.rrule.before(dt, inc=True)
|
||||
if compdt and (not lastcompdt or lastcompdt < compdt):
|
||||
lastcompdt = compdt
|
||||
lastcomp = comp
|
||||
if not lastcomp:
|
||||
# RFC says nothing about what to do when a given
|
||||
# time is before the first onset date. We'll look for the
|
||||
# first standard component, or the first component, if
|
||||
# none is found.
|
||||
for comp in self._comps:
|
||||
if not comp.isdst:
|
||||
lastcomp = comp
|
||||
break
|
||||
else:
|
||||
lastcomp = comp[0]
|
||||
self._cachedate.insert(0, dt)
|
||||
self._cachecomp.insert(0, lastcomp)
|
||||
if len(self._cachedate) > 10:
|
||||
self._cachedate.pop()
|
||||
self._cachecomp.pop()
|
||||
return lastcomp
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return self._find_comp(dt).tzoffsetto
|
||||
|
||||
def dst(self, dt):
|
||||
comp = self._find_comp(dt)
|
||||
if comp.isdst:
|
||||
return comp.tzoffsetdiff
|
||||
else:
|
||||
return ZERO
|
||||
|
||||
def tzname(self, dt):
|
||||
return self._find_comp(dt).tzname
|
||||
|
||||
def __repr__(self):
|
||||
return "<tzicalvtz %s>" % `self._tzid`
|
||||
|
||||
__reduce__ = object.__reduce__
|
||||
|
||||
class tzical:
|
||||
def __init__(self, fileobj):
|
||||
global rrule
|
||||
if not rrule:
|
||||
from dateutil import rrule
|
||||
|
||||
if isinstance(fileobj, basestring):
|
||||
self._s = fileobj
|
||||
fileobj = open(fileobj)
|
||||
elif hasattr(fileobj, "name"):
|
||||
self._s = fileobj.name
|
||||
else:
|
||||
self._s = `fileobj`
|
||||
|
||||
self._vtz = {}
|
||||
|
||||
self._parse_rfc(fileobj.read())
|
||||
|
||||
def keys(self):
|
||||
return self._vtz.keys()
|
||||
|
||||
def get(self, tzid=None):
|
||||
if tzid is None:
|
||||
keys = self._vtz.keys()
|
||||
if len(keys) == 0:
|
||||
raise ValueError, "no timezones defined"
|
||||
elif len(keys) > 1:
|
||||
raise ValueError, "more than one timezone available"
|
||||
tzid = keys[0]
|
||||
return self._vtz.get(tzid)
|
||||
|
||||
def _parse_offset(self, s):
|
||||
s = s.strip()
|
||||
if not s:
|
||||
raise ValueError, "empty offset"
|
||||
if s[0] in ('+', '-'):
|
||||
signal = (-1,+1)[s[0]=='+']
|
||||
s = s[1:]
|
||||
else:
|
||||
signal = +1
|
||||
if len(s) == 4:
|
||||
return (int(s[:2])*3600+int(s[2:])*60)*signal
|
||||
elif len(s) == 6:
|
||||
return (int(s[:2])*3600+int(s[2:4])*60+int(s[4:]))*signal
|
||||
else:
|
||||
raise ValueError, "invalid offset: "+s
|
||||
|
||||
def _parse_rfc(self, s):
|
||||
lines = s.splitlines()
|
||||
if not lines:
|
||||
raise ValueError, "empty string"
|
||||
|
||||
# Unfold
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i].rstrip()
|
||||
if not line:
|
||||
del lines[i]
|
||||
elif i > 0 and line[0] in (" ", "\t"):
|
||||
lines[i-1] += line[1:]
|
||||
del lines[i]
|
||||
else:
|
||||
i += 1
|
||||
|
||||
tzid = None
|
||||
comps = []
|
||||
invtz = False
|
||||
comptype = None
|
||||
for line in lines:
|
||||
if not line:
|
||||
continue
|
||||
name, value = line.split(':', 1)
|
||||
parms = name.split(';')
|
||||
if not parms:
|
||||
raise ValueError, "empty property name"
|
||||
name = parms[0].upper()
|
||||
parms = parms[1:]
|
||||
if invtz:
|
||||
if name == "BEGIN":
|
||||
if value in ("STANDARD", "DAYLIGHT"):
|
||||
# Process component
|
||||
pass
|
||||
else:
|
||||
raise ValueError, "unknown component: "+value
|
||||
comptype = value
|
||||
founddtstart = False
|
||||
tzoffsetfrom = None
|
||||
tzoffsetto = None
|
||||
rrulelines = []
|
||||
tzname = None
|
||||
elif name == "END":
|
||||
if value == "VTIMEZONE":
|
||||
if comptype:
|
||||
raise ValueError, \
|
||||
"component not closed: "+comptype
|
||||
if not tzid:
|
||||
raise ValueError, \
|
||||
"mandatory TZID not found"
|
||||
if not comps:
|
||||
raise ValueError, \
|
||||
"at least one component is needed"
|
||||
# Process vtimezone
|
||||
self._vtz[tzid] = _tzicalvtz(tzid, comps)
|
||||
invtz = False
|
||||
elif value == comptype:
|
||||
if not founddtstart:
|
||||
raise ValueError, \
|
||||
"mandatory DTSTART not found"
|
||||
if tzoffsetfrom is None:
|
||||
raise ValueError, \
|
||||
"mandatory TZOFFSETFROM not found"
|
||||
if tzoffsetto is None:
|
||||
raise ValueError, \
|
||||
"mandatory TZOFFSETFROM not found"
|
||||
# Process component
|
||||
rr = None
|
||||
if rrulelines:
|
||||
rr = rrule.rrulestr("\n".join(rrulelines),
|
||||
compatible=True,
|
||||
ignoretz=True,
|
||||
cache=True)
|
||||
comp = _tzicalvtzcomp(tzoffsetfrom, tzoffsetto,
|
||||
(comptype == "DAYLIGHT"),
|
||||
tzname, rr)
|
||||
comps.append(comp)
|
||||
comptype = None
|
||||
else:
|
||||
raise ValueError, \
|
||||
"invalid component end: "+value
|
||||
elif comptype:
|
||||
if name == "DTSTART":
|
||||
rrulelines.append(line)
|
||||
founddtstart = True
|
||||
elif name in ("RRULE", "RDATE", "EXRULE", "EXDATE"):
|
||||
rrulelines.append(line)
|
||||
elif name == "TZOFFSETFROM":
|
||||
if parms:
|
||||
raise ValueError, \
|
||||
"unsupported %s parm: %s "%(name, parms[0])
|
||||
tzoffsetfrom = self._parse_offset(value)
|
||||
elif name == "TZOFFSETTO":
|
||||
if parms:
|
||||
raise ValueError, \
|
||||
"unsupported TZOFFSETTO parm: "+parms[0]
|
||||
tzoffsetto = self._parse_offset(value)
|
||||
elif name == "TZNAME":
|
||||
if parms:
|
||||
raise ValueError, \
|
||||
"unsupported TZNAME parm: "+parms[0]
|
||||
tzname = value
|
||||
elif name == "COMMENT":
|
||||
pass
|
||||
elif name.upper().startswith('X-'):
|
||||
# Ignore experimental properties.
|
||||
pass
|
||||
else:
|
||||
raise ValueError, "unsupported property: "+name
|
||||
else:
|
||||
if name == "TZID":
|
||||
for p in parms:
|
||||
if not p.upper().startswith('X-'):
|
||||
raise ValueError, \
|
||||
"unsupported TZID parm: "+p
|
||||
tzid = value
|
||||
elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"):
|
||||
pass
|
||||
elif name.upper().startswith('X-'):
|
||||
# Ignore experimental properties.
|
||||
pass
|
||||
else:
|
||||
raise ValueError, "unsupported property: "+name
|
||||
elif name == "BEGIN" and value == "VTIMEZONE":
|
||||
tzid = None
|
||||
comps = []
|
||||
invtz = True
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%s)" % (self.__class__.__name__, `self._s`)
|
||||
|
||||
if sys.platform != "win32":
|
||||
TZFILES = ["/etc/localtime", "localtime"]
|
||||
TZPATHS = ["/usr/share/zoneinfo", "/usr/lib/zoneinfo", "/etc/zoneinfo"]
|
||||
else:
|
||||
TZFILES = []
|
||||
TZPATHS = []
|
||||
|
||||
def gettz(name=None):
|
||||
tz = None
|
||||
if not name:
|
||||
try:
|
||||
name = os.environ["TZ"]
|
||||
except KeyError:
|
||||
pass
|
||||
if name is None or name == ":":
|
||||
for filepath in TZFILES:
|
||||
if not os.path.isabs(filepath):
|
||||
filename = filepath
|
||||
for path in TZPATHS:
|
||||
filepath = os.path.join(path, filename)
|
||||
if os.path.isfile(filepath):
|
||||
break
|
||||
else:
|
||||
continue
|
||||
if os.path.isfile(filepath):
|
||||
try:
|
||||
tz = tzfile(filepath)
|
||||
break
|
||||
except (IOError, OSError, ValueError):
|
||||
pass
|
||||
else:
|
||||
tz = tzlocal()
|
||||
else:
|
||||
if name.startswith(":"):
|
||||
name = name[:-1]
|
||||
if os.path.isabs(name):
|
||||
if os.path.isfile(name):
|
||||
tz = tzfile(name)
|
||||
else:
|
||||
tz = None
|
||||
else:
|
||||
for path in TZPATHS:
|
||||
filepath = os.path.join(path, name)
|
||||
if not os.path.isfile(filepath):
|
||||
filepath = filepath.replace(' ','_')
|
||||
if not os.path.isfile(filepath):
|
||||
continue
|
||||
try:
|
||||
tz = tzfile(filepath)
|
||||
break
|
||||
except (IOError, OSError, ValueError):
|
||||
pass
|
||||
else:
|
||||
tz = None
|
||||
if tzwin:
|
||||
try:
|
||||
tz = tzwin(name)
|
||||
except OSError:
|
||||
pass
|
||||
if not tz:
|
||||
from dateutil.zoneinfo import gettz
|
||||
tz = gettz(name)
|
||||
if not tz:
|
||||
for c in name:
|
||||
# name must have at least one offset to be a tzstr
|
||||
if c in "0123456789":
|
||||
try:
|
||||
tz = tzstr(name)
|
||||
except ValueError:
|
||||
pass
|
||||
break
|
||||
else:
|
||||
if name in ("GMT", "UTC"):
|
||||
tz = tzutc()
|
||||
elif name in time.tzname:
|
||||
tz = tzlocal()
|
||||
return tz
|
||||
|
||||
# vim:ts=4:sw=4:et
|
||||
@@ -0,0 +1,180 @@
|
||||
# This code was originally contributed by Jeffrey Harris.
|
||||
import datetime
|
||||
import struct
|
||||
import _winreg
|
||||
|
||||
__author__ = "Jeffrey Harris & Gustavo Niemeyer <gustavo@niemeyer.net>"
|
||||
|
||||
__all__ = ["tzwin", "tzwinlocal"]
|
||||
|
||||
ONEWEEK = datetime.timedelta(7)
|
||||
|
||||
TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
|
||||
TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones"
|
||||
TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
|
||||
|
||||
def _settzkeyname():
|
||||
global TZKEYNAME
|
||||
handle = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
|
||||
try:
|
||||
_winreg.OpenKey(handle, TZKEYNAMENT).Close()
|
||||
TZKEYNAME = TZKEYNAMENT
|
||||
except WindowsError:
|
||||
TZKEYNAME = TZKEYNAME9X
|
||||
handle.Close()
|
||||
|
||||
_settzkeyname()
|
||||
|
||||
class tzwinbase(datetime.tzinfo):
|
||||
"""tzinfo class based on win32's timezones available in the registry."""
|
||||
|
||||
def utcoffset(self, dt):
|
||||
if self._isdst(dt):
|
||||
return datetime.timedelta(minutes=self._dstoffset)
|
||||
else:
|
||||
return datetime.timedelta(minutes=self._stdoffset)
|
||||
|
||||
def dst(self, dt):
|
||||
if self._isdst(dt):
|
||||
minutes = self._dstoffset - self._stdoffset
|
||||
return datetime.timedelta(minutes=minutes)
|
||||
else:
|
||||
return datetime.timedelta(0)
|
||||
|
||||
def tzname(self, dt):
|
||||
if self._isdst(dt):
|
||||
return self._dstname
|
||||
else:
|
||||
return self._stdname
|
||||
|
||||
def list():
|
||||
"""Return a list of all time zones known to the system."""
|
||||
handle = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
|
||||
tzkey = _winreg.OpenKey(handle, TZKEYNAME)
|
||||
result = [_winreg.EnumKey(tzkey, i)
|
||||
for i in range(_winreg.QueryInfoKey(tzkey)[0])]
|
||||
tzkey.Close()
|
||||
handle.Close()
|
||||
return result
|
||||
list = staticmethod(list)
|
||||
|
||||
def display(self):
|
||||
return self._display
|
||||
|
||||
def _isdst(self, dt):
|
||||
dston = picknthweekday(dt.year, self._dstmonth, self._dstdayofweek,
|
||||
self._dsthour, self._dstminute,
|
||||
self._dstweeknumber)
|
||||
dstoff = picknthweekday(dt.year, self._stdmonth, self._stddayofweek,
|
||||
self._stdhour, self._stdminute,
|
||||
self._stdweeknumber)
|
||||
if dston < dstoff:
|
||||
return dston <= dt.replace(tzinfo=None) < dstoff
|
||||
else:
|
||||
return not dstoff <= dt.replace(tzinfo=None) < dston
|
||||
|
||||
|
||||
class tzwin(tzwinbase):
|
||||
|
||||
def __init__(self, name):
|
||||
self._name = name
|
||||
|
||||
handle = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
|
||||
tzkey = _winreg.OpenKey(handle, "%s\%s" % (TZKEYNAME, name))
|
||||
keydict = valuestodict(tzkey)
|
||||
tzkey.Close()
|
||||
handle.Close()
|
||||
|
||||
self._stdname = keydict["Std"].encode("iso-8859-1")
|
||||
self._dstname = keydict["Dlt"].encode("iso-8859-1")
|
||||
|
||||
self._display = keydict["Display"]
|
||||
|
||||
# See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
|
||||
tup = struct.unpack("=3l16h", keydict["TZI"])
|
||||
self._stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1
|
||||
self._dstoffset = self._stdoffset-tup[2] # + DaylightBias * -1
|
||||
|
||||
(self._stdmonth,
|
||||
self._stddayofweek, # Sunday = 0
|
||||
self._stdweeknumber, # Last = 5
|
||||
self._stdhour,
|
||||
self._stdminute) = tup[4:9]
|
||||
|
||||
(self._dstmonth,
|
||||
self._dstdayofweek, # Sunday = 0
|
||||
self._dstweeknumber, # Last = 5
|
||||
self._dsthour,
|
||||
self._dstminute) = tup[12:17]
|
||||
|
||||
def __repr__(self):
|
||||
return "tzwin(%s)" % repr(self._name)
|
||||
|
||||
def __reduce__(self):
|
||||
return (self.__class__, (self._name,))
|
||||
|
||||
|
||||
class tzwinlocal(tzwinbase):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
handle = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
|
||||
|
||||
tzlocalkey = _winreg.OpenKey(handle, TZLOCALKEYNAME)
|
||||
keydict = valuestodict(tzlocalkey)
|
||||
tzlocalkey.Close()
|
||||
|
||||
self._stdname = keydict["StandardName"].encode("iso-8859-1")
|
||||
self._dstname = keydict["DaylightName"].encode("iso-8859-1")
|
||||
|
||||
try:
|
||||
tzkey = _winreg.OpenKey(handle, "%s\%s"%(TZKEYNAME, self._stdname))
|
||||
_keydict = valuestodict(tzkey)
|
||||
self._display = _keydict["Display"]
|
||||
tzkey.Close()
|
||||
except OSError:
|
||||
self._display = None
|
||||
|
||||
handle.Close()
|
||||
|
||||
self._stdoffset = -keydict["Bias"]-keydict["StandardBias"]
|
||||
self._dstoffset = self._stdoffset-keydict["DaylightBias"]
|
||||
|
||||
|
||||
# See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
|
||||
tup = struct.unpack("=8h", keydict["StandardStart"])
|
||||
|
||||
(self._stdmonth,
|
||||
self._stddayofweek, # Sunday = 0
|
||||
self._stdweeknumber, # Last = 5
|
||||
self._stdhour,
|
||||
self._stdminute) = tup[1:6]
|
||||
|
||||
tup = struct.unpack("=8h", keydict["DaylightStart"])
|
||||
|
||||
(self._dstmonth,
|
||||
self._dstdayofweek, # Sunday = 0
|
||||
self._dstweeknumber, # Last = 5
|
||||
self._dsthour,
|
||||
self._dstminute) = tup[1:6]
|
||||
|
||||
def __reduce__(self):
|
||||
return (self.__class__, ())
|
||||
|
||||
def picknthweekday(year, month, dayofweek, hour, minute, whichweek):
|
||||
"""dayofweek == 0 means Sunday, whichweek 5 means last instance"""
|
||||
first = datetime.datetime(year, month, 1, hour, minute)
|
||||
weekdayone = first.replace(day=((dayofweek-first.isoweekday())%7+1))
|
||||
for n in xrange(whichweek):
|
||||
dt = weekdayone+(whichweek-n)*ONEWEEK
|
||||
if dt.month == month:
|
||||
return dt
|
||||
|
||||
def valuestodict(key):
|
||||
"""Convert a registry key's values to a dictionary."""
|
||||
dict = {}
|
||||
size = _winreg.QueryInfoKey(key)[1]
|
||||
for i in range(size):
|
||||
data = _winreg.EnumValue(key, i)
|
||||
dict[data[0]] = data[1]
|
||||
return dict
|
||||
@@ -0,0 +1,85 @@
|
||||
"""
|
||||
Copyright (c) 2003-2005 Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||
|
||||
This module offers extensions to the standard python 2.3+
|
||||
datetime module.
|
||||
"""
|
||||
from dateutil.tz import tzfile
|
||||
from tarfile import TarFile
|
||||
import os
|
||||
|
||||
__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
|
||||
__license__ = "PSF License"
|
||||
|
||||
__all__ = ["setcachesize", "gettz", "rebuild"]
|
||||
|
||||
CACHE = {}
|
||||
|
||||
class tzfile(tzfile):
|
||||
def __reduce__(self):
|
||||
return (gettz, (self._filename,))
|
||||
|
||||
def getzoneinfofile():
|
||||
filenames = os.listdir(os.path.join(os.path.dirname(__file__)))
|
||||
filenames.sort()
|
||||
filenames.reverse()
|
||||
for entry in filenames:
|
||||
if entry.startswith("zoneinfo") and ".tar." in entry:
|
||||
return os.path.join(os.path.dirname(__file__), entry)
|
||||
return None
|
||||
|
||||
def buildcache():
|
||||
global CACHE
|
||||
zoneinfofile = getzoneinfofile()
|
||||
if zoneinfofile:
|
||||
tf = TarFile.open(zoneinfofile)
|
||||
try:
|
||||
for tarinfo in tf.getmembers():
|
||||
if tarinfo.islnk() or tarinfo.isfile():
|
||||
zonefile = tf.extractfile(tarinfo)
|
||||
CACHE[tarinfo.name] = tzfile(zonefile)
|
||||
finally:
|
||||
tf.close()
|
||||
|
||||
buildcache()
|
||||
|
||||
del getzoneinfofile
|
||||
del buildcache
|
||||
|
||||
def setcachesize(_):
|
||||
# Since the cache now eagerly initialized at
|
||||
# import time, there's no point in controlling
|
||||
# its size.
|
||||
pass
|
||||
|
||||
def gettz(name):
|
||||
return CACHE.get(name)
|
||||
|
||||
def rebuild(filename, tag=None, format="gz"):
|
||||
import tempfile, shutil
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
zonedir = os.path.join(tmpdir, "zoneinfo")
|
||||
moduledir = os.path.dirname(__file__)
|
||||
if tag: tag = "-"+tag
|
||||
targetname = "zoneinfo%s.tar.%s" % (tag, format)
|
||||
try:
|
||||
tf = TarFile.open(filename)
|
||||
for name in tf.getnames():
|
||||
if not (name.endswith(".sh") or
|
||||
name.endswith(".tab") or
|
||||
name == "leapseconds"):
|
||||
tf.extract(name, tmpdir)
|
||||
filepath = os.path.join(tmpdir, name)
|
||||
os.system("zic -d %s %s" % (zonedir, filepath))
|
||||
tf.close()
|
||||
target = os.path.join(moduledir, targetname)
|
||||
for entry in os.listdir(moduledir):
|
||||
if entry.startswith("zoneinfo") and ".tar." in entry:
|
||||
os.unlink(os.path.join(moduledir, entry))
|
||||
tf = TarFile.open(target, "w:%s" % format)
|
||||
for entry in os.listdir(zonedir):
|
||||
entrypath = os.path.join(zonedir, entry)
|
||||
tf.add(entrypath, entry)
|
||||
tf.close()
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
Binary file not shown.
@@ -1,35 +0,0 @@
|
||||
ISC License
|
||||
|
||||
Copyright (C) Dnspython Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for
|
||||
any purpose with or without fee is hereby granted, provided that the
|
||||
above copyright notice and this permission notice appear in all
|
||||
copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
|
||||
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
|
||||
|
||||
Copyright (C) 2001-2017 Nominum, Inc.
|
||||
Copyright (C) Google Inc.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software and its
|
||||
documentation for any purpose with or without fee is hereby granted,
|
||||
provided that the above copyright notice and this permission notice
|
||||
appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
@@ -1,665 +0,0 @@
|
||||
# dnspython
|
||||
|
||||
[](https://travis-ci.org/rthalley/dnspython)
|
||||
|
||||
## INTRODUCTION
|
||||
|
||||
dnspython is a DNS toolkit for Python. It supports almost all record types. It
|
||||
can be used for queries, zone transfers, and dynamic updates. It supports TSIG
|
||||
authenticated messages and EDNS0.
|
||||
|
||||
dnspython provides both high and low level access to DNS. The high level classes
|
||||
perform queries for data of a given name, type, and class, and return an answer
|
||||
set. The low level classes allow direct manipulation of DNS zones, messages,
|
||||
names, and records.
|
||||
|
||||
To see a few of the ways dnspython can be used, look in the `examples/` directory.
|
||||
|
||||
dnspython is a utility to work with DNS, `/etc/hosts` is thus not used. For
|
||||
simple forward DNS lookups, it's better to use `socket.gethostbyname()`.
|
||||
|
||||
dnspython originated at Nominum where it was developed
|
||||
to facilitate the testing of DNS software.
|
||||
|
||||
## INSTALLATION
|
||||
|
||||
* Many distributions have dnspython packaged for you, so you should
|
||||
check there first.
|
||||
* If you have pip installed, you can do `pip install dnspython`
|
||||
* If not just download the source file and unzip it, then run
|
||||
`sudo python setup.py install`
|
||||
|
||||
## ABOUT THIS RELEASE
|
||||
|
||||
This is dnspython 1.16.0
|
||||
|
||||
### Notices
|
||||
|
||||
Python 2.x support ends with the release of 1.16.0, unless there are
|
||||
critical bugs in 1.16.0. Future versions of dnspython will only
|
||||
support Python 3.
|
||||
|
||||
Version numbering of future dnspython releases will also start at 2.0, as
|
||||
incompatible changes will be permitted. We're not planning huge changes at
|
||||
this time, but we'd like to do a better job at IDNA, and there are other
|
||||
API improvements to be made.
|
||||
|
||||
The ChangeLog has been discontinued. Please see the git history for detailed
|
||||
change information.
|
||||
|
||||
### New since 1.15.0:
|
||||
|
||||
* Much of the internals of dns.query.udp() and dns.query.tcp() have
|
||||
been factored out into dns.query.send_udp(),
|
||||
dns.query.receive_udp(), dns.query.send_tcp(), and
|
||||
dns.query.receive_tcp(). Applications which want more control over
|
||||
the socket may find the new routines helpful; for example it would
|
||||
be easy to send multiple queries over a single TCP connection.
|
||||
|
||||
* The OPENPGPKEY RR, and the CHAOS class A RR are now supported.
|
||||
|
||||
* EDNS0 client-subnet is supported.
|
||||
|
||||
* dns.resover.query() now has a lifetime timeout optional parameter.
|
||||
|
||||
* pycryptodome and pycryptodomex are now supported and recommended for use
|
||||
instead of pycrypto.
|
||||
|
||||
* dns.message.from_wire() now has an ignore_trailing option.
|
||||
|
||||
* type signatures have been provided.
|
||||
|
||||
* module dns.hash is now deprecated, use standard Python libraries instead.
|
||||
|
||||
* setup.py supports Cythonization to improve performance.
|
||||
|
||||
### Bugs fixed since 1.15.0:
|
||||
|
||||
* DNSSEC signature validation didn't check names correctly. [Issue #295]
|
||||
|
||||
* The NXDOMAIN exception should not use its docstring. [Issue #253]
|
||||
|
||||
* Fixed regression where trailing zeros in APL RRs were not
|
||||
suppressed, and then fixed the problem where trailing zeros
|
||||
were not added back properly on python 3 when needed.
|
||||
|
||||
* Masterfile TTL defaulting is now harmonized with BIND practice.
|
||||
|
||||
* dns.query.xfr() now raises on a non-zero rcode.
|
||||
|
||||
* Rdata module importing is now locked to avoid races.
|
||||
|
||||
* Several Python 3 incompatibilities have been fixed.
|
||||
|
||||
* NSEC3 bitmap parsing now works with mulitple NSEC3 windows.
|
||||
|
||||
* dns.renderer.Render supports TSIG on DNS envelope sequences.
|
||||
|
||||
* DNSSEC validation now checks names properly [Issue #295]
|
||||
|
||||
### New since 1.14.0:
|
||||
|
||||
* IDNA 2008 support is now available if the "idna" module has been
|
||||
installed and IDNA 2008 is requested. The default IDNA behavior is
|
||||
still IDNA 2003. The new IDNA codec mechanism is currently only
|
||||
useful for direct calls to dns.name.from_text() or
|
||||
dns.name.from_unicode(), but in future releases it will be deployed
|
||||
throughout dnspython, e.g. so that you can read a masterfile with an
|
||||
IDNA 2008 codec in force.
|
||||
|
||||
* By default, dns.name.to_unicode() is not strict about which
|
||||
version of IDNA the input complies with. Strictness can be
|
||||
requested by using one of the strict IDNA codecs.
|
||||
|
||||
* The AVC RR is now supported.
|
||||
|
||||
### Bugs fixed since 1.14.0:
|
||||
|
||||
* Some problems with newlines in various output modes have been
|
||||
addressed.
|
||||
|
||||
* dns.name.to_text() now returns text and not bytes on Python 3.x
|
||||
|
||||
* Miscellaneous fixes for the Python 2/3 codeline merge.
|
||||
|
||||
* Many "lint" fixes after the addition of pylint support.
|
||||
|
||||
* The random number generator reseeds after a fork().
|
||||
|
||||
|
||||
## REQUIREMENTS
|
||||
|
||||
Python 2.7 or 3.4+.
|
||||
|
||||
|
||||
## HOME PAGE
|
||||
|
||||
For the latest in releases, documentation, and information, visit the dnspython
|
||||
home page at http://www.dnspython.org/
|
||||
|
||||
|
||||
## BUG REPORTS
|
||||
|
||||
Bug reports may be opened at
|
||||
https://github.com/rthalley/dnspython/issues or sent to
|
||||
bugs@dnspython.org
|
||||
|
||||
|
||||
## MAILING LISTS
|
||||
|
||||
A number of mailing lists are available. Visit the dnspython home page to
|
||||
subscribe or unsubscribe.
|
||||
|
||||
|
||||
## PRIOR RELEASE INFORMATION
|
||||
|
||||
### New since 1.13.0:
|
||||
|
||||
* CSYNC RRs are now supported.
|
||||
|
||||
* `dns/message.py` (`make_query`): Setting any value which implies EDNS will
|
||||
turn on EDNS if `use_edns` has not been specified.
|
||||
|
||||
### Bugs fixed since 1.13.0:
|
||||
|
||||
* TSIG signature algorithm setting was broken by the Python 2 and Python 3 code
|
||||
line merge.
|
||||
|
||||
* A bug in the LOC RR destroyed N/S and E/W distinctions within a degree of the
|
||||
equator or prime merdian respectively.
|
||||
|
||||
* Misc. fixes to deal with fallout from the Python 2 & 3 merge.
|
||||
Fixes #156, #157, #158, #159, #160.
|
||||
|
||||
* Running with python optimization on caused issues when stripped docstrings
|
||||
were referenced. Fixes #154
|
||||
|
||||
* `dns.zone.from_text()` erroneously required the zone to be provided.
|
||||
Fixes #153
|
||||
|
||||
### New since 1.12.0:
|
||||
|
||||
* Dnspython now uses a single source for Python 2 and Python 3, eliminating the
|
||||
painful merging between the Python 2 and Python 3 branches. Thank you so much
|
||||
to Arthur Gautier for taking on this challenge and making it work! It was a
|
||||
big job!
|
||||
|
||||
* Support for Python older than 2.6 dropped.
|
||||
|
||||
* Support for Python older than 3.3 dropped.
|
||||
|
||||
* Zone origin can be specified as a string.
|
||||
|
||||
* A rich string representation for all DNSExceptions.
|
||||
|
||||
* setuptools has replaced distutils
|
||||
|
||||
* Added support for CAA, CDS, CDNSKEY, EUI48, EUI64, and URI RR types.
|
||||
|
||||
* Names now support the pickle protocol.
|
||||
|
||||
* Ports can be specified per-nameserver in the stub resolver.
|
||||
|
||||
### Bugs fixed since 1.12.0:
|
||||
|
||||
* A number of Unicode name bugs have been fixed.
|
||||
|
||||
* `resolv.conf` processing now rejects lines with too few tokens.
|
||||
|
||||
* NameDicts now keep the max-depth value correct, and update properly.
|
||||
|
||||
### New since 1.11.1:
|
||||
|
||||
* Added `dns.zone.to_text()`.
|
||||
|
||||
* Added support for "options rotate" in `/etc/resolv.conf`.
|
||||
|
||||
* `dns.rdtypes.ANY.DNSKEY` now has helpers functions to convert between the
|
||||
numeric form of the flags and a set of human-friendly strings
|
||||
|
||||
* The reverse name of an IPv6 mapped IPv4 address is now in the IPv4 reverse
|
||||
namespace.
|
||||
|
||||
* The test system can now run the tests without requiring dnspython to be
|
||||
installed.
|
||||
|
||||
* Preliminary Elliptic Curve DNSSEC Validation (requires ecdsa module)
|
||||
|
||||
### Bugs fixed since 1.11.1:
|
||||
|
||||
* dnspython raised an exception when reading a masterfile starting with leading
|
||||
whitespace
|
||||
|
||||
* dnspython was affected by a python slicing API bug present on 64-bit windows.
|
||||
|
||||
* Unicode escaping was applied at the wrong time.
|
||||
|
||||
* RRSIG `to_text()` did not respect the relativize setting.
|
||||
|
||||
* APL RRs with zero rdlength were rejected.
|
||||
|
||||
* The tokenizer could put back an unescaped token.
|
||||
|
||||
* Making a response to a message signed with TSIG was broken.
|
||||
|
||||
* The IXFR state machine didn't handle long IXFR diffs.
|
||||
|
||||
### New since 1.11.0:
|
||||
|
||||
* Nothing
|
||||
|
||||
### Bugs fixed since 1.11.0:
|
||||
|
||||
* `dns.resolver.Resolver` erroneously referred to `retry_servfail`
|
||||
instead of `self.retry_servfail`.
|
||||
|
||||
* `dns.tsigkeyring.to_text()` would fail trying to convert the keyname to text.
|
||||
|
||||
* Multi-message TSIGs were broken for algorithms other than HMAC-MD5 because we
|
||||
weren't passing the right digest module to the HMAC code.
|
||||
|
||||
* `dns.dnssec._find_candidate_keys()` tried to extract the key from the wrong
|
||||
variable name.
|
||||
|
||||
* $GENERATE tests were not backward compatible with python 2.4.
|
||||
|
||||
### New since 1.10.0:
|
||||
|
||||
* $GENERATE support
|
||||
|
||||
* TLSA RR support
|
||||
|
||||
* Added set_flags() method to dns.resolver.Resolver
|
||||
|
||||
### Bugs fixed since 1.10.0:
|
||||
|
||||
* Names with offsets >= 2^14 are no longer added to the compression table.
|
||||
|
||||
* The "::" syntax is not used to shorten a single 16-bit section of the text
|
||||
form an IPv6 address.
|
||||
|
||||
* Caches are now locked.
|
||||
|
||||
* YXDOMAIN is raised if seen by the resolver.
|
||||
|
||||
* Empty rdatasets are not printed.
|
||||
|
||||
* DNSKEY key tags are no longer assumed to be unique.
|
||||
|
||||
### New since 1.9.4:
|
||||
|
||||
* Added dns.resolver.LRUCache. In this cache implementation, the cache size is
|
||||
limited to a user-specified number of nodes, and when adding a new node to a
|
||||
full cache the least-recently used node is removed. If you're crawling the web
|
||||
or otherwise doing lots of resolutions and you are using a cache, switching
|
||||
to the LRUCache is recommended.
|
||||
|
||||
* `dns.resolver.query()` will try TCP if a UDP response is truncated.
|
||||
|
||||
* The python socket module's DNS methods can be now be overridden with
|
||||
implementations that use dnspython's resolver.
|
||||
|
||||
* Old DNSSEC types KEY, NXT, and SIG have been removed.
|
||||
|
||||
* Whitespace is allowed in SSHFP fingerprints.
|
||||
|
||||
* Origin checking in `dns.zone.from_xfr()` can be disabled.
|
||||
|
||||
* Trailing junk checking can be disabled.
|
||||
|
||||
* A source port can be specified when creating a resolver query.
|
||||
|
||||
* All EDNS values may now be specified to `dns.message.make_query()`.
|
||||
|
||||
### Bugs fixed since 1.9.4:
|
||||
|
||||
* IPv4 and IPv6 address processing is now stricter.
|
||||
|
||||
* Bounds checking of slices in rdata wire processing is now more strict, and
|
||||
bounds errors (e.g. we got less data than was expected) now raise
|
||||
`dns.exception.FormError` rather than `IndexError`.
|
||||
|
||||
* Specifying a source port without specifying source used to have no effect, but
|
||||
now uses the wildcard address and the specified port.
|
||||
|
||||
### New since 1.9.3:
|
||||
|
||||
* Nothing.
|
||||
|
||||
### Bugs fixed since 1.9.3:
|
||||
|
||||
* The rdata `_wire_cmp()` routine now handles relative names.
|
||||
|
||||
* The SIG RR implementation was missing `import struct`.
|
||||
|
||||
### New since 1.9.2:
|
||||
|
||||
* A boolean parameter, `raise_on_no_answer`, has been added to the `query()`
|
||||
methods. In no-error, no-data situations, this parameter determines whether
|
||||
`NoAnswer` should be raised or not. If True, `NoAnswer` is raised. If False,
|
||||
then an `Answer()` object with a None rrset will be returned.
|
||||
|
||||
* Resolver `Answer()` objects now have a canonical_name field.
|
||||
|
||||
* Rdata now has a `__hash__` method.
|
||||
|
||||
### Bugs fixed since 1.9.2:
|
||||
|
||||
* Dnspython was erroneously doing case-insensitive comparisons of the names in
|
||||
NSEC and RRSIG RRs.
|
||||
|
||||
* We now use `is` and not `==` when testing what section an RR is in.
|
||||
|
||||
* The resolver now disallows metaqueries.
|
||||
|
||||
### New since 1.9.1:
|
||||
|
||||
* Nothing.
|
||||
|
||||
### Bugs fixed since 1.9.1:
|
||||
|
||||
* The `dns.dnssec` module didn't work at all due to missing imports that escaped
|
||||
detection in testing because the test suite also did the imports. The third
|
||||
time is the charm!
|
||||
|
||||
### New since 1.9.0:
|
||||
|
||||
* Nothing.
|
||||
|
||||
### Bugs fixed since 1.9.0:
|
||||
|
||||
* The `dns.dnssec` module didn't work with DSA due to namespace contamination
|
||||
from a "from"-style import.
|
||||
|
||||
### New since 1.8.0:
|
||||
|
||||
* dnspython now uses `poll()` instead of `select()` when available.
|
||||
|
||||
* Basic DNSSEC validation can be done using `dns.dnsec.validate()` and
|
||||
`dns.dnssec.validate_rrsig()` if you have PyCrypto 2.3 or later installed.
|
||||
Complete secure resolution is not yet available.
|
||||
|
||||
* Added `key_id()` to the DNSSEC module, which computes the DNSSEC key id of a
|
||||
DNSKEY rdata.
|
||||
|
||||
* Added `make_ds()` to the DNSSEC module, which returns the DS RR for a given
|
||||
DNSKEY rdata.
|
||||
|
||||
* dnspython now raises an exception if HMAC-SHA284 or HMAC-SHA512 are used with
|
||||
a Python older than 2.5.2. (Older Pythons do not compute the correct value.)
|
||||
|
||||
* Symbolic constants are now available for TSIG algorithm names.
|
||||
|
||||
### Bugs fixed since 1.8.0
|
||||
|
||||
* `dns.resolver.zone_for_name()` didn't handle a query response with a CNAME or
|
||||
DNAME correctly in some cases.
|
||||
|
||||
* When specifying rdata types and classes as text, Unicode strings may now be
|
||||
used.
|
||||
|
||||
* Hashlib compatibility issues have been fixed.
|
||||
|
||||
* `dns.message` now imports `dns.edns`.
|
||||
|
||||
* The TSIG algorithm value was passed incorrectly to `use_tsig()` in some cases.
|
||||
|
||||
### New since 1.7.1:
|
||||
|
||||
* Support for hmac-sha1, hmac-sha224, hmac-sha256, hmac-sha384 and hmac-sha512
|
||||
has been contributed by Kevin Chen.
|
||||
|
||||
* The tokenizer's tokens are now Token objects instead of (type, value) tuples.
|
||||
|
||||
### Bugs fixed since 1.7.1:
|
||||
|
||||
* Escapes in masterfiles now work correctly. Previously they were only working
|
||||
correctly when the text involved was part of a domain name.
|
||||
|
||||
* When constructing a DDNS update, if the `present()` method was used with a
|
||||
single rdata, a zero TTL was not added.
|
||||
|
||||
* The entropy pool needed locking to be thread safe.
|
||||
|
||||
* The entropy pool's reading of `/dev/random` could cause dnspython to block.
|
||||
|
||||
* The entropy pool did buffered reads, potentially consuming more randomness
|
||||
than we needed.
|
||||
|
||||
* The entropy pool did not seed with high quality randomness on Windows.
|
||||
|
||||
* SRV records were compared incorrectly.
|
||||
|
||||
* In the e164 query function, the resolver parameter was not used.
|
||||
|
||||
### New since 1.7.0:
|
||||
|
||||
* Nothing
|
||||
|
||||
### Bugs fixed since 1.7.0:
|
||||
|
||||
* The 1.7.0 kitting process inadvertently omitted the code for the DLV RR.
|
||||
|
||||
* Negative DDNS prerequisites are now handled correctly.
|
||||
|
||||
### New since 1.6.0:
|
||||
|
||||
* Rdatas now have a `to_digestable()` method, which returns the DNSSEC canonical
|
||||
form of the rdata, suitable for use in signature computations.
|
||||
|
||||
* The NSEC3, NSEC3PARAM, DLV, and HIP RR types are now supported.
|
||||
|
||||
* An entropy module has been added and is used to randomize query ids.
|
||||
|
||||
* EDNS0 options are now supported.
|
||||
|
||||
* UDP IXFR is now supported.
|
||||
|
||||
* The wire format parser now has a `one_rr_per_rrset` mode, which suppresses the
|
||||
usual coalescing of all RRs of a given type into a single RRset.
|
||||
|
||||
* Various helpful DNSSEC-related constants are now defined.
|
||||
|
||||
* The resolver's `query()` method now has an optional `source` parameter,
|
||||
allowing the source IP address to be specified.
|
||||
|
||||
### Bugs fixed since 1.6.0:
|
||||
|
||||
* On Windows, the resolver set the domain incorrectly.
|
||||
|
||||
* DS RR parsing only allowed one Base64 chunk.
|
||||
|
||||
* TSIG validation didn't always use absolute names.
|
||||
|
||||
* `NSEC.to_text()` only printed the last window.
|
||||
|
||||
* We did not canonicalize IPv6 addresses before comparing them; we
|
||||
would thus treat equivalent but different textual forms, e.g.
|
||||
"1:00::1" and "1::1" as being non-equivalent.
|
||||
|
||||
* If the peer set a TSIG error, we didn't raise an exception.
|
||||
|
||||
* Some EDNS bugs in the message code have been fixed (see the ChangeLog
|
||||
for details).
|
||||
|
||||
### New since 1.5.0:
|
||||
|
||||
* Added dns.inet.is_multicast().
|
||||
|
||||
### Bugs fixed since 1.5.0:
|
||||
|
||||
* If `select()` raises an exception due to EINTR, we should just `select()`
|
||||
again.
|
||||
|
||||
* If the queried address is a multicast address, then don't check that the
|
||||
address of the response is the same as the address queried.
|
||||
|
||||
* NAPTR comparisons didn't compare the preference field due to a typo.
|
||||
|
||||
* Testing of whether a Windows NIC is enabled now works on Vista thanks to code
|
||||
contributed by Paul Marks.
|
||||
|
||||
### New since 1.4.0:
|
||||
|
||||
* Answer objects now support more of the python sequence protocol, forwarding
|
||||
the requests to the answer rrset. E.g. `for a in answer` is equivalent to
|
||||
`for a in answer.rrset`, `answer[i]` is equivalent to `answer.rrset[i]`, and
|
||||
`answer[i:j]` is equivalent to `answer.rrset[i:j]`.
|
||||
|
||||
* Making requests using EDNS, including indicating DNSSEC awareness,
|
||||
is now easier. For example, you can now say:
|
||||
`q = dns.message.make_query('www.dnspython.org', 'MX', want_dnssec=True)`
|
||||
|
||||
* `dns.query.xfr()` can now be used for IXFR.
|
||||
|
||||
* Support has been added for the DHCID, IPSECKEY, and SPF RR types.
|
||||
|
||||
* UDP messages from unexpected sources can now be ignored by setting
|
||||
`ignore_unexpected` to True when calling `dns.query.udp`.
|
||||
|
||||
### Bugs fixed since 1.4.0:
|
||||
|
||||
* If `/etc/resolv.conf` didn't exist, we raised an exception instead of simply
|
||||
using the default resolver configuration.
|
||||
|
||||
* In `dns.resolver.Resolver._config_win32_fromkey()`, we were passing the wrong
|
||||
variable to `self._config_win32_search()`.
|
||||
|
||||
### New since 1.3.5:
|
||||
|
||||
* You can now convert E.164 numbers to/from their ENUM name forms:
|
||||
```python
|
||||
>>> import dns.e164
|
||||
>>> n = dns.e164.from_e164("+1 555 1212")
|
||||
>>> n
|
||||
<DNS name 2.1.2.1.5.5.5.1.e164.arpa.>
|
||||
>>> dns.e164.to_e164(n)
|
||||
'+15551212'
|
||||
```
|
||||
|
||||
* You can now convert IPv4 and IPv6 address to/from their corresponding DNS
|
||||
reverse map names:
|
||||
```python
|
||||
>>> import dns.reversename
|
||||
>>> n = dns.reversename.from_address("127.0.0.1")
|
||||
>>> n
|
||||
<DNS name 1.0.0.127.in-addr.arpa.>
|
||||
>>> dns.reversename.to_address(n)
|
||||
'127.0.0.1'
|
||||
```
|
||||
|
||||
* You can now convert between Unicode strings and their IDN ACE form:
|
||||
```python
|
||||
>>> n = dns.name.from_text(u'les-\u00e9l\u00e8ves.example.')
|
||||
>>> n
|
||||
<DNS name xn--les-lves-50ai.example.>
|
||||
>>> n.to_unicode()
|
||||
u'les-\xe9l\xe8ves.example.'
|
||||
```
|
||||
|
||||
* The origin parameter to `dns.zone.from_text()` and `dns.zone.to_text()` is now
|
||||
optional. If not specified, the origin will be taken from the first $ORIGIN
|
||||
statement in the master file.
|
||||
|
||||
* Sanity checking of a zone can be disabled; this is useful when working with
|
||||
files which are zone fragments.
|
||||
|
||||
### Bugs fixed since 1.3.5:
|
||||
|
||||
* The correct delimiter was not used when retrieving the list of nameservers
|
||||
from the registry in certain versions of windows.
|
||||
|
||||
* The floating-point version of latitude and longitude in LOC RRs
|
||||
(`float_latitude` and `float_longitude`) had incorrect signs for south
|
||||
latitudes and west longitudes.
|
||||
|
||||
* BIND 8 TTL syntax is now accepted in all TTL-like places (i.e. SOA fields
|
||||
refresh, retry, expire, and minimum; SIG/RRSIG field original_ttl).
|
||||
|
||||
* TTLs are now bounds checked when their text form is parsed, and their values
|
||||
must be in the closed interval `[0, 2^31 - 1]`.
|
||||
|
||||
### New since 1.3.4:
|
||||
|
||||
* In the resolver, if time goes backward a little bit, ignore it.
|
||||
|
||||
* `zone_for_name()` has been added to the resolver module. It returns the zone
|
||||
which is authoritative for the specified name, which is handy for dynamic
|
||||
update. E.g.
|
||||
|
||||
import dns.resolver
|
||||
print dns.resolver.zone_for_name('www.dnspython.org')
|
||||
|
||||
will output `"dnspython.org."` and
|
||||
`print dns.resolver.zone_for_name('a.b.c.d.e.f.example.')`
|
||||
will output `"."`.
|
||||
|
||||
* The default resolver can be fetched with the `get_default_resolver()` method.
|
||||
|
||||
* You can now get the parent (immediate superdomain) of a name by using the
|
||||
`parent()` method.
|
||||
|
||||
* `Zone.iterate_rdatasets()` and `Zone.iterate_rdatas()` now have a default
|
||||
rdtype of `dns.rdatatype.ANY` like the documentation says.
|
||||
|
||||
* A Dynamic DNS example, ddns.py, has been added.
|
||||
|
||||
### New since 1.3.3:
|
||||
|
||||
* The source address and port may now be specified when calling
|
||||
`dns.query.{udp,tcp,xfr}`.
|
||||
|
||||
* The resolver now does exponential backoff each time it runs through all of the
|
||||
nameservers.
|
||||
|
||||
* Rcodes which indicate a nameserver is likely to be a "permanent failure" for a
|
||||
query cause the nameserver to be removed from the mix for that query.
|
||||
|
||||
### New since 1.3.2:
|
||||
|
||||
* `dns.message.Message.find_rrset()` now uses an index, vastly improving the
|
||||
`from_wire()` performance of large messages such as zone transfers.
|
||||
|
||||
* Added `dns.message.make_response()`, which creates a skeletal response for the
|
||||
specified query.
|
||||
|
||||
* Added `opcode()` and `set_opcode()` convenience methods to the
|
||||
`dns.message.Message` class. Added the `request_payload` attribute to the
|
||||
Message class.
|
||||
|
||||
* The `file` parameter of `dns.name.Name.to_wire()` is now optional; if omitted,
|
||||
the wire form will be returned as the value of the function.
|
||||
|
||||
* `dns.zone.from_xfr()` in relativization mode incorrectly set `zone.origin` to
|
||||
the empty name.
|
||||
|
||||
* The masterfile parser incorrectly rejected TXT records where a value was not
|
||||
quoted.
|
||||
|
||||
### New since 1.3.1:
|
||||
|
||||
* The NSEC format doesn't allow specifying types by number, so we shouldn't
|
||||
either. (Using the unknown type format is still OK though.)
|
||||
|
||||
* The resolver wasn't catching `dns.exception.Timeout`, so a timeout erroneously
|
||||
caused the whole resolution to fail instead of just going on to the next
|
||||
server.
|
||||
|
||||
* The renderer module didn't import random, causing an exception to be raised if
|
||||
a query id wasn't provided when a Renderer was created.
|
||||
|
||||
* The conversion of LOC milliseconds values from text to binary was incorrect if
|
||||
the length of the milliseconds string was not 3.
|
||||
|
||||
### New since 1.3.0:
|
||||
|
||||
* Added support for the SSHFP type.
|
||||
|
||||
### New since 1.2.0:
|
||||
|
||||
* Added support for new DNSSEC types RRSIG, NSEC, and DNSKEY.
|
||||
|
||||
* This release fixes all known bugs.
|
||||
|
||||
* See the ChangeLog file for more detailed information on changes since the
|
||||
prior release.
|
||||
@@ -1,59 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2003-2007, 2009, 2011 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
# PROJECT URL
|
||||
# https://github.com/rthalley/dnspython
|
||||
|
||||
"""dnspython DNS toolkit"""
|
||||
|
||||
__all__ = [
|
||||
'dnssec',
|
||||
'e164',
|
||||
'edns',
|
||||
'entropy',
|
||||
'exception',
|
||||
'flags',
|
||||
'hash',
|
||||
'inet',
|
||||
'ipv4',
|
||||
'ipv6',
|
||||
'message',
|
||||
'name',
|
||||
'namedict',
|
||||
'node',
|
||||
'opcode',
|
||||
'query',
|
||||
'rcode',
|
||||
'rdata',
|
||||
'rdataclass',
|
||||
'rdataset',
|
||||
'rdatatype',
|
||||
'renderer',
|
||||
'resolver',
|
||||
'reversename',
|
||||
'rrset',
|
||||
'set',
|
||||
'tokenizer',
|
||||
'tsig',
|
||||
'tsigkeyring',
|
||||
'ttl',
|
||||
'rdtypes',
|
||||
'update',
|
||||
'version',
|
||||
'wiredata',
|
||||
'zone',
|
||||
]
|
||||
@@ -1,59 +0,0 @@
|
||||
import sys
|
||||
import decimal
|
||||
from decimal import Context
|
||||
|
||||
PY3 = sys.version_info[0] == 3
|
||||
PY2 = sys.version_info[0] == 2
|
||||
|
||||
|
||||
if PY3:
|
||||
long = int
|
||||
xrange = range
|
||||
else:
|
||||
long = long # pylint: disable=long-builtin
|
||||
xrange = xrange # pylint: disable=xrange-builtin
|
||||
|
||||
# unicode / binary types
|
||||
if PY3:
|
||||
text_type = str
|
||||
binary_type = bytes
|
||||
string_types = (str,)
|
||||
unichr = chr
|
||||
def maybe_decode(x):
|
||||
return x.decode()
|
||||
def maybe_encode(x):
|
||||
return x.encode()
|
||||
def maybe_chr(x):
|
||||
return x
|
||||
def maybe_ord(x):
|
||||
return x
|
||||
else:
|
||||
text_type = unicode # pylint: disable=unicode-builtin, undefined-variable
|
||||
binary_type = str
|
||||
string_types = (
|
||||
basestring, # pylint: disable=basestring-builtin, undefined-variable
|
||||
)
|
||||
unichr = unichr # pylint: disable=unichr-builtin
|
||||
def maybe_decode(x):
|
||||
return x
|
||||
def maybe_encode(x):
|
||||
return x
|
||||
def maybe_chr(x):
|
||||
return chr(x)
|
||||
def maybe_ord(x):
|
||||
return ord(x)
|
||||
|
||||
|
||||
def round_py2_compat(what):
|
||||
"""
|
||||
Python 2 and Python 3 use different rounding strategies in round(). This
|
||||
function ensures that results are python2/3 compatible and backward
|
||||
compatible with previous py2 releases
|
||||
:param what: float
|
||||
:return: rounded long
|
||||
"""
|
||||
d = Context(
|
||||
prec=len(str(long(what))), # round to integer with max precision
|
||||
rounding=decimal.ROUND_HALF_UP
|
||||
).create_decimal(str(what)) # str(): python 2.6 compat
|
||||
return long(d)
|
||||
@@ -1,519 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2003-2017 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
"""Common DNSSEC-related functions and constants."""
|
||||
|
||||
from io import BytesIO
|
||||
import struct
|
||||
import time
|
||||
|
||||
import dns.exception
|
||||
import dns.name
|
||||
import dns.node
|
||||
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
|
||||
RSAMD5 = 1
|
||||
#: DH
|
||||
DH = 2
|
||||
#: DSA
|
||||
DSA = 3
|
||||
#: ECC
|
||||
ECC = 4
|
||||
#: RSASHA1
|
||||
RSASHA1 = 5
|
||||
#: DSANSEC3SHA1
|
||||
DSANSEC3SHA1 = 6
|
||||
#: RSASHA1NSEC3SHA1
|
||||
RSASHA1NSEC3SHA1 = 7
|
||||
#: RSASHA256
|
||||
RSASHA256 = 8
|
||||
#: RSASHA512
|
||||
RSASHA512 = 10
|
||||
#: ECDSAP256SHA256
|
||||
ECDSAP256SHA256 = 13
|
||||
#: ECDSAP384SHA384
|
||||
ECDSAP384SHA384 = 14
|
||||
#: INDIRECT
|
||||
INDIRECT = 252
|
||||
#: PRIVATEDNS
|
||||
PRIVATEDNS = 253
|
||||
#: PRIVATEOID
|
||||
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,
|
||||
}
|
||||
|
||||
# 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 = {y: x for x, y in _algorithm_by_text.items()}
|
||||
|
||||
|
||||
def algorithm_from_text(text):
|
||||
"""Convert text into a DNSSEC algorithm value.
|
||||
|
||||
Returns an ``int``.
|
||||
"""
|
||||
|
||||
value = _algorithm_by_text.get(text.upper())
|
||||
if value is None:
|
||||
value = int(text)
|
||||
return value
|
||||
|
||||
|
||||
def algorithm_to_text(value):
|
||||
"""Convert a DNSSEC algorithm value to text
|
||||
|
||||
Returns a ``str``.
|
||||
"""
|
||||
|
||||
text = _algorithm_by_value.get(value)
|
||||
if text is None:
|
||||
text = str(value)
|
||||
return text
|
||||
|
||||
|
||||
def _to_rdata(record, origin):
|
||||
s = BytesIO()
|
||||
record.to_wire(s, origin=origin)
|
||||
return s.getvalue()
|
||||
|
||||
|
||||
def key_id(key, origin=None):
|
||||
"""Return the key id (a 16-bit number) for the specified key.
|
||||
|
||||
Note the *origin* parameter of this function is historical and
|
||||
is not needed.
|
||||
|
||||
Returns an ``int`` between 0 and 65535.
|
||||
"""
|
||||
|
||||
rdata = _to_rdata(key, origin)
|
||||
rdata = bytearray(rdata)
|
||||
if key.algorithm == RSAMD5:
|
||||
return (rdata[-3] << 8) + rdata[-2]
|
||||
else:
|
||||
total = 0
|
||||
for i in range(len(rdata) // 2):
|
||||
total += (rdata[2 * i] << 8) + \
|
||||
rdata[2 * i + 1]
|
||||
if len(rdata) % 2 != 0:
|
||||
total += rdata[len(rdata) - 1] << 8
|
||||
total += ((total >> 16) & 0xffff)
|
||||
return total & 0xffff
|
||||
|
||||
|
||||
def make_ds(name, key, algorithm, origin=None):
|
||||
"""Create a DS record for a DNSSEC key.
|
||||
|
||||
*name* is the owner name of the DS record.
|
||||
|
||||
*key* is a ``dns.rdtypes.ANY.DNSKEY``.
|
||||
|
||||
*algorithm* is a string describing which hash algorithm to use. The
|
||||
currently supported hashes are "SHA1" and "SHA256". Case does not
|
||||
matter for these strings.
|
||||
|
||||
*origin* is a ``dns.name.Name`` and will be used as the origin
|
||||
if *key* is a relative name.
|
||||
|
||||
Returns a ``dns.rdtypes.ANY.DS``.
|
||||
"""
|
||||
|
||||
if algorithm.upper() == 'SHA1':
|
||||
dsalg = 1
|
||||
hash = SHA1.new()
|
||||
elif algorithm.upper() == 'SHA256':
|
||||
dsalg = 2
|
||||
hash = SHA256.new()
|
||||
else:
|
||||
raise UnsupportedAlgorithm('unsupported algorithm "%s"' % algorithm)
|
||||
|
||||
if isinstance(name, string_types):
|
||||
name = dns.name.from_text(name, origin)
|
||||
hash.update(name.canonicalize().to_wire())
|
||||
hash.update(_to_rdata(key, origin))
|
||||
digest = hash.digest()
|
||||
|
||||
dsrdata = struct.pack("!HBB", key_id(key), key.algorithm, dsalg) + digest
|
||||
return dns.rdata.from_wire(dns.rdataclass.IN, dns.rdatatype.DS, dsrdata, 0,
|
||||
len(dsrdata))
|
||||
|
||||
|
||||
def _find_candidate_keys(keys, rrsig):
|
||||
candidate_keys = []
|
||||
value = keys.get(rrsig.signer)
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, dns.node.Node):
|
||||
try:
|
||||
rdataset = value.find_rdataset(dns.rdataclass.IN,
|
||||
dns.rdatatype.DNSKEY)
|
||||
except KeyError:
|
||||
return None
|
||||
else:
|
||||
rdataset = value
|
||||
for rdata in rdataset:
|
||||
if rdata.algorithm == rrsig.algorithm and \
|
||||
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 MD5.new()
|
||||
if _is_sha1(algorithm):
|
||||
return SHA1.new()
|
||||
if _is_sha256(algorithm):
|
||||
return SHA256.new()
|
||||
if _is_sha384(algorithm):
|
||||
return SHA384.new()
|
||||
if _is_sha512(algorithm):
|
||||
return SHA512.new()
|
||||
raise ValidationFailure('unknown hash for algorithm %u' % algorithm)
|
||||
|
||||
|
||||
def _make_algorithm_id(algorithm):
|
||||
if _is_md5(algorithm):
|
||||
oid = [0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05]
|
||||
elif _is_sha1(algorithm):
|
||||
oid = [0x2b, 0x0e, 0x03, 0x02, 0x1a]
|
||||
elif _is_sha256(algorithm):
|
||||
oid = [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01]
|
||||
elif _is_sha512(algorithm):
|
||||
oid = [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03]
|
||||
else:
|
||||
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 struct.pack('!%dB' % len(idbytes), *idbytes)
|
||||
|
||||
|
||||
def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
|
||||
"""Validate an RRset against a single signature rdata
|
||||
|
||||
The owner name of *rrsig* is assumed to be the same as the owner name
|
||||
of *rrset*.
|
||||
|
||||
*rrset* is the RRset to validate. It can be a ``dns.rrset.RRset`` or
|
||||
a ``(dns.name.Name, dns.rdataset.Rdataset)`` tuple.
|
||||
|
||||
*rrsig* is a ``dns.rdata.Rdata``, the signature to validate.
|
||||
|
||||
*keys* is the key dictionary, used to find the DNSKEY associated with
|
||||
a given name. The dictionary is keyed by a ``dns.name.Name``, and has
|
||||
``dns.node.Node`` or ``dns.rdataset.Rdataset`` values.
|
||||
|
||||
*origin* is a ``dns.name.Name``, the origin to use for relative names.
|
||||
|
||||
*now* is an ``int``, the time to use when validating the signatures,
|
||||
in seconds since the UNIX epoch. The default is the current time.
|
||||
"""
|
||||
|
||||
if isinstance(origin, string_types):
|
||||
origin = dns.name.from_text(origin, dns.name.root)
|
||||
|
||||
candidate_keys = _find_candidate_keys(keys, rrsig)
|
||||
if candidate_keys is None:
|
||||
raise ValidationFailure('unknown key')
|
||||
|
||||
for candidate_key in candidate_keys:
|
||||
# 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]
|
||||
else:
|
||||
rrname = rrset.name
|
||||
rdataset = rrset
|
||||
|
||||
if now is None:
|
||||
now = time.time()
|
||||
if rrsig.expiration < now:
|
||||
raise ValidationFailure('expired')
|
||||
if rrsig.inception > now:
|
||||
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])
|
||||
keyptr = keyptr[1:]
|
||||
if bytes_ == 0:
|
||||
(bytes_,) = struct.unpack('!H', keyptr[0:2])
|
||||
keyptr = keyptr[2:]
|
||||
rsa_e = keyptr[0:bytes_]
|
||||
rsa_n = keyptr[bytes_:]
|
||||
try:
|
||||
pubkey = CryptoRSA.construct(
|
||||
(number.bytes_to_long(rsa_n),
|
||||
number.bytes_to_long(rsa_e)))
|
||||
except ValueError:
|
||||
raise ValidationFailure('invalid public key')
|
||||
sig = rrsig.signature
|
||||
elif _is_dsa(rrsig.algorithm):
|
||||
keyptr = candidate_key.key
|
||||
(t,) = struct.unpack('!B', keyptr[0:1])
|
||||
keyptr = keyptr[1:]
|
||||
octets = 64 + t * 8
|
||||
dsa_q = keyptr[0:20]
|
||||
keyptr = keyptr[20:]
|
||||
dsa_p = keyptr[0:octets]
|
||||
keyptr = keyptr[octets:]
|
||||
dsa_g = keyptr[0:octets]
|
||||
keyptr = keyptr[octets:]
|
||||
dsa_y = keyptr[0:octets]
|
||||
pubkey = CryptoDSA.construct(
|
||||
(number.bytes_to_long(dsa_y),
|
||||
number.bytes_to_long(dsa_g),
|
||||
number.bytes_to_long(dsa_p),
|
||||
number.bytes_to_long(dsa_q)))
|
||||
sig = rrsig.signature[1:]
|
||||
elif _is_ecdsa(rrsig.algorithm):
|
||||
# use ecdsa for NIST-384p -- not currently supported by pycryptodome
|
||||
|
||||
keyptr = candidate_key.key
|
||||
|
||||
if rrsig.algorithm == ECDSAP256SHA256:
|
||||
curve = ecdsa.curves.NIST256p
|
||||
key_len = 32
|
||||
elif rrsig.algorithm == ECDSAP384SHA384:
|
||||
curve = ecdsa.curves.NIST384p
|
||||
key_len = 48
|
||||
|
||||
x = number.bytes_to_long(keyptr[0:key_len])
|
||||
y = number.bytes_to_long(keyptr[key_len:key_len * 2])
|
||||
if not ecdsa.ecdsa.point_is_valid(curve.generator, x, y):
|
||||
raise ValidationFailure('invalid ECDSA key')
|
||||
point = ecdsa.ellipticcurve.Point(curve.curve, x, y, curve.order)
|
||||
verifying_key = ecdsa.keys.VerifyingKey.from_public_point(point,
|
||||
curve)
|
||||
pubkey = ECKeyWrapper(verifying_key, key_len)
|
||||
r = rrsig.signature[:key_len]
|
||||
s = rrsig.signature[key_len:]
|
||||
sig = ecdsa.ecdsa.Signature(number.bytes_to_long(r),
|
||||
number.bytes_to_long(s))
|
||||
|
||||
else:
|
||||
raise ValidationFailure('unknown algorithm %u' % rrsig.algorithm)
|
||||
|
||||
hash.update(_to_rdata(rrsig, origin)[:18])
|
||||
hash.update(rrsig.signer.to_digestable(origin))
|
||||
|
||||
if rrsig.labels < len(rrname) - 1:
|
||||
suffix = rrname.split(rrsig.labels + 1)[1]
|
||||
rrname = dns.name.from_text('*', suffix)
|
||||
rrnamebuf = rrname.to_digestable(origin)
|
||||
rrfixed = struct.pack('!HHI', rdataset.rdtype, rdataset.rdclass,
|
||||
rrsig.original_ttl)
|
||||
rrlist = sorted(rdataset)
|
||||
for rr in rrlist:
|
||||
hash.update(rrnamebuf)
|
||||
hash.update(rrfixed)
|
||||
rrdata = rr.to_digestable(origin)
|
||||
rrlen = struct.pack('!H', len(rrdata))
|
||||
hash.update(rrlen)
|
||||
hash.update(rrdata)
|
||||
|
||||
try:
|
||||
if _is_rsa(rrsig.algorithm):
|
||||
verifier = pkcs1_15.new(pubkey)
|
||||
# will raise ValueError if verify fails:
|
||||
verifier.verify(hash, sig)
|
||||
elif _is_dsa(rrsig.algorithm):
|
||||
verifier = DSS.new(pubkey, 'fips-186-3')
|
||||
verifier.verify(hash, sig)
|
||||
elif _is_ecdsa(rrsig.algorithm):
|
||||
digest = hash.digest()
|
||||
if not pubkey.verify(digest, sig):
|
||||
raise ValueError
|
||||
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)
|
||||
# If we got here, we successfully verified so we can return without error
|
||||
return
|
||||
except ValueError:
|
||||
# this happens on an individual validation failure
|
||||
continue
|
||||
# nothing verified -- raise failure:
|
||||
raise ValidationFailure('verify failure')
|
||||
|
||||
|
||||
def _validate(rrset, rrsigset, keys, origin=None, now=None):
|
||||
"""Validate an RRset.
|
||||
|
||||
*rrset* is the RRset to validate. It can be a ``dns.rrset.RRset`` or
|
||||
a ``(dns.name.Name, dns.rdataset.Rdataset)`` tuple.
|
||||
|
||||
*rrsigset* is the signature RRset to be validated. It can be a
|
||||
``dns.rrset.RRset`` or a ``(dns.name.Name, dns.rdataset.Rdataset)`` tuple.
|
||||
|
||||
*keys* is the key dictionary, used to find the DNSKEY associated with
|
||||
a given name. The dictionary is keyed by a ``dns.name.Name``, and has
|
||||
``dns.node.Node`` or ``dns.rdataset.Rdataset`` values.
|
||||
|
||||
*origin* is a ``dns.name.Name``, the origin to use for relative names.
|
||||
|
||||
*now* is an ``int``, the time to use when validating the signatures,
|
||||
in seconds since the UNIX epoch. The default is the current time.
|
||||
"""
|
||||
|
||||
if isinstance(origin, string_types):
|
||||
origin = dns.name.from_text(origin, dns.name.root)
|
||||
|
||||
if isinstance(rrset, tuple):
|
||||
rrname = rrset[0]
|
||||
else:
|
||||
rrname = rrset.name
|
||||
|
||||
if isinstance(rrsigset, tuple):
|
||||
rrsigname = rrsigset[0]
|
||||
rrsigrdataset = rrsigset[1]
|
||||
else:
|
||||
rrsigname = rrsigset.name
|
||||
rrsigrdataset = rrsigset
|
||||
|
||||
rrname = rrname.choose_relativity(origin)
|
||||
rrsigname = rrsigname.choose_relativity(origin)
|
||||
if rrname != rrsigname:
|
||||
raise ValidationFailure("owner names do not match")
|
||||
|
||||
for rrsig in rrsigrdataset:
|
||||
try:
|
||||
_validate_rrsig(rrset, rrsig, keys, origin, now)
|
||||
return
|
||||
except ValidationFailure:
|
||||
pass
|
||||
raise ValidationFailure("no RRSIGs validated")
|
||||
|
||||
|
||||
def _need_pycrypto(*args, **kwargs):
|
||||
raise NotImplementedError("DNSSEC validation requires pycryptodome/pycryptodomex")
|
||||
|
||||
|
||||
try:
|
||||
try:
|
||||
# test we're using pycryptodome, not pycrypto (which misses SHA1 for example)
|
||||
from Crypto.Hash import MD5, SHA1, SHA256, SHA384, SHA512
|
||||
from Crypto.PublicKey import RSA as CryptoRSA, DSA as CryptoDSA
|
||||
from Crypto.Signature import pkcs1_15, DSS
|
||||
from Crypto.Util import number
|
||||
except ImportError:
|
||||
from Cryptodome.Hash import MD5, SHA1, SHA256, SHA384, SHA512
|
||||
from Cryptodome.PublicKey import RSA as CryptoRSA, DSA as CryptoDSA
|
||||
from Cryptodome.Signature import pkcs1_15, DSS
|
||||
from Cryptodome.Util import number
|
||||
except ImportError:
|
||||
validate = _need_pycrypto
|
||||
validate_rrsig = _need_pycrypto
|
||||
_have_pycrypto = False
|
||||
_have_ecdsa = False
|
||||
else:
|
||||
validate = _validate
|
||||
validate_rrsig = _validate_rrsig
|
||||
_have_pycrypto = True
|
||||
|
||||
try:
|
||||
import ecdsa
|
||||
import ecdsa.ecdsa
|
||||
import ecdsa.ellipticcurve
|
||||
import ecdsa.keys
|
||||
except ImportError:
|
||||
_have_ecdsa = False
|
||||
else:
|
||||
_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 = number.bytes_to_long(digest)
|
||||
return self.key.pubkey.verifies(diglong, sig)
|
||||
@@ -1,19 +0,0 @@
|
||||
from typing import Union, Dict, Tuple, Optional
|
||||
from . import rdataset, rrset, exception, name, rdtypes, rdata, node
|
||||
import dns.rdtypes.ANY.DS as DS
|
||||
import dns.rdtypes.ANY.DNSKEY as DNSKEY
|
||||
|
||||
_have_ecdsa : bool
|
||||
_have_pycrypto : bool
|
||||
|
||||
def validate_rrsig(rrset : Union[Tuple[name.Name, rdataset.Rdataset], rrset.RRset], rrsig : rdata.Rdata, keys : Dict[name.Name, Union[node.Node, rdataset.Rdataset]], origin : Optional[name.Name] = None, now : Optional[int] = None) -> None:
|
||||
...
|
||||
|
||||
def validate(rrset: Union[Tuple[name.Name, rdataset.Rdataset], rrset.RRset], rrsigset : Union[Tuple[name.Name, rdataset.Rdataset], rrset.RRset], keys : Dict[name.Name, Union[node.Node, rdataset.Rdataset]], origin=None, now=None) -> None:
|
||||
...
|
||||
|
||||
class ValidationFailure(exception.DNSException):
|
||||
...
|
||||
|
||||
def make_ds(name : name.Name, key : DNSKEY.DNSKEY, algorithm : str, origin : Optional[name.Name] = None) -> DS.DS:
|
||||
...
|
||||
-105
@@ -1,105 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2006-2017 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
"""DNS E.164 helpers."""
|
||||
|
||||
import dns.exception
|
||||
import dns.name
|
||||
import dns.resolver
|
||||
from ._compat import string_types, maybe_decode
|
||||
|
||||
#: The public E.164 domain.
|
||||
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.
|
||||
|
||||
Non-digits in the text are ignored, i.e. "16505551212",
|
||||
"+1.650.555.1212" and "1 (650) 555-1212" are all the same.
|
||||
|
||||
*text*, a ``text``, is an E.164 number in textual form.
|
||||
|
||||
*origin*, a ``dns.name.Name``, the domain in which the number
|
||||
should be constructed. The default is ``e164.arpa.``.
|
||||
|
||||
Returns a ``dns.name.Name``.
|
||||
"""
|
||||
|
||||
parts = [d for d in text if d.isdigit()]
|
||||
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.
|
||||
|
||||
Note that dnspython does not have any information about preferred
|
||||
number formats within national numbering plans, so all numbers are
|
||||
emitted as a simple string of digits, prefixed by a '+' (unless
|
||||
*want_plus_prefix* is ``False``).
|
||||
|
||||
*name* is a ``dns.name.Name``, the ENUM domain name.
|
||||
|
||||
*origin* is a ``dns.name.Name``, a domain containing the ENUM
|
||||
domain name. The name is relativized to this domain before being
|
||||
converted to text. If ``None``, no relativization is done.
|
||||
|
||||
*want_plus_prefix* is a ``bool``. If True, add a '+' to the beginning of
|
||||
the returned number.
|
||||
|
||||
Returns a ``text``.
|
||||
|
||||
"""
|
||||
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 = b''.join(dlabels)
|
||||
if want_plus_prefix:
|
||||
text = b'+' + text
|
||||
return maybe_decode(text)
|
||||
|
||||
|
||||
def query(number, domains, resolver=None):
|
||||
"""Look for NAPTR RRs for the specified number in the specified domains.
|
||||
|
||||
e.g. lookup('16505551212', ['e164.dnspython.org.', 'e164.arpa.'])
|
||||
|
||||
*number*, a ``text`` is the number to look for.
|
||||
|
||||
*domains* is an iterable containing ``dns.name.Name`` values.
|
||||
|
||||
*resolver*, a ``dns.resolver.Resolver``, is the resolver to use. If
|
||||
``None``, the default resolver is used.
|
||||
"""
|
||||
|
||||
if resolver is None:
|
||||
resolver = dns.resolver.get_default_resolver()
|
||||
e_nx = dns.resolver.NXDOMAIN()
|
||||
for domain in domains:
|
||||
if isinstance(domain, string_types):
|
||||
domain = dns.name.from_text(domain)
|
||||
qname = dns.e164.from_e164(number, domain)
|
||||
try:
|
||||
return resolver.query(qname, 'NAPTR')
|
||||
except dns.resolver.NXDOMAIN as e:
|
||||
e_nx += e
|
||||
raise e_nx
|
||||
@@ -1,10 +0,0 @@
|
||||
from typing import Optional, Iterable
|
||||
from . import name, resolver
|
||||
def from_e164(text : str, origin=name.Name(".")) -> name.Name:
|
||||
...
|
||||
|
||||
def to_e164(name : name.Name, origin : Optional[name.Name] = None, want_plus_prefix=True) -> str:
|
||||
...
|
||||
|
||||
def query(number : str, domains : Iterable[str], resolver : Optional[resolver.Resolver] = None) -> resolver.Answer:
|
||||
...
|
||||
-269
@@ -1,269 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2009-2017 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
"""EDNS Options"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import math
|
||||
import struct
|
||||
|
||||
import dns.inet
|
||||
|
||||
#: NSID
|
||||
NSID = 3
|
||||
#: DAU
|
||||
DAU = 5
|
||||
#: DHU
|
||||
DHU = 6
|
||||
#: N3U
|
||||
N3U = 7
|
||||
#: ECS (client-subnet)
|
||||
ECS = 8
|
||||
#: EXPIRE
|
||||
EXPIRE = 9
|
||||
#: COOKIE
|
||||
COOKIE = 10
|
||||
#: KEEPALIVE
|
||||
KEEPALIVE = 11
|
||||
#: PADDING
|
||||
PADDING = 12
|
||||
#: CHAIN
|
||||
CHAIN = 13
|
||||
|
||||
class Option(object):
|
||||
|
||||
"""Base class for all EDNS option types."""
|
||||
|
||||
def __init__(self, otype):
|
||||
"""Initialize an option.
|
||||
|
||||
*otype*, an ``int``, is the option type.
|
||||
"""
|
||||
self.otype = otype
|
||||
|
||||
def to_wire(self, file):
|
||||
"""Convert an option to wire format.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def from_wire(cls, otype, wire, current, olen):
|
||||
"""Build an EDNS option object from wire format.
|
||||
|
||||
*otype*, an ``int``, is the option type.
|
||||
|
||||
*wire*, a ``binary``, is the wire-format message.
|
||||
|
||||
*current*, an ``int``, is the offset in *wire* of the beginning
|
||||
of the rdata.
|
||||
|
||||
*olen*, an ``int``, is the length of the wire-format option data
|
||||
|
||||
Returns a ``dns.edns.Option``.
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
def _cmp(self, other):
|
||||
"""Compare an EDNS option with another option of the same type.
|
||||
|
||||
Returns < 0 if < *other*, 0 if == *other*, and > 0 if > *other*.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Option):
|
||||
return False
|
||||
if self.otype != other.otype:
|
||||
return False
|
||||
return self._cmp(other) == 0
|
||||
|
||||
def __ne__(self, other):
|
||||
if not isinstance(other, Option):
|
||||
return False
|
||||
if self.otype != other.otype:
|
||||
return False
|
||||
return self._cmp(other) != 0
|
||||
|
||||
def __lt__(self, other):
|
||||
if not isinstance(other, Option) or \
|
||||
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:
|
||||
return NotImplemented
|
||||
return self._cmp(other) <= 0
|
||||
|
||||
def __ge__(self, other):
|
||||
if not isinstance(other, Option) or \
|
||||
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:
|
||||
return NotImplemented
|
||||
return self._cmp(other) > 0
|
||||
|
||||
|
||||
class GenericOption(Option):
|
||||
|
||||
"""Generic Option Class
|
||||
|
||||
This class is used for EDNS option types for which we have no better
|
||||
implementation.
|
||||
"""
|
||||
|
||||
def __init__(self, otype, data):
|
||||
super(GenericOption, self).__init__(otype)
|
||||
self.data = data
|
||||
|
||||
def to_wire(self, file):
|
||||
file.write(self.data)
|
||||
|
||||
def to_text(self):
|
||||
return "Generic %d" % self.otype
|
||||
|
||||
@classmethod
|
||||
def from_wire(cls, otype, wire, current, olen):
|
||||
return cls(otype, wire[current: current + olen])
|
||||
|
||||
def _cmp(self, other):
|
||||
if self.data == other.data:
|
||||
return 0
|
||||
if self.data > other.data:
|
||||
return 1
|
||||
return -1
|
||||
|
||||
|
||||
class ECSOption(Option):
|
||||
"""EDNS Client Subnet (ECS, RFC7871)"""
|
||||
|
||||
def __init__(self, address, srclen=None, scopelen=0):
|
||||
"""*address*, a ``text``, is the client address information.
|
||||
|
||||
*srclen*, an ``int``, the source prefix length, which is the
|
||||
leftmost number of bits of the address to be used for the
|
||||
lookup. The default is 24 for IPv4 and 56 for IPv6.
|
||||
|
||||
*scopelen*, an ``int``, the scope prefix length. This value
|
||||
must be 0 in queries, and should be set in responses.
|
||||
"""
|
||||
|
||||
super(ECSOption, self).__init__(ECS)
|
||||
af = dns.inet.af_for_address(address)
|
||||
|
||||
if af == dns.inet.AF_INET6:
|
||||
self.family = 2
|
||||
if srclen is None:
|
||||
srclen = 56
|
||||
elif af == dns.inet.AF_INET:
|
||||
self.family = 1
|
||||
if srclen is None:
|
||||
srclen = 24
|
||||
else:
|
||||
raise ValueError('Bad ip family')
|
||||
|
||||
self.address = address
|
||||
self.srclen = srclen
|
||||
self.scopelen = scopelen
|
||||
|
||||
addrdata = dns.inet.inet_pton(af, address)
|
||||
nbytes = int(math.ceil(srclen/8.0))
|
||||
|
||||
# Truncate to srclen and pad to the end of the last octet needed
|
||||
# See RFC section 6
|
||||
self.addrdata = addrdata[:nbytes]
|
||||
nbits = srclen % 8
|
||||
if nbits != 0:
|
||||
last = struct.pack('B', ord(self.addrdata[-1:]) & (0xff << nbits))
|
||||
self.addrdata = self.addrdata[:-1] + last
|
||||
|
||||
def to_text(self):
|
||||
return "ECS {}/{} scope/{}".format(self.address, self.srclen,
|
||||
self.scopelen)
|
||||
|
||||
def to_wire(self, file):
|
||||
file.write(struct.pack('!H', self.family))
|
||||
file.write(struct.pack('!BB', self.srclen, self.scopelen))
|
||||
file.write(self.addrdata)
|
||||
|
||||
@classmethod
|
||||
def from_wire(cls, otype, wire, cur, olen):
|
||||
family, src, scope = struct.unpack('!HBB', wire[cur:cur+4])
|
||||
cur += 4
|
||||
|
||||
addrlen = int(math.ceil(src/8.0))
|
||||
|
||||
if family == 1:
|
||||
af = dns.inet.AF_INET
|
||||
pad = 4 - addrlen
|
||||
elif family == 2:
|
||||
af = dns.inet.AF_INET6
|
||||
pad = 16 - addrlen
|
||||
else:
|
||||
raise ValueError('unsupported family')
|
||||
|
||||
addr = dns.inet.inet_ntop(af, wire[cur:cur+addrlen] + b'\x00' * pad)
|
||||
return cls(addr, src, scope)
|
||||
|
||||
def _cmp(self, other):
|
||||
if self.addrdata == other.addrdata:
|
||||
return 0
|
||||
if self.addrdata > other.addrdata:
|
||||
return 1
|
||||
return -1
|
||||
|
||||
_type_to_class = {
|
||||
ECS: ECSOption
|
||||
}
|
||||
|
||||
def get_option_class(otype):
|
||||
"""Return the class for the specified option type.
|
||||
|
||||
The GenericOption class is used if a more specific class is not
|
||||
known.
|
||||
"""
|
||||
|
||||
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.
|
||||
|
||||
*otype*, an ``int``, is the option type.
|
||||
|
||||
*wire*, a ``binary``, is the wire-format message.
|
||||
|
||||
*current*, an ``int``, is the offset in *wire* of the beginning
|
||||
of the rdata.
|
||||
|
||||
*olen*, an ``int``, is the length of the wire-format option data
|
||||
|
||||
Returns an instance of a subclass of ``dns.edns.Option``.
|
||||
"""
|
||||
|
||||
cls = get_option_class(otype)
|
||||
return cls.from_wire(otype, wire, current, olen)
|
||||
@@ -1,148 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2009-2017 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
from ._compat import long, binary_type
|
||||
try:
|
||||
import threading as _threading
|
||||
except ImportError:
|
||||
import dummy_threading as _threading
|
||||
|
||||
|
||||
class EntropyPool(object):
|
||||
|
||||
# This is an entropy pool for Python implementations that do not
|
||||
# have a working SystemRandom. I'm not sure there are any, but
|
||||
# leaving this code doesn't hurt anything as the library code
|
||||
# is used if present.
|
||||
|
||||
def __init__(self, seed=None):
|
||||
self.pool_index = 0
|
||||
self.digest = None
|
||||
self.next_byte = 0
|
||||
self.lock = _threading.Lock()
|
||||
try:
|
||||
import hashlib
|
||||
self.hash = hashlib.sha1()
|
||||
self.hash_len = 20
|
||||
except ImportError:
|
||||
try:
|
||||
import sha
|
||||
self.hash = sha.new()
|
||||
self.hash_len = 20
|
||||
except ImportError:
|
||||
import md5 # pylint: disable=import-error
|
||||
self.hash = md5.new()
|
||||
self.hash_len = 16
|
||||
self.pool = bytearray(b'\0' * self.hash_len)
|
||||
if seed is not None:
|
||||
self.stir(bytearray(seed))
|
||||
self.seeded = True
|
||||
self.seed_pid = os.getpid()
|
||||
else:
|
||||
self.seeded = False
|
||||
self.seed_pid = 0
|
||||
|
||||
def stir(self, entropy, already_locked=False):
|
||||
if not already_locked:
|
||||
self.lock.acquire()
|
||||
try:
|
||||
for c in entropy:
|
||||
if self.pool_index == self.hash_len:
|
||||
self.pool_index = 0
|
||||
b = c & 0xff
|
||||
self.pool[self.pool_index] ^= b
|
||||
self.pool_index += 1
|
||||
finally:
|
||||
if not already_locked:
|
||||
self.lock.release()
|
||||
|
||||
def _maybe_seed(self):
|
||||
if not self.seeded or self.seed_pid != os.getpid():
|
||||
try:
|
||||
seed = os.urandom(16)
|
||||
except Exception:
|
||||
try:
|
||||
r = open('/dev/urandom', 'rb', 0)
|
||||
try:
|
||||
seed = r.read(16)
|
||||
finally:
|
||||
r.close()
|
||||
except Exception:
|
||||
seed = str(time.time())
|
||||
self.seeded = True
|
||||
self.seed_pid = os.getpid()
|
||||
self.digest = None
|
||||
seed = bytearray(seed)
|
||||
self.stir(seed, True)
|
||||
|
||||
def random_8(self):
|
||||
self.lock.acquire()
|
||||
try:
|
||||
self._maybe_seed()
|
||||
if self.digest is None or self.next_byte == self.hash_len:
|
||||
self.hash.update(binary_type(self.pool))
|
||||
self.digest = bytearray(self.hash.digest())
|
||||
self.stir(self.digest, True)
|
||||
self.next_byte = 0
|
||||
value = self.digest[self.next_byte]
|
||||
self.next_byte += 1
|
||||
finally:
|
||||
self.lock.release()
|
||||
return value
|
||||
|
||||
def random_16(self):
|
||||
return self.random_8() * 256 + self.random_8()
|
||||
|
||||
def random_32(self):
|
||||
return self.random_16() * 65536 + self.random_16()
|
||||
|
||||
def random_between(self, first, last):
|
||||
size = last - first + 1
|
||||
if size > long(4294967296):
|
||||
raise ValueError('too big')
|
||||
if size > 65536:
|
||||
rand = self.random_32
|
||||
max = long(4294967295)
|
||||
elif size > 256:
|
||||
rand = self.random_16
|
||||
max = 65535
|
||||
else:
|
||||
rand = self.random_8
|
||||
max = 255
|
||||
return first + size * rand() // (max + 1)
|
||||
|
||||
pool = EntropyPool()
|
||||
|
||||
try:
|
||||
system_random = random.SystemRandom()
|
||||
except Exception:
|
||||
system_random = None
|
||||
|
||||
def random_16():
|
||||
if system_random is not None:
|
||||
return system_random.randrange(0, 65536)
|
||||
else:
|
||||
return pool.random_16()
|
||||
|
||||
def between(first, last):
|
||||
if system_random is not None:
|
||||
return system_random.randrange(first, last + 1)
|
||||
else:
|
||||
return pool.random_between(first, last)
|
||||
@@ -1,10 +0,0 @@
|
||||
from typing import Optional
|
||||
from random import SystemRandom
|
||||
|
||||
system_random : Optional[SystemRandom]
|
||||
|
||||
def random_16() -> int:
|
||||
pass
|
||||
|
||||
def between(first: int, last: int) -> int:
|
||||
pass
|
||||
@@ -1,128 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2003-2017 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
"""Common DNS Exceptions.
|
||||
|
||||
Dnspython modules may also define their own exceptions, which will
|
||||
always be subclasses of ``DNSException``.
|
||||
"""
|
||||
|
||||
class DNSException(Exception):
|
||||
"""Abstract base class shared by all dnspython exceptions.
|
||||
|
||||
It supports two basic modes of operation:
|
||||
|
||||
a) Old/compatible mode is used if ``__init__`` was called with
|
||||
empty *kwargs*. In compatible mode all *args* are passed
|
||||
to the standard Python Exception class as before and all *args* are
|
||||
printed by the standard ``__str__`` implementation. Class variable
|
||||
``msg`` (or doc string if ``msg`` is ``None``) is returned from ``str()``
|
||||
if *args* is empty.
|
||||
|
||||
b) New/parametrized mode is used if ``__init__`` was called with
|
||||
non-empty *kwargs*.
|
||||
In the new mode *args* must be empty and all kwargs must match
|
||||
those set in class variable ``supp_kwargs``. All kwargs are stored inside
|
||||
``self.kwargs`` and used in a new ``__str__`` implementation to construct
|
||||
a formatted message based on the ``fmt`` class variable, a ``string``.
|
||||
|
||||
In the simplest case it is enough to override the ``supp_kwargs``
|
||||
and ``fmt`` class variables to get nice parametrized messages.
|
||||
"""
|
||||
|
||||
msg = None # non-parametrized message
|
||||
supp_kwargs = set() # accepted parameters for _fmt_kwargs (sanity check)
|
||||
fmt = None # message parametrized with results from _fmt_kwargs
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._check_params(*args, **kwargs)
|
||||
if kwargs:
|
||||
self.kwargs = self._check_kwargs(**kwargs)
|
||||
self.msg = str(self)
|
||||
else:
|
||||
self.kwargs = dict() # defined but empty for old mode exceptions
|
||||
if self.msg is None:
|
||||
# doc string is better implicit message than empty string
|
||||
self.msg = self.__doc__
|
||||
if args:
|
||||
super(DNSException, self).__init__(*args)
|
||||
else:
|
||||
super(DNSException, self).__init__(self.msg)
|
||||
|
||||
def _check_params(self, *args, **kwargs):
|
||||
"""Old exceptions supported only args and not kwargs.
|
||||
|
||||
For sanity we do not allow to mix old and new behavior."""
|
||||
if args or kwargs:
|
||||
assert bool(args) != bool(kwargs), \
|
||||
'keyword arguments are mutually exclusive with positional args'
|
||||
|
||||
def _check_kwargs(self, **kwargs):
|
||||
if kwargs:
|
||||
assert set(kwargs.keys()) == self.supp_kwargs, \
|
||||
'following set of keyword args is required: %s' % (
|
||||
self.supp_kwargs)
|
||||
return kwargs
|
||||
|
||||
def _fmt_kwargs(self, **kwargs):
|
||||
"""Format kwargs before printing them.
|
||||
|
||||
Resulting dictionary has to have keys necessary for str.format call
|
||||
on fmt class variable.
|
||||
"""
|
||||
fmtargs = {}
|
||||
for kw, data in kwargs.items():
|
||||
if isinstance(data, (list, set)):
|
||||
# convert list of <someobj> to list of str(<someobj>)
|
||||
fmtargs[kw] = list(map(str, data))
|
||||
if len(fmtargs[kw]) == 1:
|
||||
# remove list brackets [] from single-item lists
|
||||
fmtargs[kw] = fmtargs[kw].pop()
|
||||
else:
|
||||
fmtargs[kw] = data
|
||||
return fmtargs
|
||||
|
||||
def __str__(self):
|
||||
if self.kwargs and self.fmt:
|
||||
# provide custom message constructed from keyword arguments
|
||||
fmtargs = self._fmt_kwargs(**self.kwargs)
|
||||
return self.fmt.format(**fmtargs)
|
||||
else:
|
||||
# print *args directly in the same way as old DNSException
|
||||
return super(DNSException, self).__str__()
|
||||
|
||||
|
||||
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 = {'timeout'}
|
||||
fmt = "The DNS operation timed out after {timeout} seconds"
|
||||
@@ -1,9 +0,0 @@
|
||||
from typing import Set, Optional, Dict
|
||||
|
||||
class DNSException(Exception):
|
||||
supp_kwargs : Set[str]
|
||||
kwargs : Optional[Dict]
|
||||
|
||||
class SyntaxError(DNSException): ...
|
||||
class FormError(DNSException): ...
|
||||
class Timeout(DNSException): ...
|
||||
@@ -1,130 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2001-2017 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
"""DNS Message Flags."""
|
||||
|
||||
# Standard DNS flags
|
||||
|
||||
#: Query Response
|
||||
QR = 0x8000
|
||||
#: Authoritative Answer
|
||||
AA = 0x0400
|
||||
#: Truncated Response
|
||||
TC = 0x0200
|
||||
#: Recursion Desired
|
||||
RD = 0x0100
|
||||
#: Recursion Available
|
||||
RA = 0x0080
|
||||
#: Authentic Data
|
||||
AD = 0x0020
|
||||
#: Checking Disabled
|
||||
CD = 0x0010
|
||||
|
||||
# EDNS flags
|
||||
|
||||
#: DNSSEC answer OK
|
||||
DO = 0x8000
|
||||
|
||||
_by_text = {
|
||||
'QR': QR,
|
||||
'AA': AA,
|
||||
'TC': TC,
|
||||
'RD': RD,
|
||||
'RA': RA,
|
||||
'AD': AD,
|
||||
'CD': CD
|
||||
}
|
||||
|
||||
_edns_by_text = {
|
||||
'DO': DO
|
||||
}
|
||||
|
||||
|
||||
# We construct the inverse mappings programmatically to ensure that we
|
||||
# cannot make any mistakes (e.g. omissions, cut-and-paste errors) that
|
||||
# would cause the mappings not to be true inverses.
|
||||
|
||||
_by_value = {y: x for x, y in _by_text.items()}
|
||||
|
||||
_edns_by_value = {y: x for x, y in _edns_by_text.items()}
|
||||
|
||||
|
||||
def _order_flags(table):
|
||||
order = list(table.items())
|
||||
order.sort()
|
||||
order.reverse()
|
||||
return order
|
||||
|
||||
_flags_order = _order_flags(_by_value)
|
||||
|
||||
_edns_flags_order = _order_flags(_edns_by_value)
|
||||
|
||||
|
||||
def _from_text(text, table):
|
||||
flags = 0
|
||||
tokens = text.split()
|
||||
for t in tokens:
|
||||
flags = flags | table[t.upper()]
|
||||
return flags
|
||||
|
||||
|
||||
def _to_text(flags, table, order):
|
||||
text_flags = []
|
||||
for k, v in order:
|
||||
if flags & k != 0:
|
||||
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.
|
||||
|
||||
Returns an ``int``
|
||||
"""
|
||||
|
||||
return _from_text(text, _by_text)
|
||||
|
||||
|
||||
def to_text(flags):
|
||||
"""Convert a flags value into a space-separated list of flag text
|
||||
values.
|
||||
|
||||
Returns a ``text``.
|
||||
"""
|
||||
|
||||
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
|
||||
flags value.
|
||||
|
||||
Returns an ``int``
|
||||
"""
|
||||
|
||||
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.
|
||||
|
||||
Returns a ``text``.
|
||||
"""
|
||||
|
||||
return _to_text(flags, _edns_by_value, _edns_flags_order)
|
||||
@@ -1,69 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2012-2017 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
"""DNS GENERATE range conversion."""
|
||||
|
||||
import dns
|
||||
|
||||
def from_text(text):
|
||||
"""Convert the text form of a range in a ``$GENERATE`` statement to an
|
||||
integer.
|
||||
|
||||
*text*, a ``str``, the textual range in ``$GENERATE`` form.
|
||||
|
||||
Returns a tuple of three ``int`` values ``(start, stop, step)``.
|
||||
"""
|
||||
|
||||
# TODO, figure out the bounds on start, stop and step.
|
||||
step = 1
|
||||
cur = ''
|
||||
state = 0
|
||||
# state 0 1 2 3 4
|
||||
# x - y / z
|
||||
|
||||
if text and text[0] == '-':
|
||||
raise dns.exception.SyntaxError("Start cannot be a negative number")
|
||||
|
||||
for c in text:
|
||||
if c == '-' and state == 0:
|
||||
start = int(cur)
|
||||
cur = ''
|
||||
state = 2
|
||||
elif c == '/':
|
||||
stop = int(cur)
|
||||
cur = ''
|
||||
state = 4
|
||||
elif c.isdigit():
|
||||
cur += c
|
||||
else:
|
||||
raise dns.exception.SyntaxError("Could not parse %s" % (c))
|
||||
|
||||
if state in (1, 3):
|
||||
raise dns.exception.SyntaxError()
|
||||
|
||||
if state == 2:
|
||||
stop = int(cur)
|
||||
|
||||
if state == 4:
|
||||
step = int(cur)
|
||||
|
||||
assert step >= 1
|
||||
assert start >= 0
|
||||
assert start <= stop
|
||||
# TODO, can start == stop?
|
||||
|
||||
return (start, stop, step)
|
||||
@@ -1,37 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2011 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
"""Hashing backwards compatibility wrapper"""
|
||||
|
||||
import hashlib
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"dns.hash module will be removed in future versions. Please use hashlib instead.",
|
||||
DeprecationWarning)
|
||||
|
||||
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 get(algorithm):
|
||||
return hashes[algorithm.upper()]
|
||||
-124
@@ -1,124 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2003-2017 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
"""Generic Internet address helper functions."""
|
||||
|
||||
import socket
|
||||
|
||||
import dns.ipv4
|
||||
import dns.ipv6
|
||||
|
||||
from ._compat import maybe_ord
|
||||
|
||||
# We assume that AF_INET is always defined.
|
||||
|
||||
AF_INET = socket.AF_INET
|
||||
|
||||
# AF_INET6 might not be defined in the socket module, but we need it.
|
||||
# We'll try to use the socket module's value, and if it doesn't work,
|
||||
# we'll use our own value.
|
||||
|
||||
try:
|
||||
AF_INET6 = socket.AF_INET6
|
||||
except AttributeError:
|
||||
AF_INET6 = 9999
|
||||
|
||||
|
||||
def inet_pton(family, text):
|
||||
"""Convert the textual form of a network address into its binary form.
|
||||
|
||||
*family* is an ``int``, the address family.
|
||||
|
||||
*text* is a ``text``, the textual address.
|
||||
|
||||
Raises ``NotImplementedError`` if the address family specified is not
|
||||
implemented.
|
||||
|
||||
Returns a ``binary``.
|
||||
"""
|
||||
|
||||
if family == AF_INET:
|
||||
return dns.ipv4.inet_aton(text)
|
||||
elif family == AF_INET6:
|
||||
return dns.ipv6.inet_aton(text)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def inet_ntop(family, address):
|
||||
"""Convert the binary form of a network address into its textual form.
|
||||
|
||||
*family* is an ``int``, the address family.
|
||||
|
||||
*address* is a ``binary``, the network address in binary form.
|
||||
|
||||
Raises ``NotImplementedError`` if the address family specified is not
|
||||
implemented.
|
||||
|
||||
Returns a ``text``.
|
||||
"""
|
||||
|
||||
if family == AF_INET:
|
||||
return dns.ipv4.inet_ntoa(address)
|
||||
elif family == AF_INET6:
|
||||
return dns.ipv6.inet_ntoa(address)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def af_for_address(text):
|
||||
"""Determine the address family of a textual-form network address.
|
||||
|
||||
*text*, a ``text``, the textual address.
|
||||
|
||||
Raises ``ValueError`` if the address family cannot be determined
|
||||
from the input.
|
||||
|
||||
Returns an ``int``.
|
||||
"""
|
||||
|
||||
try:
|
||||
dns.ipv4.inet_aton(text)
|
||||
return AF_INET
|
||||
except Exception:
|
||||
try:
|
||||
dns.ipv6.inet_aton(text)
|
||||
return AF_INET6
|
||||
except:
|
||||
raise ValueError
|
||||
|
||||
|
||||
def is_multicast(text):
|
||||
"""Is the textual-form network address a multicast address?
|
||||
|
||||
*text*, a ``text``, the textual address.
|
||||
|
||||
Raises ``ValueError`` if the address family cannot be determined
|
||||
from the input.
|
||||
|
||||
Returns a ``bool``.
|
||||
"""
|
||||
|
||||
try:
|
||||
first = maybe_ord(dns.ipv4.inet_aton(text)[0])
|
||||
return first >= 224 and first <= 239
|
||||
except Exception:
|
||||
try:
|
||||
first = maybe_ord(dns.ipv6.inet_aton(text)[0])
|
||||
return first == 255
|
||||
except Exception:
|
||||
raise ValueError
|
||||
@@ -1,4 +0,0 @@
|
||||
from typing import Union
|
||||
from socket import AddressFamily
|
||||
|
||||
AF_INET6 : Union[int, AddressFamily]
|
||||
@@ -1,63 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2003-2017 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
"""IPv4 helper functions."""
|
||||
|
||||
import struct
|
||||
|
||||
import dns.exception
|
||||
from ._compat import binary_type
|
||||
|
||||
def inet_ntoa(address):
|
||||
"""Convert an IPv4 address in binary form to text form.
|
||||
|
||||
*address*, a ``binary``, the IPv4 address in binary form.
|
||||
|
||||
Returns a ``text``.
|
||||
"""
|
||||
|
||||
if len(address) != 4:
|
||||
raise dns.exception.SyntaxError
|
||||
if not isinstance(address, bytearray):
|
||||
address = bytearray(address)
|
||||
return ('%u.%u.%u.%u' % (address[0], address[1],
|
||||
address[2], address[3]))
|
||||
|
||||
def inet_aton(text):
|
||||
"""Convert an IPv4 address in text form to binary form.
|
||||
|
||||
*text*, a ``text``, the IPv4 address in textual form.
|
||||
|
||||
Returns a ``binary``.
|
||||
"""
|
||||
|
||||
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:
|
||||
if not part.isdigit():
|
||||
raise dns.exception.SyntaxError
|
||||
if len(part) > 1 and part[0] == '0':
|
||||
# No leading zeros
|
||||
raise dns.exception.SyntaxError
|
||||
try:
|
||||
bytes = [int(part) for part in parts]
|
||||
return struct.pack('BBBB', *bytes)
|
||||
except:
|
||||
raise dns.exception.SyntaxError
|
||||
-181
@@ -1,181 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2003-2017 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
"""IPv6 helper functions."""
|
||||
|
||||
import re
|
||||
import binascii
|
||||
|
||||
import dns.exception
|
||||
import dns.ipv4
|
||||
from ._compat import xrange, binary_type, maybe_decode
|
||||
|
||||
_leading_zero = re.compile(r'0+([0-9a-f]+)')
|
||||
|
||||
def inet_ntoa(address):
|
||||
"""Convert an IPv6 address in binary form to text form.
|
||||
|
||||
*address*, a ``binary``, the IPv6 address in binary form.
|
||||
|
||||
Raises ``ValueError`` if the address isn't 16 bytes long.
|
||||
Returns a ``text``.
|
||||
"""
|
||||
|
||||
if len(address) != 16:
|
||||
raise ValueError("IPv6 addresses are 16 bytes long")
|
||||
hex = binascii.hexlify(address)
|
||||
chunks = []
|
||||
i = 0
|
||||
l = len(hex)
|
||||
while i < l:
|
||||
chunk = maybe_decode(hex[i : i + 4])
|
||||
# strip leading zeros. we do this with an re instead of
|
||||
# with lstrip() because lstrip() didn't support chars until
|
||||
# python 2.2.2
|
||||
m = _leading_zero.match(chunk)
|
||||
if not m is None:
|
||||
chunk = m.group(1)
|
||||
chunks.append(chunk)
|
||||
i += 4
|
||||
#
|
||||
# Compress the longest subsequence of 0-value chunks to ::
|
||||
#
|
||||
best_start = 0
|
||||
best_len = 0
|
||||
start = -1
|
||||
last_was_zero = False
|
||||
for i in xrange(8):
|
||||
if chunks[i] != '0':
|
||||
if last_was_zero:
|
||||
end = i
|
||||
current_len = end - start
|
||||
if current_len > best_len:
|
||||
best_start = start
|
||||
best_len = current_len
|
||||
last_was_zero = False
|
||||
elif not last_was_zero:
|
||||
start = i
|
||||
last_was_zero = True
|
||||
if last_was_zero:
|
||||
end = 8
|
||||
current_len = end - start
|
||||
if current_len > best_len:
|
||||
best_start = start
|
||||
best_len = current_len
|
||||
if best_len > 1:
|
||||
if best_start == 0 and \
|
||||
(best_len == 6 or
|
||||
best_len == 5 and chunks[5] == 'ffff'):
|
||||
# We have an embedded IPv4 address
|
||||
if best_len == 6:
|
||||
prefix = '::'
|
||||
else:
|
||||
prefix = '::ffff:'
|
||||
hex = prefix + dns.ipv4.inet_ntoa(address[12:])
|
||||
else:
|
||||
hex = ':'.join(chunks[:best_start]) + '::' + \
|
||||
':'.join(chunks[best_start + best_len:])
|
||||
else:
|
||||
hex = ':'.join(chunks)
|
||||
return hex
|
||||
|
||||
_v4_ending = re.compile(br'(.*):(\d+\.\d+\.\d+\.\d+)$')
|
||||
_colon_colon_start = re.compile(br'::.*')
|
||||
_colon_colon_end = re.compile(br'.*::$')
|
||||
|
||||
def inet_aton(text):
|
||||
"""Convert an IPv6 address in text form to binary form.
|
||||
|
||||
*text*, a ``text``, the IPv6 address in textual form.
|
||||
|
||||
Returns a ``binary``.
|
||||
"""
|
||||
|
||||
#
|
||||
# Our aim here is not something fast; we just want something that works.
|
||||
#
|
||||
if not isinstance(text, binary_type):
|
||||
text = text.encode()
|
||||
|
||||
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 = bytearray(dns.ipv4.inet_aton(m.group(2)))
|
||||
text = (u"{}:{:02x}{:02x}:{:02x}{:02x}".format(m.group(1).decode(),
|
||||
b[0], b[1], b[2],
|
||||
b[3])).encode()
|
||||
#
|
||||
# Try to turn '::<whatever>' into ':<whatever>'; if no match try to
|
||||
# turn '<whatever>::' into '<whatever>:'
|
||||
#
|
||||
m = _colon_colon_start.match(text)
|
||||
if not m is None:
|
||||
text = text[1:]
|
||||
else:
|
||||
m = _colon_colon_end.match(text)
|
||||
if not m is None:
|
||||
text = text[:-1]
|
||||
#
|
||||
# Now canonicalize into 8 chunks of 4 hex digits each
|
||||
#
|
||||
chunks = text.split(b':')
|
||||
l = len(chunks)
|
||||
if l > 8:
|
||||
raise dns.exception.SyntaxError
|
||||
seen_empty = False
|
||||
canonical = []
|
||||
for c in chunks:
|
||||
if c == b'':
|
||||
if seen_empty:
|
||||
raise dns.exception.SyntaxError
|
||||
seen_empty = True
|
||||
for i in xrange(0, 8 - l + 1):
|
||||
canonical.append(b'0000')
|
||||
else:
|
||||
lc = len(c)
|
||||
if lc > 4:
|
||||
raise dns.exception.SyntaxError
|
||||
if lc != 4:
|
||||
c = (b'0' * (4 - lc)) + c
|
||||
canonical.append(c)
|
||||
if l < 8 and not seen_empty:
|
||||
raise dns.exception.SyntaxError
|
||||
text = b''.join(canonical)
|
||||
|
||||
#
|
||||
# Finally we can go to binary.
|
||||
#
|
||||
try:
|
||||
return binascii.unhexlify(text)
|
||||
except (binascii.Error, TypeError):
|
||||
raise dns.exception.SyntaxError
|
||||
|
||||
_mapped_prefix = b'\x00' * 10 + b'\xff\xff'
|
||||
|
||||
def is_mapped(address):
|
||||
"""Is the specified address a mapped IPv4 address?
|
||||
|
||||
*address*, a ``binary`` is an IPv6 address in binary form.
|
||||
|
||||
Returns a ``bool``.
|
||||
"""
|
||||
|
||||
return address.startswith(_mapped_prefix)
|
||||
-1175
File diff suppressed because it is too large
Load Diff
@@ -1,55 +0,0 @@
|
||||
from typing import Optional, Dict, List, Tuple, Union
|
||||
from . import name, rrset, tsig, rdatatype, entropy, edns, rdataclass
|
||||
import hmac
|
||||
|
||||
class Message:
|
||||
def to_wire(self, origin : Optional[name.Name]=None, max_size=0, **kw) -> bytes:
|
||||
...
|
||||
def find_rrset(self, section : List[rrset.RRset], name : name.Name, rdclass : int, rdtype : int,
|
||||
covers=rdatatype.NONE, deleting : Optional[int]=None, create=False,
|
||||
force_unique=False) -> rrset.RRset:
|
||||
...
|
||||
def __init__(self, id : Optional[int] =None) -> None:
|
||||
self.id : int
|
||||
self.flags = 0
|
||||
self.question : List[rrset.RRset] = []
|
||||
self.answer : List[rrset.RRset] = []
|
||||
self.authority : List[rrset.RRset] = []
|
||||
self.additional : List[rrset.RRset] = []
|
||||
self.edns = -1
|
||||
self.ednsflags = 0
|
||||
self.payload = 0
|
||||
self.options : List[edns.Option] = []
|
||||
self.request_payload = 0
|
||||
self.keyring = None
|
||||
self.keyname = None
|
||||
self.keyalgorithm = tsig.default_algorithm
|
||||
self.request_mac = b''
|
||||
self.other_data = b''
|
||||
self.tsig_error = 0
|
||||
self.fudge = 300
|
||||
self.original_id = self.id
|
||||
self.mac = b''
|
||||
self.xfr = False
|
||||
self.origin = None
|
||||
self.tsig_ctx = None
|
||||
self.had_tsig = False
|
||||
self.multi = False
|
||||
self.first = True
|
||||
self.index : Dict[Tuple[rrset.RRset, name.Name, int, int, Union[int,str], int], rrset.RRset] = {}
|
||||
def from_text(a : str) -> Message:
|
||||
...
|
||||
|
||||
def from_wire(wire, keyring : Optional[Dict[name.Name,bytes]] = None, request_mac = b'', xfr=False, origin=None,
|
||||
tsig_ctx : Optional[hmac.HMAC] = None, multi=False, first=True,
|
||||
question_only=False, one_rr_per_rrset=False,
|
||||
ignore_trailing=False) -> Message:
|
||||
...
|
||||
def make_response(query : Message, recursion_available=False, our_payload=8192,
|
||||
fudge=300) -> Message:
|
||||
...
|
||||
|
||||
def make_query(qname : Union[name.Name,str], rdtype : Union[str,int], rdclass : Union[int,str] =rdataclass.IN, use_edns : Optional[bool] = None,
|
||||
want_dnssec=False, ednsflags : Optional[int] = None, payload : Optional[int] = None,
|
||||
request_payload : Optional[int] = None, options : Optional[List[edns.Option]] = None) -> Message:
|
||||
...
|
||||
-994
@@ -1,994 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2001-2017 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
"""DNS Names.
|
||||
"""
|
||||
|
||||
from io import BytesIO
|
||||
import struct
|
||||
import sys
|
||||
import copy
|
||||
import encodings.idna
|
||||
try:
|
||||
import idna
|
||||
have_idna_2008 = True
|
||||
except ImportError:
|
||||
have_idna_2008 = False
|
||||
|
||||
import dns.exception
|
||||
import dns.wiredata
|
||||
|
||||
from ._compat import long, binary_type, text_type, unichr, maybe_decode
|
||||
|
||||
try:
|
||||
maxint = sys.maxint # pylint: disable=sys-max-int
|
||||
except AttributeError:
|
||||
maxint = (1 << (8 * struct.calcsize("P"))) // 2 - 1
|
||||
|
||||
|
||||
# fullcompare() result values
|
||||
|
||||
#: The compared names have no relationship to each other.
|
||||
NAMERELN_NONE = 0
|
||||
#: the first name is a superdomain of the second.
|
||||
NAMERELN_SUPERDOMAIN = 1
|
||||
#: The first name is a subdomain of the second.
|
||||
NAMERELN_SUBDOMAIN = 2
|
||||
#: The compared names are equal.
|
||||
NAMERELN_EQUAL = 3
|
||||
#: The compared names have a common ancestor.
|
||||
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."""
|
||||
|
||||
class NoIDNA2008(dns.exception.DNSException):
|
||||
"""IDNA 2008 processing was requested but the idna module is not
|
||||
available."""
|
||||
|
||||
|
||||
class IDNAException(dns.exception.DNSException):
|
||||
"""IDNA processing raised an exception."""
|
||||
|
||||
supp_kwargs = {'idna_exception'}
|
||||
fmt = "IDNA processing exception: {idna_exception}"
|
||||
|
||||
|
||||
class IDNACodec(object):
|
||||
"""Abstract base class for IDNA encoder/decoders."""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def encode(self, label):
|
||||
raise NotImplementedError
|
||||
|
||||
def decode(self, label):
|
||||
# We do not apply any IDNA policy on decode; we just
|
||||
downcased = label.lower()
|
||||
if downcased.startswith(b'xn--'):
|
||||
try:
|
||||
label = downcased[4:].decode('punycode')
|
||||
except Exception as e:
|
||||
raise IDNAException(idna_exception=e)
|
||||
else:
|
||||
label = maybe_decode(label)
|
||||
return _escapify(label, True)
|
||||
|
||||
|
||||
class IDNA2003Codec(IDNACodec):
|
||||
"""IDNA 2003 encoder/decoder."""
|
||||
|
||||
def __init__(self, strict_decode=False):
|
||||
"""Initialize the IDNA 2003 encoder/decoder.
|
||||
|
||||
*strict_decode* is a ``bool``. If `True`, then IDNA2003 checking
|
||||
is done when decoding. This can cause failures if the name
|
||||
was encoded with IDNA2008. The default is `False`.
|
||||
"""
|
||||
|
||||
super(IDNA2003Codec, self).__init__()
|
||||
self.strict_decode = strict_decode
|
||||
|
||||
def encode(self, label):
|
||||
"""Encode *label*."""
|
||||
|
||||
if label == '':
|
||||
return b''
|
||||
try:
|
||||
return encodings.idna.ToASCII(label)
|
||||
except UnicodeError:
|
||||
raise LabelTooLong
|
||||
|
||||
def decode(self, label):
|
||||
"""Decode *label*."""
|
||||
if not self.strict_decode:
|
||||
return super(IDNA2003Codec, self).decode(label)
|
||||
if label == b'':
|
||||
return u''
|
||||
try:
|
||||
return _escapify(encodings.idna.ToUnicode(label), True)
|
||||
except Exception as e:
|
||||
raise IDNAException(idna_exception=e)
|
||||
|
||||
|
||||
class IDNA2008Codec(IDNACodec):
|
||||
"""IDNA 2008 encoder/decoder.
|
||||
|
||||
*uts_46* is a ``bool``. If True, apply Unicode IDNA
|
||||
compatibility processing as described in Unicode Technical
|
||||
Standard #46 (http://unicode.org/reports/tr46/).
|
||||
If False, do not apply the mapping. The default is False.
|
||||
|
||||
*transitional* is a ``bool``: If True, use the
|
||||
"transitional" mode described in Unicode Technical Standard
|
||||
#46. The default is False.
|
||||
|
||||
*allow_pure_ascii* is a ``bool``. If True, then a label which
|
||||
consists of only ASCII characters is allowed. This is less
|
||||
strict than regular IDNA 2008, but is also necessary for mixed
|
||||
names, e.g. a name with starting with "_sip._tcp." and ending
|
||||
in an IDN suffix which would otherwise be disallowed. The
|
||||
default is False.
|
||||
|
||||
*strict_decode* is a ``bool``: If True, then IDNA2008 checking
|
||||
is done when decoding. This can cause failures if the name
|
||||
was encoded with IDNA2003. The default is False.
|
||||
"""
|
||||
|
||||
def __init__(self, uts_46=False, transitional=False,
|
||||
allow_pure_ascii=False, strict_decode=False):
|
||||
"""Initialize the IDNA 2008 encoder/decoder."""
|
||||
super(IDNA2008Codec, self).__init__()
|
||||
self.uts_46 = uts_46
|
||||
self.transitional = transitional
|
||||
self.allow_pure_ascii = allow_pure_ascii
|
||||
self.strict_decode = strict_decode
|
||||
|
||||
def is_all_ascii(self, label):
|
||||
for c in label:
|
||||
if ord(c) > 0x7f:
|
||||
return False
|
||||
return True
|
||||
|
||||
def encode(self, label):
|
||||
if label == '':
|
||||
return b''
|
||||
if self.allow_pure_ascii and self.is_all_ascii(label):
|
||||
return label.encode('ascii')
|
||||
if not have_idna_2008:
|
||||
raise NoIDNA2008
|
||||
try:
|
||||
if self.uts_46:
|
||||
label = idna.uts46_remap(label, False, self.transitional)
|
||||
return idna.alabel(label)
|
||||
except idna.IDNAError as e:
|
||||
raise IDNAException(idna_exception=e)
|
||||
|
||||
def decode(self, label):
|
||||
if not self.strict_decode:
|
||||
return super(IDNA2008Codec, self).decode(label)
|
||||
if label == b'':
|
||||
return u''
|
||||
if not have_idna_2008:
|
||||
raise NoIDNA2008
|
||||
try:
|
||||
if self.uts_46:
|
||||
label = idna.uts46_remap(label, False, False)
|
||||
return _escapify(idna.ulabel(label), True)
|
||||
except idna.IDNAError as e:
|
||||
raise IDNAException(idna_exception=e)
|
||||
|
||||
_escaped = bytearray(b'"().;\\@$')
|
||||
|
||||
IDNA_2003_Practical = IDNA2003Codec(False)
|
||||
IDNA_2003_Strict = IDNA2003Codec(True)
|
||||
IDNA_2003 = IDNA_2003_Practical
|
||||
IDNA_2008_Practical = IDNA2008Codec(True, False, True, False)
|
||||
IDNA_2008_UTS_46 = IDNA2008Codec(True, False, False, False)
|
||||
IDNA_2008_Strict = IDNA2008Codec(False, False, False, True)
|
||||
IDNA_2008_Transitional = IDNA2008Codec(True, True, False, False)
|
||||
IDNA_2008 = IDNA_2008_Practical
|
||||
|
||||
def _escapify(label, unicode_mode=False):
|
||||
"""Escape the characters in label which need it.
|
||||
@param unicode_mode: escapify only special and whitespace (<= 0x20)
|
||||
characters
|
||||
@returns: the escaped string
|
||||
@rtype: string"""
|
||||
if not unicode_mode:
|
||||
text = ''
|
||||
if isinstance(label, text_type):
|
||||
label = label.encode()
|
||||
for c in bytearray(label):
|
||||
if c in _escaped:
|
||||
text += '\\' + chr(c)
|
||||
elif c > 0x20 and c < 0x7F:
|
||||
text += chr(c)
|
||||
else:
|
||||
text += '\\%03d' % c
|
||||
return text.encode()
|
||||
|
||||
text = u''
|
||||
if isinstance(label, binary_type):
|
||||
label = label.decode()
|
||||
for c in label:
|
||||
if c > u'\x20' and c < u'\x7f':
|
||||
text += c
|
||||
else:
|
||||
if c >= u'\x7f':
|
||||
text += c
|
||||
else:
|
||||
text += u'\\%03d' % ord(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 ``dns.name.NameTooLong`` if the name as a whole is too long.
|
||||
|
||||
Raises ``dns.name.EmptyLabel`` if a label is empty (i.e. the root
|
||||
label) and appears in a position other than the end of the label
|
||||
sequence
|
||||
|
||||
"""
|
||||
|
||||
l = len(labels)
|
||||
total = 0
|
||||
i = -1
|
||||
j = 0
|
||||
for label in labels:
|
||||
ll = len(label)
|
||||
total += ll + 1
|
||||
if ll > 63:
|
||||
raise LabelTooLong
|
||||
if i < 0 and label == b'':
|
||||
i = j
|
||||
j += 1
|
||||
if total > 255:
|
||||
raise NameTooLong
|
||||
if i >= 0 and i != l - 1:
|
||||
raise EmptyLabel
|
||||
|
||||
|
||||
def _maybe_convert_to_binary(label):
|
||||
"""If label is ``text``, convert it to ``binary``. If it is already
|
||||
``binary`` just return it.
|
||||
|
||||
"""
|
||||
|
||||
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. Each label is a `binary` in DNS wire format. Instances
|
||||
of the class are immutable.
|
||||
"""
|
||||
|
||||
__slots__ = ['labels']
|
||||
|
||||
def __init__(self, labels):
|
||||
"""*labels* is any iterable whose values are ``text`` or ``binary``.
|
||||
"""
|
||||
|
||||
labels = [_maybe_convert_to_binary(x) for x in labels]
|
||||
super(Name, self).__setattr__('labels', tuple(labels))
|
||||
_validate_labels(self.labels)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
# Names are immutable
|
||||
raise TypeError("object doesn't support attribute assignment")
|
||||
|
||||
def __copy__(self):
|
||||
return Name(self.labels)
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
return Name(copy.deepcopy(self.labels, memo))
|
||||
|
||||
def __getstate__(self):
|
||||
# Names can be pickled
|
||||
return {'labels': self.labels}
|
||||
|
||||
def __setstate__(self, state):
|
||||
super(Name, self).__setattr__('labels', state['labels'])
|
||||
_validate_labels(self.labels)
|
||||
|
||||
def is_absolute(self):
|
||||
"""Is the most significant label of this name the root label?
|
||||
|
||||
Returns a ``bool``.
|
||||
"""
|
||||
|
||||
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 '*'?)
|
||||
|
||||
Returns a ``bool``.
|
||||
"""
|
||||
|
||||
return len(self.labels) > 0 and self.labels[0] == b'*'
|
||||
|
||||
def __hash__(self):
|
||||
"""Return a case-insensitive hash of the name.
|
||||
|
||||
Returns an ``int``.
|
||||
"""
|
||||
|
||||
h = long(0)
|
||||
for label in self.labels:
|
||||
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)``.
|
||||
|
||||
*relation* describes the relation ship between the names,
|
||||
and is one of: ``dns.name.NAMERELN_NONE``,
|
||||
``dns.name.NAMERELN_SUPERDOMAIN``, ``dns.name.NAMERELN_SUBDOMAIN``,
|
||||
``dns.name.NAMERELN_EQUAL``, or ``dns.name.NAMERELN_COMMONANCESTOR``.
|
||||
|
||||
*order* is < 0 if *self* < *other*, > 0 if *self* > *other*, and ==
|
||||
0 if *self* == *other*. A relative name is always less than an
|
||||
absolute name. If both names have the same relativity, then
|
||||
the DNSSEC order relation is used to order them.
|
||||
|
||||
*nlabels* is the number of significant labels that the two names
|
||||
have in common.
|
||||
|
||||
Here are some examples. Names ending in "." are absolute names,
|
||||
those not ending in "." are relative names.
|
||||
|
||||
============= ============= =========== ===== =======
|
||||
self other relation order nlabels
|
||||
============= ============= =========== ===== =======
|
||||
www.example. www.example. equal 0 3
|
||||
www.example. example. subdomain > 0 2
|
||||
example. www.example. superdomain < 0 2
|
||||
example1.com. example2.com. common anc. < 0 2
|
||||
example1 example2. none < 0 0
|
||||
example1. example2 none > 0 0
|
||||
============= ============= =========== ===== =======
|
||||
"""
|
||||
|
||||
sabs = self.is_absolute()
|
||||
oabs = other.is_absolute()
|
||||
if sabs != oabs:
|
||||
if sabs:
|
||||
return (NAMERELN_NONE, 1, 0)
|
||||
else:
|
||||
return (NAMERELN_NONE, -1, 0)
|
||||
l1 = len(self.labels)
|
||||
l2 = len(other.labels)
|
||||
ldiff = l1 - l2
|
||||
if ldiff < 0:
|
||||
l = l1
|
||||
else:
|
||||
l = l2
|
||||
|
||||
order = 0
|
||||
nlabels = 0
|
||||
namereln = NAMERELN_NONE
|
||||
while l > 0:
|
||||
l -= 1
|
||||
l1 -= 1
|
||||
l2 -= 1
|
||||
label1 = self.labels[l1].lower()
|
||||
label2 = other.labels[l2].lower()
|
||||
if label1 < label2:
|
||||
order = -1
|
||||
if nlabels > 0:
|
||||
namereln = NAMERELN_COMMONANCESTOR
|
||||
return (namereln, order, nlabels)
|
||||
elif label1 > label2:
|
||||
order = 1
|
||||
if nlabels > 0:
|
||||
namereln = NAMERELN_COMMONANCESTOR
|
||||
return (namereln, order, nlabels)
|
||||
nlabels += 1
|
||||
order = ldiff
|
||||
if ldiff < 0:
|
||||
namereln = NAMERELN_SUPERDOMAIN
|
||||
elif ldiff > 0:
|
||||
namereln = NAMERELN_SUBDOMAIN
|
||||
else:
|
||||
namereln = NAMERELN_EQUAL
|
||||
return (namereln, order, nlabels)
|
||||
|
||||
def is_subdomain(self, other):
|
||||
"""Is self a subdomain of other?
|
||||
|
||||
Note that the notion of subdomain includes equality, e.g.
|
||||
"dnpython.org" is a subdomain of itself.
|
||||
|
||||
Returns a ``bool``.
|
||||
"""
|
||||
|
||||
(nr, o, nl) = self.fullcompare(other)
|
||||
if nr == NAMERELN_SUBDOMAIN or nr == NAMERELN_EQUAL:
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_superdomain(self, other):
|
||||
"""Is self a superdomain of other?
|
||||
|
||||
Note that the notion of superdomain includes equality, e.g.
|
||||
"dnpython.org" is a superdomain of itself.
|
||||
|
||||
Returns a ``bool``.
|
||||
"""
|
||||
|
||||
(nr, o, nl) = self.fullcompare(other)
|
||||
if nr == NAMERELN_SUPERDOMAIN or nr == NAMERELN_EQUAL:
|
||||
return True
|
||||
return False
|
||||
|
||||
def canonicalize(self):
|
||||
"""Return a name which is equal to the current name, but is in
|
||||
DNSSEC canonical form.
|
||||
"""
|
||||
|
||||
return Name([x.lower() for x in self.labels])
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Name):
|
||||
return self.fullcompare(other)[1] == 0
|
||||
else:
|
||||
return False
|
||||
|
||||
def __ne__(self, other):
|
||||
if isinstance(other, Name):
|
||||
return self.fullcompare(other)[1] != 0
|
||||
else:
|
||||
return True
|
||||
|
||||
def __lt__(self, other):
|
||||
if isinstance(other, Name):
|
||||
return self.fullcompare(other)[1] < 0
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
def __le__(self, other):
|
||||
if isinstance(other, Name):
|
||||
return self.fullcompare(other)[1] <= 0
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
def __ge__(self, other):
|
||||
if isinstance(other, Name):
|
||||
return self.fullcompare(other)[1] >= 0
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
def __gt__(self, other):
|
||||
if isinstance(other, Name):
|
||||
return self.fullcompare(other)[1] > 0
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
def __repr__(self):
|
||||
return '<DNS name ' + self.__str__() + '>'
|
||||
|
||||
def __str__(self):
|
||||
return self.to_text(False)
|
||||
|
||||
def to_text(self, omit_final_dot=False):
|
||||
"""Convert name to DNS text format.
|
||||
|
||||
*omit_final_dot* is a ``bool``. If True, don't emit the final
|
||||
dot (denoting the root label) for absolute names. The default
|
||||
is False.
|
||||
|
||||
Returns a ``text``.
|
||||
"""
|
||||
|
||||
if len(self.labels) == 0:
|
||||
return maybe_decode(b'@')
|
||||
if len(self.labels) == 1 and self.labels[0] == b'':
|
||||
return maybe_decode(b'.')
|
||||
if omit_final_dot and self.is_absolute():
|
||||
l = self.labels[:-1]
|
||||
else:
|
||||
l = self.labels
|
||||
s = b'.'.join(map(_escapify, l))
|
||||
return maybe_decode(s)
|
||||
|
||||
def to_unicode(self, omit_final_dot=False, idna_codec=None):
|
||||
"""Convert name to Unicode text format.
|
||||
|
||||
IDN ACE labels are converted to Unicode.
|
||||
|
||||
*omit_final_dot* is a ``bool``. If True, don't emit the final
|
||||
dot (denoting the root label) for absolute names. The default
|
||||
is False.
|
||||
*idna_codec* specifies the IDNA encoder/decoder. If None, the
|
||||
dns.name.IDNA_2003_Practical encoder/decoder is used.
|
||||
The IDNA_2003_Practical decoder does
|
||||
not impose any policy, it just decodes punycode, so if you
|
||||
don't want checking for compliance, you can use this decoder
|
||||
for IDNA2008 as well.
|
||||
|
||||
Returns a ``text``.
|
||||
"""
|
||||
|
||||
if len(self.labels) == 0:
|
||||
return u'@'
|
||||
if len(self.labels) == 1 and self.labels[0] == b'':
|
||||
return u'.'
|
||||
if omit_final_dot and self.is_absolute():
|
||||
l = self.labels[:-1]
|
||||
else:
|
||||
l = self.labels
|
||||
if idna_codec is None:
|
||||
idna_codec = IDNA_2003_Practical
|
||||
return u'.'.join([idna_codec.decode(x) for x in l])
|
||||
|
||||
def to_digestable(self, origin=None):
|
||||
"""Convert name to a format suitable for digesting in hashes.
|
||||
|
||||
The name is canonicalized and converted to uncompressed wire
|
||||
format. All names in wire format are absolute. If the name
|
||||
is a relative name, then an origin must be supplied.
|
||||
|
||||
*origin* is a ``dns.name.Name`` or ``None``. If the name is
|
||||
relative and origin is not ``None``, then origin will be appended
|
||||
to the name.
|
||||
|
||||
Raises ``dns.name.NeedAbsoluteNameOrOrigin`` if the name is
|
||||
relative and no origin was provided.
|
||||
|
||||
Returns a ``binary``.
|
||||
"""
|
||||
|
||||
if not self.is_absolute():
|
||||
if origin is None or not origin.is_absolute():
|
||||
raise NeedAbsoluteNameOrOrigin
|
||||
labels = list(self.labels)
|
||||
labels.extend(list(origin.labels))
|
||||
else:
|
||||
labels = self.labels
|
||||
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):
|
||||
"""Convert name to wire format, possibly compressing it.
|
||||
|
||||
*file* is the file where the name is emitted (typically a
|
||||
BytesIO file). If ``None`` (the default), a ``binary``
|
||||
containing the wire name will be returned.
|
||||
|
||||
*compress*, a ``dict``, is the compression table to use. If
|
||||
``None`` (the default), names will not be compressed.
|
||||
|
||||
*origin* is a ``dns.name.Name`` or ``None``. If the name is
|
||||
relative and origin is not ``None``, then *origin* will be appended
|
||||
to it.
|
||||
|
||||
Raises ``dns.name.NeedAbsoluteNameOrOrigin`` if the name is
|
||||
relative and no origin was provided.
|
||||
|
||||
Returns a ``binary`` or ``None``.
|
||||
"""
|
||||
|
||||
if file is None:
|
||||
file = BytesIO()
|
||||
want_return = True
|
||||
else:
|
||||
want_return = False
|
||||
|
||||
if not self.is_absolute():
|
||||
if origin is None or not origin.is_absolute():
|
||||
raise NeedAbsoluteNameOrOrigin
|
||||
labels = list(self.labels)
|
||||
labels.extend(list(origin.labels))
|
||||
else:
|
||||
labels = self.labels
|
||||
i = 0
|
||||
for label in labels:
|
||||
n = Name(labels[i:])
|
||||
i += 1
|
||||
if compress is not None:
|
||||
pos = compress.get(n)
|
||||
else:
|
||||
pos = None
|
||||
if pos is not None:
|
||||
value = 0xc000 + pos
|
||||
s = struct.pack('!H', value)
|
||||
file.write(s)
|
||||
break
|
||||
else:
|
||||
if compress is not None and len(n) > 1:
|
||||
pos = file.tell()
|
||||
if pos <= 0x3fff:
|
||||
compress[n] = pos
|
||||
l = len(label)
|
||||
file.write(struct.pack('!B', l))
|
||||
if l > 0:
|
||||
file.write(label)
|
||||
if want_return:
|
||||
return file.getvalue()
|
||||
|
||||
def __len__(self):
|
||||
"""The length of the name (in labels).
|
||||
|
||||
Returns an ``int``.
|
||||
"""
|
||||
|
||||
return len(self.labels)
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self.labels[index]
|
||||
|
||||
def __add__(self, other):
|
||||
return self.concatenate(other)
|
||||
|
||||
def __sub__(self, other):
|
||||
return self.relativize(other)
|
||||
|
||||
def split(self, depth):
|
||||
"""Split a name into a prefix and suffix names at the specified depth.
|
||||
|
||||
*depth* is an ``int`` specifying the number of labels in the suffix
|
||||
|
||||
Raises ``ValueError`` if *depth* was not >= 0 and <= the length of the
|
||||
name.
|
||||
|
||||
Returns the tuple ``(prefix, suffix)``.
|
||||
"""
|
||||
|
||||
l = len(self.labels)
|
||||
if depth == 0:
|
||||
return (self, dns.name.empty)
|
||||
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:]))
|
||||
|
||||
def concatenate(self, other):
|
||||
"""Return a new name which is the concatenation of self and other.
|
||||
|
||||
Raises ``dns.name.AbsoluteConcatenation`` if the name is
|
||||
absolute and *other* is not the empty name.
|
||||
|
||||
Returns a ``dns.name.Name``.
|
||||
"""
|
||||
|
||||
if self.is_absolute() and len(other) > 0:
|
||||
raise AbsoluteConcatenation
|
||||
labels = list(self.labels)
|
||||
labels.extend(list(other.labels))
|
||||
return Name(labels)
|
||||
|
||||
def relativize(self, origin):
|
||||
"""If the name is a subdomain of *origin*, return a new name which is
|
||||
the name relative to origin. Otherwise return the name.
|
||||
|
||||
For example, relativizing ``www.dnspython.org.`` to origin
|
||||
``dnspython.org.`` returns the name ``www``. Relativizing ``example.``
|
||||
to origin ``dnspython.org.`` returns ``example.``.
|
||||
|
||||
Returns a ``dns.name.Name``.
|
||||
"""
|
||||
|
||||
if origin is not None and self.is_subdomain(origin):
|
||||
return Name(self[: -len(origin)])
|
||||
else:
|
||||
return self
|
||||
|
||||
def derelativize(self, origin):
|
||||
"""If the name is a relative name, return a new name which is the
|
||||
concatenation of the name and origin. Otherwise return the name.
|
||||
|
||||
For example, derelativizing ``www`` to origin ``dnspython.org.``
|
||||
returns the name ``www.dnspython.org.``. Derelativizing ``example.``
|
||||
to origin ``dnspython.org.`` returns ``example.``.
|
||||
|
||||
Returns a ``dns.name.Name``.
|
||||
"""
|
||||
|
||||
if not self.is_absolute():
|
||||
return self.concatenate(origin)
|
||||
else:
|
||||
return self
|
||||
|
||||
def choose_relativity(self, origin=None, relativize=True):
|
||||
"""Return a name with the relativity desired by the caller.
|
||||
|
||||
If *origin* is ``None``, then the name is returned.
|
||||
Otherwise, if *relativize* is ``True`` the name is
|
||||
relativized, and if *relativize* is ``False`` the name is
|
||||
derelativized.
|
||||
|
||||
Returns a ``dns.name.Name``.
|
||||
"""
|
||||
|
||||
if origin:
|
||||
if relativize:
|
||||
return self.relativize(origin)
|
||||
else:
|
||||
return self.derelativize(origin)
|
||||
else:
|
||||
return self
|
||||
|
||||
def parent(self):
|
||||
"""Return the parent of the name.
|
||||
|
||||
For example, the parent of ``www.dnspython.org.`` is ``dnspython.org``.
|
||||
|
||||
Raises ``dns.name.NoParent`` if the name is either the root name or the
|
||||
empty name, and thus has no parent.
|
||||
|
||||
Returns a ``dns.name.Name``.
|
||||
"""
|
||||
|
||||
if self == root or self == empty:
|
||||
raise NoParent
|
||||
return Name(self.labels[1:])
|
||||
|
||||
#: The root name, '.'
|
||||
root = Name([b''])
|
||||
|
||||
#: The empty name.
|
||||
empty = Name([])
|
||||
|
||||
def from_unicode(text, origin=root, idna_codec=None):
|
||||
"""Convert unicode text into a Name object.
|
||||
|
||||
Labels are encoded in IDN ACE form according to rules specified by
|
||||
the IDNA codec.
|
||||
|
||||
*text*, a ``text``, is the text to convert into a name.
|
||||
|
||||
*origin*, a ``dns.name.Name``, specifies the origin to
|
||||
append to non-absolute names. The default is the root name.
|
||||
|
||||
*idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA
|
||||
encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder
|
||||
is used.
|
||||
|
||||
Returns a ``dns.name.Name``.
|
||||
"""
|
||||
|
||||
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")
|
||||
labels = []
|
||||
label = u''
|
||||
escaping = False
|
||||
edigits = 0
|
||||
total = 0
|
||||
if idna_codec is None:
|
||||
idna_codec = IDNA_2003
|
||||
if text == u'@':
|
||||
text = u''
|
||||
if text:
|
||||
if text == u'.':
|
||||
return Name([b'']) # no Unicode "u" on this constant!
|
||||
for c in text:
|
||||
if escaping:
|
||||
if edigits == 0:
|
||||
if c.isdigit():
|
||||
total = int(c)
|
||||
edigits += 1
|
||||
else:
|
||||
label += c
|
||||
escaping = False
|
||||
else:
|
||||
if not c.isdigit():
|
||||
raise BadEscape
|
||||
total *= 10
|
||||
total += int(c)
|
||||
edigits += 1
|
||||
if edigits == 3:
|
||||
escaping = False
|
||||
label += unichr(total)
|
||||
elif c in [u'.', u'\u3002', u'\uff0e', u'\uff61']:
|
||||
if len(label) == 0:
|
||||
raise EmptyLabel
|
||||
labels.append(idna_codec.encode(label))
|
||||
label = u''
|
||||
elif c == u'\\':
|
||||
escaping = True
|
||||
edigits = 0
|
||||
total = 0
|
||||
else:
|
||||
label += c
|
||||
if escaping:
|
||||
raise BadEscape
|
||||
if len(label) > 0:
|
||||
labels.append(idna_codec.encode(label))
|
||||
else:
|
||||
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, idna_codec=None):
|
||||
"""Convert text into a Name object.
|
||||
|
||||
*text*, a ``text``, is the text to convert into a name.
|
||||
|
||||
*origin*, a ``dns.name.Name``, specifies the origin to
|
||||
append to non-absolute names. The default is the root name.
|
||||
|
||||
*idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA
|
||||
encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder
|
||||
is used.
|
||||
|
||||
Returns a ``dns.name.Name``.
|
||||
"""
|
||||
|
||||
if isinstance(text, text_type):
|
||||
return from_unicode(text, origin, idna_codec)
|
||||
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 = b''
|
||||
escaping = False
|
||||
edigits = 0
|
||||
total = 0
|
||||
if text == b'@':
|
||||
text = b''
|
||||
if text:
|
||||
if text == b'.':
|
||||
return Name([b''])
|
||||
for c in bytearray(text):
|
||||
byte_ = struct.pack('!B', c)
|
||||
if escaping:
|
||||
if edigits == 0:
|
||||
if byte_.isdigit():
|
||||
total = int(byte_)
|
||||
edigits += 1
|
||||
else:
|
||||
label += byte_
|
||||
escaping = False
|
||||
else:
|
||||
if not byte_.isdigit():
|
||||
raise BadEscape
|
||||
total *= 10
|
||||
total += int(byte_)
|
||||
edigits += 1
|
||||
if edigits == 3:
|
||||
escaping = False
|
||||
label += struct.pack('!B', total)
|
||||
elif byte_ == b'.':
|
||||
if len(label) == 0:
|
||||
raise EmptyLabel
|
||||
labels.append(label)
|
||||
label = b''
|
||||
elif byte_ == b'\\':
|
||||
escaping = True
|
||||
edigits = 0
|
||||
total = 0
|
||||
else:
|
||||
label += byte_
|
||||
if escaping:
|
||||
raise BadEscape
|
||||
if len(label) > 0:
|
||||
labels.append(label)
|
||||
else:
|
||||
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.
|
||||
|
||||
*message* is a ``binary`` containing an entire DNS message in DNS
|
||||
wire form.
|
||||
|
||||
*current*, an ``int``, is the offset of the beginning of the name
|
||||
from the start of the message
|
||||
|
||||
Raises ``dns.name.BadPointer`` if a compression pointer did not
|
||||
point backwards in the message.
|
||||
|
||||
Raises ``dns.name.BadLabelType`` if an invalid label type was encountered.
|
||||
|
||||
Returns a ``(dns.name.Name, int)`` tuple consisting of the name
|
||||
that was read and the number of bytes of the wire format message
|
||||
which were consumed reading it.
|
||||
"""
|
||||
|
||||
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 = message[current]
|
||||
current += 1
|
||||
cused = 1
|
||||
while count != 0:
|
||||
if count < 64:
|
||||
labels.append(message[current: current + count].unwrap())
|
||||
current += count
|
||||
if hops == 0:
|
||||
cused += count
|
||||
elif count >= 192:
|
||||
current = (count & 0x3f) * 256 + message[current]
|
||||
if hops == 0:
|
||||
cused += 1
|
||||
if current >= biggest_pointer:
|
||||
raise BadPointer
|
||||
biggest_pointer = current
|
||||
hops += 1
|
||||
else:
|
||||
raise BadLabelType
|
||||
count = message[current]
|
||||
current += 1
|
||||
if hops == 0:
|
||||
cused += 1
|
||||
labels.append('')
|
||||
return (Name(labels), cused)
|
||||
@@ -1,35 +0,0 @@
|
||||
from typing import Optional, Union, Tuple, Iterable, List
|
||||
|
||||
class Name:
|
||||
def is_subdomain(self, o : Name) -> bool: ...
|
||||
def is_superdomain(self, o : Name) -> bool: ...
|
||||
def __init__(self, labels : Iterable[Union[bytes,str]]) -> None:
|
||||
self.labels : List[bytes]
|
||||
def is_absolute(self) -> bool: ...
|
||||
def is_wild(self) -> bool: ...
|
||||
def fullcompare(self, other) -> Tuple[int,int,int]: ...
|
||||
def canonicalize(self) -> Name: ...
|
||||
def __lt__(self, other : Name): ...
|
||||
def __le__(self, other : Name): ...
|
||||
def __ge__(self, other : Name): ...
|
||||
def __gt__(self, other : Name): ...
|
||||
def to_text(self, omit_final_dot=False) -> str: ...
|
||||
def to_unicode(self, omit_final_dot=False, idna_codec=None) -> str: ...
|
||||
def to_digestable(self, origin=None) -> bytes: ...
|
||||
def to_wire(self, file=None, compress=None, origin=None) -> Optional[bytes]: ...
|
||||
def __add__(self, other : Name): ...
|
||||
def __sub__(self, other : Name): ...
|
||||
def split(self, depth) -> List[Tuple[str,str]]: ...
|
||||
def concatenate(self, other : Name) -> Name: ...
|
||||
def relativize(self, origin): ...
|
||||
def derelativize(self, origin): ...
|
||||
def choose_relativity(self, origin : Optional[Name] = None, relativize=True): ...
|
||||
def parent(self) -> Name: ...
|
||||
|
||||
class IDNACodec:
|
||||
pass
|
||||
|
||||
def from_text(text, origin : Optional[Name] = Name('.'), idna_codec : Optional[IDNACodec] = None) -> Name:
|
||||
...
|
||||
|
||||
empty : Name
|
||||
@@ -1,108 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2003-2017 Nominum, Inc.
|
||||
# Copyright (C) 2016 Coresec Systems AB
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND CORESEC SYSTEMS AB DISCLAIMS ALL
|
||||
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL CORESEC
|
||||
# SYSTEMS AB BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
|
||||
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
"""DNS name dictionary"""
|
||||
|
||||
import collections
|
||||
import dns.name
|
||||
from ._compat import xrange
|
||||
|
||||
|
||||
class NameDict(collections.MutableMapping):
|
||||
"""A dictionary whose keys are dns.name.Name objects.
|
||||
|
||||
In addition to being like a regular Python dictionary, this
|
||||
dictionary can also get the deepest match for a given key.
|
||||
"""
|
||||
|
||||
__slots__ = ["max_depth", "max_depth_items", "__store"]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NameDict, self).__init__()
|
||||
self.__store = dict()
|
||||
#: the maximum depth of the keys that have ever been added
|
||||
self.max_depth = 0
|
||||
#: the number of items of maximum depth
|
||||
self.max_depth_items = 0
|
||||
self.update(dict(*args, **kwargs))
|
||||
|
||||
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:
|
||||
self.max_depth = len(key)
|
||||
self.max_depth_items = 1
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.__store[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if not isinstance(key, dns.name.Name):
|
||||
raise ValueError('NameDict key must be a name')
|
||||
self.__store[key] = value
|
||||
self.__update_max_depth(key)
|
||||
|
||||
def __delitem__(self, key):
|
||||
value = self.__store.pop(key)
|
||||
if len(value) == self.max_depth:
|
||||
self.max_depth_items = self.max_depth_items - 1
|
||||
if self.max_depth_items == 0:
|
||||
self.max_depth = 0
|
||||
for k in self.__store:
|
||||
self.__update_max_depth(k)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.__store)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.__store)
|
||||
|
||||
def has_key(self, key):
|
||||
return key in self.__store
|
||||
|
||||
def get_deepest_match(self, name):
|
||||
"""Find the deepest match to *fname* in the dictionary.
|
||||
|
||||
The deepest match is the longest name in the dictionary which is
|
||||
a superdomain of *name*. Note that *superdomain* includes matching
|
||||
*name* itself.
|
||||
|
||||
*name*, a ``dns.name.Name``, the name to find.
|
||||
|
||||
Returns a ``(key, value)`` where *key* is the deepest
|
||||
``dns.name.Name``, and *value* is the value associated with *key*.
|
||||
"""
|
||||
|
||||
depth = len(name)
|
||||
if depth > self.max_depth:
|
||||
depth = self.max_depth
|
||||
for i in xrange(-depth, 0):
|
||||
n = dns.name.Name(name[i:])
|
||||
if n in self:
|
||||
return (n, self[n])
|
||||
v = self[dns.name.empty]
|
||||
return (dns.name.empty, v)
|
||||
-182
@@ -1,182 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2001-2017 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
"""DNS nodes. A node is a set of rdatasets."""
|
||||
|
||||
from io import StringIO
|
||||
|
||||
import dns.rdataset
|
||||
import dns.rdatatype
|
||||
import dns.renderer
|
||||
|
||||
|
||||
class Node(object):
|
||||
|
||||
"""A Node is a set of rdatasets."""
|
||||
|
||||
__slots__ = ['rdatasets']
|
||||
|
||||
def __init__(self):
|
||||
#: the set of rdatsets, represented as a list.
|
||||
self.rdatasets = []
|
||||
|
||||
def to_text(self, name, **kw):
|
||||
"""Convert a node to text format.
|
||||
|
||||
Each rdataset at the node is printed. Any keyword arguments
|
||||
to this method are passed on to the rdataset's to_text() method.
|
||||
|
||||
*name*, a ``dns.name.Name`` or ``text``, the owner name of the rdatasets.
|
||||
|
||||
Returns a ``text``.
|
||||
"""
|
||||
|
||||
s = StringIO()
|
||||
for rds in self.rdatasets:
|
||||
if len(rds) > 0:
|
||||
s.write(rds.to_text(name, **kw))
|
||||
s.write(u'\n')
|
||||
return s.getvalue()[:-1]
|
||||
|
||||
def __repr__(self):
|
||||
return '<DNS node ' + str(id(self)) + '>'
|
||||
|
||||
def __eq__(self, other):
|
||||
#
|
||||
# This is inefficient. Good thing we don't need to do it much.
|
||||
#
|
||||
for rd in self.rdatasets:
|
||||
if rd not in other.rdatasets:
|
||||
return False
|
||||
for rd in other.rdatasets:
|
||||
if rd not in self.rdatasets:
|
||||
return False
|
||||
return True
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.rdatasets)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.rdatasets)
|
||||
|
||||
def find_rdataset(self, rdclass, rdtype, covers=dns.rdatatype.NONE,
|
||||
create=False):
|
||||
"""Find an rdataset matching the specified properties in the
|
||||
current node.
|
||||
|
||||
*rdclass*, an ``int``, the class of the rdataset.
|
||||
|
||||
*rdtype*, an ``int``, the type of the rdataset.
|
||||
|
||||
*covers*, an ``int``, the covered type. Usually this value is
|
||||
dns.rdatatype.NONE, but if the rdtype is dns.rdatatype.SIG or
|
||||
dns.rdatatype.RRSIG, then the covers value will be the rdata
|
||||
type the SIG/RRSIG covers. The library treats the SIG and RRSIG
|
||||
types as if they were a family of
|
||||
types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). This makes RRSIGs much
|
||||
easier to work with than if RRSIGs covering different rdata
|
||||
types were aggregated into a single RRSIG rdataset.
|
||||
|
||||
*create*, a ``bool``. If True, create the rdataset if it is not found.
|
||||
|
||||
Raises ``KeyError`` if an rdataset of the desired type and class does
|
||||
not exist and *create* is not ``True``.
|
||||
|
||||
Returns a ``dns.rdataset.Rdataset``.
|
||||
"""
|
||||
|
||||
for rds in self.rdatasets:
|
||||
if rds.match(rdclass, rdtype, covers):
|
||||
return rds
|
||||
if not create:
|
||||
raise KeyError
|
||||
rds = dns.rdataset.Rdataset(rdclass, rdtype)
|
||||
self.rdatasets.append(rds)
|
||||
return rds
|
||||
|
||||
def get_rdataset(self, rdclass, rdtype, covers=dns.rdatatype.NONE,
|
||||
create=False):
|
||||
"""Get an rdataset matching the specified properties in the
|
||||
current node.
|
||||
|
||||
None is returned if an rdataset of the specified type and
|
||||
class does not exist and *create* is not ``True``.
|
||||
|
||||
*rdclass*, an ``int``, the class of the rdataset.
|
||||
|
||||
*rdtype*, an ``int``, the type of the rdataset.
|
||||
|
||||
*covers*, an ``int``, the covered type. Usually this value is
|
||||
dns.rdatatype.NONE, but if the rdtype is dns.rdatatype.SIG or
|
||||
dns.rdatatype.RRSIG, then the covers value will be the rdata
|
||||
type the SIG/RRSIG covers. The library treats the SIG and RRSIG
|
||||
types as if they were a family of
|
||||
types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). This makes RRSIGs much
|
||||
easier to work with than if RRSIGs covering different rdata
|
||||
types were aggregated into a single RRSIG rdataset.
|
||||
|
||||
*create*, a ``bool``. If True, create the rdataset if it is not found.
|
||||
|
||||
Returns a ``dns.rdataset.Rdataset`` or ``None``.
|
||||
"""
|
||||
|
||||
try:
|
||||
rds = self.find_rdataset(rdclass, rdtype, covers, create)
|
||||
except KeyError:
|
||||
rds = None
|
||||
return rds
|
||||
|
||||
def delete_rdataset(self, rdclass, rdtype, covers=dns.rdatatype.NONE):
|
||||
"""Delete the rdataset matching the specified properties in the
|
||||
current node.
|
||||
|
||||
If a matching rdataset does not exist, it is not an error.
|
||||
|
||||
*rdclass*, an ``int``, the class of the rdataset.
|
||||
|
||||
*rdtype*, an ``int``, the type of the rdataset.
|
||||
|
||||
*covers*, an ``int``, the covered type.
|
||||
"""
|
||||
|
||||
rds = self.get_rdataset(rdclass, rdtype, covers)
|
||||
if rds is not None:
|
||||
self.rdatasets.remove(rds)
|
||||
|
||||
def replace_rdataset(self, replacement):
|
||||
"""Replace an rdataset.
|
||||
|
||||
It is not an error if there is no rdataset matching *replacement*.
|
||||
|
||||
Ownership of the *replacement* object is transferred to the node;
|
||||
in other words, this method does not store a copy of *replacement*
|
||||
at the node, it stores *replacement* itself.
|
||||
|
||||
*replacement*, a ``dns.rdataset.Rdataset``.
|
||||
|
||||
Raises ``ValueError`` if *replacement* is not a
|
||||
``dns.rdataset.Rdataset``.
|
||||
"""
|
||||
|
||||
if not isinstance(replacement, dns.rdataset.Rdataset):
|
||||
raise ValueError('replacement is not an rdataset')
|
||||
self.delete_rdataset(replacement.rdclass, replacement.rdtype,
|
||||
replacement.covers)
|
||||
self.rdatasets.append(replacement)
|
||||
@@ -1,17 +0,0 @@
|
||||
from typing import List, Optional, Union
|
||||
from . import rdataset, rdatatype, name
|
||||
class Node:
|
||||
def __init__(self):
|
||||
self.rdatasets : List[rdataset.Rdataset]
|
||||
def to_text(self, name : Union[str,name.Name], **kw) -> str:
|
||||
...
|
||||
def find_rdataset(self, rdclass : int, rdtype : int, covers=rdatatype.NONE,
|
||||
create=False) -> rdataset.Rdataset:
|
||||
...
|
||||
def get_rdataset(self, rdclass : int, rdtype : int, covers=rdatatype.NONE,
|
||||
create=False) -> Optional[rdataset.Rdataset]:
|
||||
...
|
||||
def delete_rdataset(self, rdclass : int, rdtype : int, covers=rdatatype.NONE):
|
||||
...
|
||||
def replace_rdataset(self, replacement : rdataset.Rdataset) -> None:
|
||||
...
|
||||
@@ -1,119 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2001-2017 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
"""DNS Opcodes."""
|
||||
|
||||
import dns.exception
|
||||
|
||||
#: Query
|
||||
QUERY = 0
|
||||
#: Inverse Query (historical)
|
||||
IQUERY = 1
|
||||
#: Server Status (unspecified and unimplemented anywhere)
|
||||
STATUS = 2
|
||||
#: Notify
|
||||
NOTIFY = 4
|
||||
#: Dynamic Update
|
||||
UPDATE = 5
|
||||
|
||||
_by_text = {
|
||||
'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 = {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.
|
||||
|
||||
*text*, a ``text``, the textual opcode
|
||||
|
||||
Raises ``dns.opcode.UnknownOpcode`` if the opcode is unknown.
|
||||
|
||||
Returns an ``int``.
|
||||
"""
|
||||
|
||||
if text.isdigit():
|
||||
value = int(text)
|
||||
if value >= 0 and value <= 15:
|
||||
return value
|
||||
value = _by_text.get(text.upper())
|
||||
if value is None:
|
||||
raise UnknownOpcode
|
||||
return value
|
||||
|
||||
|
||||
def from_flags(flags):
|
||||
"""Extract an opcode from DNS message flags.
|
||||
|
||||
*flags*, an ``int``, the DNS flags.
|
||||
|
||||
Returns an ``int``.
|
||||
"""
|
||||
|
||||
return (flags & 0x7800) >> 11
|
||||
|
||||
|
||||
def to_flags(value):
|
||||
"""Convert an opcode to a value suitable for ORing into DNS message
|
||||
flags.
|
||||
|
||||
*value*, an ``int``, the DNS opcode value.
|
||||
|
||||
Returns an ``int``.
|
||||
"""
|
||||
|
||||
return (value << 11) & 0x7800
|
||||
|
||||
|
||||
def to_text(value):
|
||||
"""Convert an opcode to text.
|
||||
|
||||
*value*, an ``int`` the opcode value,
|
||||
|
||||
Raises ``dns.opcode.UnknownOpcode`` if the opcode is unknown.
|
||||
|
||||
Returns a ``text``.
|
||||
"""
|
||||
|
||||
text = _by_value.get(value)
|
||||
if text is None:
|
||||
text = str(value)
|
||||
return text
|
||||
|
||||
|
||||
def is_update(flags):
|
||||
"""Is the opcode in flags UPDATE?
|
||||
|
||||
*flags*, an ``int``, the DNS message flags.
|
||||
|
||||
Returns a ``bool``.
|
||||
"""
|
||||
|
||||
return from_flags(flags) == UPDATE
|
||||
@@ -1,683 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2003-2017 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
"""Talk to a DNS server."""
|
||||
|
||||
from __future__ import generators
|
||||
|
||||
import errno
|
||||
import select
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
|
||||
import dns.exception
|
||||
import dns.inet
|
||||
import dns.name
|
||||
import dns.message
|
||||
import dns.rcode
|
||||
import dns.rdataclass
|
||||
import dns.rdatatype
|
||||
from ._compat import long, string_types, PY3
|
||||
|
||||
if PY3:
|
||||
select_error = OSError
|
||||
else:
|
||||
select_error = select.error
|
||||
|
||||
# Function used to create a socket. Can be overridden if needed in special
|
||||
# situations.
|
||||
socket_factory = socket.socket
|
||||
|
||||
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."""
|
||||
|
||||
|
||||
class TransferError(dns.exception.DNSException):
|
||||
"""A zone transfer response got a non-zero rcode."""
|
||||
|
||||
def __init__(self, rcode):
|
||||
message = 'Zone transfer error: %s' % dns.rcode.to_text(rcode)
|
||||
super(TransferError, self).__init__(message)
|
||||
self.rcode = rcode
|
||||
|
||||
|
||||
def _compute_expiration(timeout):
|
||||
if timeout is None:
|
||||
return None
|
||||
else:
|
||||
return time.time() + timeout
|
||||
|
||||
# This module can use either poll() or select() as the "polling backend".
|
||||
#
|
||||
# A backend function takes an fd, bools for readability, writablity, and
|
||||
# error detection, and a timeout.
|
||||
|
||||
def _poll_for(fd, readable, writable, error, timeout):
|
||||
"""Poll polling backend."""
|
||||
|
||||
event_mask = 0
|
||||
if readable:
|
||||
event_mask |= select.POLLIN
|
||||
if writable:
|
||||
event_mask |= select.POLLOUT
|
||||
if error:
|
||||
event_mask |= select.POLLERR
|
||||
|
||||
pollable = select.poll()
|
||||
pollable.register(fd, event_mask)
|
||||
|
||||
if timeout:
|
||||
event_list = pollable.poll(long(timeout * 1000))
|
||||
else:
|
||||
event_list = pollable.poll()
|
||||
|
||||
return bool(event_list)
|
||||
|
||||
|
||||
def _select_for(fd, readable, writable, error, timeout):
|
||||
"""Select polling backend."""
|
||||
|
||||
rset, wset, xset = [], [], []
|
||||
|
||||
if readable:
|
||||
rset = [fd]
|
||||
if writable:
|
||||
wset = [fd]
|
||||
if error:
|
||||
xset = [fd]
|
||||
|
||||
if timeout is None:
|
||||
(rcount, wcount, xcount) = select.select(rset, wset, xset)
|
||||
else:
|
||||
(rcount, wcount, xcount) = select.select(rset, wset, xset, timeout)
|
||||
|
||||
return bool((rcount or wcount or xcount))
|
||||
|
||||
|
||||
def _wait_for(fd, readable, writable, error, expiration):
|
||||
# Use the selected polling backend to wait for any of the specified
|
||||
# events. An "expiration" absolute time is converted into a relative
|
||||
# timeout.
|
||||
|
||||
done = False
|
||||
while not done:
|
||||
if expiration is None:
|
||||
timeout = None
|
||||
else:
|
||||
timeout = expiration - time.time()
|
||||
if timeout <= 0.0:
|
||||
raise dns.exception.Timeout
|
||||
try:
|
||||
if not _polling_backend(fd, readable, writable, error, timeout):
|
||||
raise dns.exception.Timeout
|
||||
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.
|
||||
|
||||
global _polling_backend
|
||||
|
||||
_polling_backend = fn
|
||||
|
||||
if hasattr(select, 'poll'):
|
||||
# Prefer poll() on platforms that support it because it has no
|
||||
# limits on the maximum value of a file descriptor (plus it will
|
||||
# be more efficient for high values).
|
||||
_polling_backend = _poll_for
|
||||
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
|
||||
# textual representations of the same address
|
||||
try:
|
||||
n1 = dns.inet.inet_pton(af, a1[0])
|
||||
n2 = dns.inet.inet_pton(af, a2[0])
|
||||
except dns.exception.SyntaxError:
|
||||
return False
|
||||
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().
|
||||
if af is None:
|
||||
try:
|
||||
af = dns.inet.af_for_address(where)
|
||||
except Exception:
|
||||
af = dns.inet.AF_INET
|
||||
if af == dns.inet.AF_INET:
|
||||
destination = (where, port)
|
||||
if source is not None or source_port != 0:
|
||||
if source is None:
|
||||
source = '0.0.0.0'
|
||||
source = (source, source_port)
|
||||
elif af == dns.inet.AF_INET6:
|
||||
destination = (where, port, 0, 0)
|
||||
if source is not None or source_port != 0:
|
||||
if source is None:
|
||||
source = '::'
|
||||
source = (source, source_port, 0, 0)
|
||||
return (af, destination, source)
|
||||
|
||||
|
||||
def send_udp(sock, what, destination, expiration=None):
|
||||
"""Send a DNS message to the specified UDP socket.
|
||||
|
||||
*sock*, a ``socket``.
|
||||
|
||||
*what*, a ``binary`` or ``dns.message.Message``, the message to send.
|
||||
|
||||
*destination*, a destination tuple appropriate for the address family
|
||||
of the socket, specifying where to send the query.
|
||||
|
||||
*expiration*, a ``float`` or ``None``, the absolute time at which
|
||||
a timeout exception should be raised. If ``None``, no timeout will
|
||||
occur.
|
||||
|
||||
Returns an ``(int, float)`` tuple of bytes sent and the sent time.
|
||||
"""
|
||||
|
||||
if isinstance(what, dns.message.Message):
|
||||
what = what.to_wire()
|
||||
_wait_for_writable(sock, expiration)
|
||||
sent_time = time.time()
|
||||
n = sock.sendto(what, destination)
|
||||
return (n, sent_time)
|
||||
|
||||
|
||||
def receive_udp(sock, destination, expiration=None,
|
||||
ignore_unexpected=False, one_rr_per_rrset=False,
|
||||
keyring=None, request_mac=b'', ignore_trailing=False):
|
||||
"""Read a DNS message from a UDP socket.
|
||||
|
||||
*sock*, a ``socket``.
|
||||
|
||||
*destination*, a destination tuple appropriate for the address family
|
||||
of the socket, specifying where the associated query was sent.
|
||||
|
||||
*expiration*, a ``float`` or ``None``, the absolute time at which
|
||||
a timeout exception should be raised. If ``None``, no timeout will
|
||||
occur.
|
||||
|
||||
*ignore_unexpected*, a ``bool``. If ``True``, ignore responses from
|
||||
unexpected sources.
|
||||
|
||||
*one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own
|
||||
RRset.
|
||||
|
||||
*keyring*, a ``dict``, the keyring to use for TSIG.
|
||||
|
||||
*request_mac*, a ``binary``, the MAC of the request (for TSIG).
|
||||
|
||||
*ignore_trailing*, a ``bool``. If ``True``, ignore trailing
|
||||
junk at end of the received message.
|
||||
|
||||
Raises if the message is malformed, if network errors occur, of if
|
||||
there is a timeout.
|
||||
|
||||
Returns a ``dns.message.Message`` object.
|
||||
"""
|
||||
|
||||
wire = b''
|
||||
while 1:
|
||||
_wait_for_readable(sock, expiration)
|
||||
(wire, from_address) = sock.recvfrom(65535)
|
||||
if _addresses_equal(sock.family, from_address, destination) or \
|
||||
(dns.inet.is_multicast(destination[0]) and
|
||||
from_address[1:] == destination[1:]):
|
||||
break
|
||||
if not ignore_unexpected:
|
||||
raise UnexpectedSource('got a response from '
|
||||
'%s instead of %s' % (from_address,
|
||||
destination))
|
||||
received_time = time.time()
|
||||
r = dns.message.from_wire(wire, keyring=keyring, request_mac=request_mac,
|
||||
one_rr_per_rrset=one_rr_per_rrset,
|
||||
ignore_trailing=ignore_trailing)
|
||||
return (r, received_time)
|
||||
|
||||
def udp(q, where, timeout=None, port=53, af=None, source=None, source_port=0,
|
||||
ignore_unexpected=False, one_rr_per_rrset=False, ignore_trailing=False):
|
||||
"""Return the response obtained after sending a query via UDP.
|
||||
|
||||
*q*, a ``dns.message.Message``, the query to send
|
||||
|
||||
*where*, a ``text`` containing an IPv4 or IPv6 address, where
|
||||
to send the message.
|
||||
|
||||
*timeout*, a ``float`` or ``None``, the number of seconds to wait before the
|
||||
query times out. If ``None``, the default, wait forever.
|
||||
|
||||
*port*, an ``int``, the port send the message to. The default is 53.
|
||||
|
||||
*af*, an ``int``, the address family to use. The default is ``None``,
|
||||
which causes the address family to use to be inferred from the form of
|
||||
*where*. If the inference attempt fails, AF_INET is used. This
|
||||
parameter is historical; you need never set it.
|
||||
|
||||
*source*, a ``text`` containing an IPv4 or IPv6 address, specifying
|
||||
the source address. The default is the wildcard address.
|
||||
|
||||
*source_port*, an ``int``, the port from which to send the message.
|
||||
The default is 0.
|
||||
|
||||
*ignore_unexpected*, a ``bool``. If ``True``, ignore responses from
|
||||
unexpected sources.
|
||||
|
||||
*one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own
|
||||
RRset.
|
||||
|
||||
*ignore_trailing*, a ``bool``. If ``True``, ignore trailing
|
||||
junk at end of the received message.
|
||||
|
||||
Returns a ``dns.message.Message``.
|
||||
"""
|
||||
|
||||
wire = q.to_wire()
|
||||
(af, destination, source) = _destination_and_source(af, where, port,
|
||||
source, source_port)
|
||||
s = socket_factory(af, socket.SOCK_DGRAM, 0)
|
||||
received_time = None
|
||||
sent_time = None
|
||||
try:
|
||||
expiration = _compute_expiration(timeout)
|
||||
s.setblocking(0)
|
||||
if source is not None:
|
||||
s.bind(source)
|
||||
(_, sent_time) = send_udp(s, wire, destination, expiration)
|
||||
(r, received_time) = receive_udp(s, destination, expiration,
|
||||
ignore_unexpected, one_rr_per_rrset,
|
||||
q.keyring, q.mac, ignore_trailing)
|
||||
finally:
|
||||
if sent_time is None or received_time is None:
|
||||
response_time = 0
|
||||
else:
|
||||
response_time = received_time - sent_time
|
||||
s.close()
|
||||
r.time = response_time
|
||||
if not q.is_response(r):
|
||||
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.
|
||||
A Timeout exception will be raised if the operation is not completed
|
||||
by the expiration time.
|
||||
"""
|
||||
s = b''
|
||||
while count > 0:
|
||||
_wait_for_readable(sock, expiration)
|
||||
n = sock.recv(count)
|
||||
if n == b'':
|
||||
raise EOFError
|
||||
count = count - len(n)
|
||||
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
|
||||
by the expiration time.
|
||||
"""
|
||||
current = 0
|
||||
l = len(data)
|
||||
while current < l:
|
||||
_wait_for_writable(sock, expiration)
|
||||
current += sock.send(data[current:])
|
||||
|
||||
|
||||
def send_tcp(sock, what, expiration=None):
|
||||
"""Send a DNS message to the specified TCP socket.
|
||||
|
||||
*sock*, a ``socket``.
|
||||
|
||||
*what*, a ``binary`` or ``dns.message.Message``, the message to send.
|
||||
|
||||
*expiration*, a ``float`` or ``None``, the absolute time at which
|
||||
a timeout exception should be raised. If ``None``, no timeout will
|
||||
occur.
|
||||
|
||||
Returns an ``(int, float)`` tuple of bytes sent and the sent time.
|
||||
"""
|
||||
|
||||
if isinstance(what, dns.message.Message):
|
||||
what = what.to_wire()
|
||||
l = len(what)
|
||||
# copying the wire into tcpmsg is inefficient, but lets us
|
||||
# avoid writev() or doing a short write that would get pushed
|
||||
# onto the net
|
||||
tcpmsg = struct.pack("!H", l) + what
|
||||
_wait_for_writable(sock, expiration)
|
||||
sent_time = time.time()
|
||||
_net_write(sock, tcpmsg, expiration)
|
||||
return (len(tcpmsg), sent_time)
|
||||
|
||||
def receive_tcp(sock, expiration=None, one_rr_per_rrset=False,
|
||||
keyring=None, request_mac=b'', ignore_trailing=False):
|
||||
"""Read a DNS message from a TCP socket.
|
||||
|
||||
*sock*, a ``socket``.
|
||||
|
||||
*expiration*, a ``float`` or ``None``, the absolute time at which
|
||||
a timeout exception should be raised. If ``None``, no timeout will
|
||||
occur.
|
||||
|
||||
*one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own
|
||||
RRset.
|
||||
|
||||
*keyring*, a ``dict``, the keyring to use for TSIG.
|
||||
|
||||
*request_mac*, a ``binary``, the MAC of the request (for TSIG).
|
||||
|
||||
*ignore_trailing*, a ``bool``. If ``True``, ignore trailing
|
||||
junk at end of the received message.
|
||||
|
||||
Raises if the message is malformed, if network errors occur, of if
|
||||
there is a timeout.
|
||||
|
||||
Returns a ``dns.message.Message`` object.
|
||||
"""
|
||||
|
||||
ldata = _net_read(sock, 2, expiration)
|
||||
(l,) = struct.unpack("!H", ldata)
|
||||
wire = _net_read(sock, l, expiration)
|
||||
received_time = time.time()
|
||||
r = dns.message.from_wire(wire, keyring=keyring, request_mac=request_mac,
|
||||
one_rr_per_rrset=one_rr_per_rrset,
|
||||
ignore_trailing=ignore_trailing)
|
||||
return (r, received_time)
|
||||
|
||||
def _connect(s, address):
|
||||
try:
|
||||
s.connect(address)
|
||||
except socket.error:
|
||||
(ty, v) = sys.exc_info()[:2]
|
||||
|
||||
if hasattr(v, 'errno'):
|
||||
v_err = v.errno
|
||||
else:
|
||||
v_err = v[0]
|
||||
if v_err not in [errno.EINPROGRESS, errno.EWOULDBLOCK, errno.EALREADY]:
|
||||
raise v
|
||||
|
||||
|
||||
def tcp(q, where, timeout=None, port=53, af=None, source=None, source_port=0,
|
||||
one_rr_per_rrset=False, ignore_trailing=False):
|
||||
"""Return the response obtained after sending a query via TCP.
|
||||
|
||||
*q*, a ``dns.message.Message``, the query to send
|
||||
|
||||
*where*, a ``text`` containing an IPv4 or IPv6 address, where
|
||||
to send the message.
|
||||
|
||||
*timeout*, a ``float`` or ``None``, the number of seconds to wait before the
|
||||
query times out. If ``None``, the default, wait forever.
|
||||
|
||||
*port*, an ``int``, the port send the message to. The default is 53.
|
||||
|
||||
*af*, an ``int``, the address family to use. The default is ``None``,
|
||||
which causes the address family to use to be inferred from the form of
|
||||
*where*. If the inference attempt fails, AF_INET is used. This
|
||||
parameter is historical; you need never set it.
|
||||
|
||||
*source*, a ``text`` containing an IPv4 or IPv6 address, specifying
|
||||
the source address. The default is the wildcard address.
|
||||
|
||||
*source_port*, an ``int``, the port from which to send the message.
|
||||
The default is 0.
|
||||
|
||||
*one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own
|
||||
RRset.
|
||||
|
||||
*ignore_trailing*, a ``bool``. If ``True``, ignore trailing
|
||||
junk at end of the received message.
|
||||
|
||||
Returns a ``dns.message.Message``.
|
||||
"""
|
||||
|
||||
wire = q.to_wire()
|
||||
(af, destination, source) = _destination_and_source(af, where, port,
|
||||
source, source_port)
|
||||
s = socket_factory(af, socket.SOCK_STREAM, 0)
|
||||
begin_time = None
|
||||
received_time = None
|
||||
try:
|
||||
expiration = _compute_expiration(timeout)
|
||||
s.setblocking(0)
|
||||
begin_time = time.time()
|
||||
if source is not None:
|
||||
s.bind(source)
|
||||
_connect(s, destination)
|
||||
send_tcp(s, wire, expiration)
|
||||
(r, received_time) = receive_tcp(s, expiration, one_rr_per_rrset,
|
||||
q.keyring, q.mac, ignore_trailing)
|
||||
finally:
|
||||
if begin_time is None or received_time is None:
|
||||
response_time = 0
|
||||
else:
|
||||
response_time = received_time - begin_time
|
||||
s.close()
|
||||
r.time = response_time
|
||||
if not q.is_response(r):
|
||||
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,
|
||||
use_udp=False, keyalgorithm=dns.tsig.default_algorithm):
|
||||
"""Return a generator for the responses to a zone transfer.
|
||||
|
||||
*where*. If the inference attempt fails, AF_INET is used. This
|
||||
parameter is historical; you need never set it.
|
||||
|
||||
*zone*, a ``dns.name.Name`` or ``text``, the name of the zone to transfer.
|
||||
|
||||
*rdtype*, an ``int`` or ``text``, the type of zone transfer. The
|
||||
default is ``dns.rdatatype.AXFR``. ``dns.rdatatype.IXFR`` can be
|
||||
used to do an incremental transfer instead.
|
||||
|
||||
*rdclass*, an ``int`` or ``text``, the class of the zone transfer.
|
||||
The default is ``dns.rdataclass.IN``.
|
||||
|
||||
*timeout*, a ``float``, the number of seconds to wait for each
|
||||
response message. If None, the default, wait forever.
|
||||
|
||||
*port*, an ``int``, the port send the message to. The default is 53.
|
||||
|
||||
*keyring*, a ``dict``, the keyring to use for TSIG.
|
||||
|
||||
*keyname*, a ``dns.name.Name`` or ``text``, the name of the TSIG
|
||||
key to use.
|
||||
|
||||
*relativize*, a ``bool``. If ``True``, all names in the zone will be
|
||||
relativized to the zone origin. It is essential that the
|
||||
relativize setting matches the one specified to
|
||||
``dns.zone.from_xfr()`` if using this generator to make a zone.
|
||||
|
||||
*af*, an ``int``, the address family to use. The default is ``None``,
|
||||
which causes the address family to use to be inferred from the form of
|
||||
*where*. If the inference attempt fails, AF_INET is used. This
|
||||
parameter is historical; you need never set it.
|
||||
|
||||
*lifetime*, a ``float``, the total number of seconds to spend
|
||||
doing the transfer. If ``None``, the default, then there is no
|
||||
limit on the time the transfer may take.
|
||||
|
||||
*source*, a ``text`` containing an IPv4 or IPv6 address, specifying
|
||||
the source address. The default is the wildcard address.
|
||||
|
||||
*source_port*, an ``int``, the port from which to send the message.
|
||||
The default is 0.
|
||||
|
||||
*serial*, an ``int``, the SOA serial number to use as the base for
|
||||
an IXFR diff sequence (only meaningful if *rdtype* is
|
||||
``dns.rdatatype.IXFR``).
|
||||
|
||||
*use_udp*, a ``bool``. If ``True``, use UDP (only meaningful for IXFR).
|
||||
|
||||
*keyalgorithm*, a ``dns.name.Name`` or ``text``, the TSIG algorithm to use.
|
||||
|
||||
Raises on errors, and so does the generator.
|
||||
|
||||
Returns a generator of ``dns.message.Message`` objects.
|
||||
"""
|
||||
|
||||
if isinstance(zone, string_types):
|
||||
zone = dns.name.from_text(zone)
|
||||
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 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)
|
||||
if use_udp:
|
||||
if rdtype != dns.rdatatype.IXFR:
|
||||
raise ValueError('cannot do a UDP AXFR')
|
||||
s = socket_factory(af, socket.SOCK_DGRAM, 0)
|
||||
else:
|
||||
s = socket_factory(af, socket.SOCK_STREAM, 0)
|
||||
s.setblocking(0)
|
||||
if source is not None:
|
||||
s.bind(source)
|
||||
expiration = _compute_expiration(lifetime)
|
||||
_connect(s, destination)
|
||||
l = len(wire)
|
||||
if use_udp:
|
||||
_wait_for_writable(s, expiration)
|
||||
s.send(wire)
|
||||
else:
|
||||
tcpmsg = struct.pack("!H", l) + wire
|
||||
_net_write(s, tcpmsg, expiration)
|
||||
done = False
|
||||
delete_mode = True
|
||||
expecting_SOA = False
|
||||
soa_rrset = None
|
||||
if relativize:
|
||||
origin = zone
|
||||
oname = dns.name.empty
|
||||
else:
|
||||
origin = None
|
||||
oname = zone
|
||||
tsig_ctx = None
|
||||
first = True
|
||||
while not done:
|
||||
mexpiration = _compute_expiration(timeout)
|
||||
if mexpiration is None or mexpiration > expiration:
|
||||
mexpiration = expiration
|
||||
if use_udp:
|
||||
_wait_for_readable(s, expiration)
|
||||
(wire, from_address) = s.recvfrom(65535)
|
||||
else:
|
||||
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=is_ixfr)
|
||||
rcode = r.rcode()
|
||||
if rcode != dns.rcode.NOERROR:
|
||||
raise TransferError(rcode)
|
||||
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")
|
||||
rrset = r.answer[0]
|
||||
if rrset.rdtype != dns.rdatatype.SOA:
|
||||
raise dns.exception.FormError("first RRset is not an SOA")
|
||||
answer_index = 1
|
||||
soa_rrset = rrset.copy()
|
||||
if rdtype == dns.rdatatype.IXFR:
|
||||
if soa_rrset[0].serial <= serial:
|
||||
#
|
||||
# We're already up-to-date.
|
||||
#
|
||||
done = True
|
||||
else:
|
||||
expecting_SOA = True
|
||||
#
|
||||
# Process SOAs in the answer section (other than the initial
|
||||
# SOA in the first message).
|
||||
#
|
||||
for rrset in r.answer[answer_index:]:
|
||||
if done:
|
||||
raise dns.exception.FormError("answers after final SOA")
|
||||
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")
|
||||
expecting_SOA = False
|
||||
elif rdtype == dns.rdatatype.IXFR:
|
||||
delete_mode = not delete_mode
|
||||
#
|
||||
# If this SOA RRset is equal to the first we saw then we're
|
||||
# finished. If this is an IXFR we also check that we're seeing
|
||||
# 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)):
|
||||
done = True
|
||||
elif expecting_SOA:
|
||||
#
|
||||
# We made an IXFR request and are expecting another
|
||||
# SOA RR, but saw something else, so this must be an
|
||||
# AXFR response.
|
||||
#
|
||||
rdtype = dns.rdatatype.AXFR
|
||||
expecting_SOA = False
|
||||
if done and q.keyring and not r.had_tsig:
|
||||
raise dns.exception.FormError("missing TSIG")
|
||||
yield r
|
||||
s.close()
|
||||
@@ -1,15 +0,0 @@
|
||||
from typing import Optional, Union, Dict, Generator, Any
|
||||
from . import message, tsig, rdatatype, rdataclass, name, message
|
||||
def tcp(q : message.Message, where : str, timeout : float = None, port=53, af : Optional[int] = None, source : Optional[str] = None, source_port : int = 0,
|
||||
one_rr_per_rrset=False) -> message.Message:
|
||||
pass
|
||||
|
||||
def xfr(where : None, zone : Union[name.Name,str], rdtype=rdatatype.AXFR, rdclass=rdataclass.IN,
|
||||
timeout : Optional[float] =None, port=53, keyring : Optional[Dict[name.Name, bytes]] =None, keyname : Union[str,name.Name]=None, relativize=True,
|
||||
af : Optional[int] =None, lifetime : Optional[float]=None, source : Optional[str] =None, source_port=0, serial=0,
|
||||
use_udp=False, keyalgorithm=tsig.default_algorithm) -> Generator[Any,Any,message.Message]:
|
||||
pass
|
||||
|
||||
def udp(q : message.Message, where : str, timeout : Optional[float] = None, port=53, af : Optional[int] = None, source : Optional[str] = None, source_port=0,
|
||||
ignore_unexpected=False, one_rr_per_rrset=False) -> message.Message:
|
||||
...
|
||||
@@ -1,144 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2001-2017 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
"""DNS Result Codes."""
|
||||
|
||||
import dns.exception
|
||||
from ._compat import long
|
||||
|
||||
#: No error
|
||||
NOERROR = 0
|
||||
#: Form error
|
||||
FORMERR = 1
|
||||
#: Server failure
|
||||
SERVFAIL = 2
|
||||
#: Name does not exist ("Name Error" in RFC 1025 terminology).
|
||||
NXDOMAIN = 3
|
||||
#: Not implemented
|
||||
NOTIMP = 4
|
||||
#: Refused
|
||||
REFUSED = 5
|
||||
#: Name exists.
|
||||
YXDOMAIN = 6
|
||||
#: RRset exists.
|
||||
YXRRSET = 7
|
||||
#: RRset does not exist.
|
||||
NXRRSET = 8
|
||||
#: Not authoritative.
|
||||
NOTAUTH = 9
|
||||
#: Name not in zone.
|
||||
NOTZONE = 10
|
||||
#: Bad EDNS version.
|
||||
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
|
||||
}
|
||||
|
||||
# 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 = {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.
|
||||
|
||||
*text*, a ``text``, the textual rcode or an integer in textual form.
|
||||
|
||||
Raises ``dns.rcode.UnknownRcode`` if the rcode mnemonic is unknown.
|
||||
|
||||
Returns an ``int``.
|
||||
"""
|
||||
|
||||
if text.isdigit():
|
||||
v = int(text)
|
||||
if v >= 0 and v <= 4095:
|
||||
return v
|
||||
v = _by_text.get(text.upper())
|
||||
if v is None:
|
||||
raise UnknownRcode
|
||||
return v
|
||||
|
||||
|
||||
def from_flags(flags, ednsflags):
|
||||
"""Return the rcode value encoded by flags and ednsflags.
|
||||
|
||||
*flags*, an ``int``, the DNS flags field.
|
||||
|
||||
*ednsflags*, an ``int``, the EDNS flags field.
|
||||
|
||||
Raises ``ValueError`` if rcode is < 0 or > 4095
|
||||
|
||||
Returns an ``int``.
|
||||
"""
|
||||
|
||||
value = (flags & 0x000f) | ((ednsflags >> 20) & 0xff0)
|
||||
if value < 0 or value > 4095:
|
||||
raise ValueError('rcode must be >= 0 and <= 4095')
|
||||
return value
|
||||
|
||||
|
||||
def to_flags(value):
|
||||
"""Return a (flags, ednsflags) tuple which encodes the rcode.
|
||||
|
||||
*value*, an ``int``, the rcode.
|
||||
|
||||
Raises ``ValueError`` if rcode is < 0 or > 4095.
|
||||
|
||||
Returns an ``(int, int)`` tuple.
|
||||
"""
|
||||
|
||||
if value < 0 or value > 4095:
|
||||
raise ValueError('rcode must be >= 0 and <= 4095')
|
||||
v = value & 0xf
|
||||
ev = long(value & 0xff0) << 20
|
||||
return (v, ev)
|
||||
|
||||
|
||||
def to_text(value):
|
||||
"""Convert rcode into text.
|
||||
|
||||
*value*, and ``int``, the rcode.
|
||||
|
||||
Raises ``ValueError`` if rcode is < 0 or > 4095.
|
||||
|
||||
Returns a ``text``.
|
||||
"""
|
||||
|
||||
if value < 0 or value > 4095:
|
||||
raise ValueError('rcode must be >= 0 and <= 4095')
|
||||
text = _by_value.get(value)
|
||||
if text is None:
|
||||
text = str(value)
|
||||
return text
|
||||
@@ -1,456 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2001-2017 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
"""DNS rdata."""
|
||||
|
||||
from io import BytesIO
|
||||
import base64
|
||||
import binascii
|
||||
|
||||
import dns.exception
|
||||
import dns.name
|
||||
import dns.rdataclass
|
||||
import dns.rdatatype
|
||||
import dns.tokenizer
|
||||
import dns.wiredata
|
||||
from ._compat import xrange, string_types, text_type
|
||||
|
||||
try:
|
||||
import threading as _threading
|
||||
except ImportError:
|
||||
import dummy_threading as _threading
|
||||
|
||||
_hex_chunksize = 32
|
||||
|
||||
|
||||
def _hexify(data, chunksize=_hex_chunksize):
|
||||
"""Convert a binary string into its hex encoding, broken up into chunks
|
||||
of chunksize characters separated by a space.
|
||||
"""
|
||||
|
||||
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=_base64_chunksize):
|
||||
"""Convert a binary string into its base64 encoding, broken up into chunks
|
||||
of chunksize characters separated by a space.
|
||||
"""
|
||||
|
||||
line = base64.b64encode(data)
|
||||
return b' '.join([line[i:i + chunksize]
|
||||
for i
|
||||
in range(0, len(line), chunksize)]).decode()
|
||||
|
||||
__escaped = bytearray(b'"\\')
|
||||
|
||||
def _escapify(qstring):
|
||||
"""Escape the characters in a quoted string which need it."""
|
||||
|
||||
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 += '\\' + chr(c)
|
||||
elif c >= 0x20 and c < 0x7F:
|
||||
text += chr(c)
|
||||
else:
|
||||
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.
|
||||
"""
|
||||
|
||||
for i in xrange(len(what) - 1, -1, -1):
|
||||
if what[i] != 0:
|
||||
return what[0: i + 1]
|
||||
return what[0:1]
|
||||
|
||||
|
||||
class Rdata(object):
|
||||
"""Base class for all DNS rdata types."""
|
||||
|
||||
__slots__ = ['rdclass', 'rdtype']
|
||||
|
||||
def __init__(self, rdclass, rdtype):
|
||||
"""Initialize an rdata.
|
||||
|
||||
*rdclass*, an ``int`` is the rdataclass of the Rdata.
|
||||
*rdtype*, an ``int`` is the rdatatype of the Rdata.
|
||||
"""
|
||||
|
||||
self.rdclass = rdclass
|
||||
self.rdtype = rdtype
|
||||
|
||||
def covers(self):
|
||||
"""Return the type a Rdata covers.
|
||||
|
||||
DNS SIG/RRSIG rdatas apply to a specific type; this type is
|
||||
returned by the covers() function. If the rdata type is not
|
||||
SIG or RRSIG, dns.rdatatype.NONE is returned. This is useful when
|
||||
creating rdatasets, allowing the rdataset to contain only RRSIGs
|
||||
of a particular type, e.g. RRSIG(NS).
|
||||
|
||||
Returns an ``int``.
|
||||
"""
|
||||
|
||||
return dns.rdatatype.NONE
|
||||
|
||||
def extended_rdatatype(self):
|
||||
"""Return a 32-bit type value, the least significant 16 bits of
|
||||
which are the ordinary DNS type, and the upper 16 bits of which are
|
||||
the "covered" type, if any.
|
||||
|
||||
Returns an ``int``.
|
||||
"""
|
||||
|
||||
return self.covers() << 16 | self.rdtype
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
"""Convert an rdata to text format.
|
||||
|
||||
Returns a ``text``.
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
def to_wire(self, file, compress=None, origin=None):
|
||||
"""Convert an rdata to wire format.
|
||||
|
||||
Returns a ``binary``.
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
def to_digestable(self, origin=None):
|
||||
"""Convert rdata to a format suitable for digesting in hashes. This
|
||||
is also the DNSSEC canonical form.
|
||||
|
||||
Returns a ``binary``.
|
||||
"""
|
||||
|
||||
f = BytesIO()
|
||||
self.to_wire(f, None, origin)
|
||||
return f.getvalue()
|
||||
|
||||
def validate(self):
|
||||
"""Check that the current contents of the rdata's fields are
|
||||
valid.
|
||||
|
||||
If you change an rdata by assigning to its fields,
|
||||
it is a good idea to call validate() when you are done making
|
||||
changes.
|
||||
|
||||
Raises various exceptions if there are problems.
|
||||
|
||||
Returns ``None``.
|
||||
"""
|
||||
|
||||
dns.rdata.from_text(self.rdclass, self.rdtype, self.to_text())
|
||||
|
||||
def __repr__(self):
|
||||
covers = self.covers()
|
||||
if covers == dns.rdatatype.NONE:
|
||||
ctext = ''
|
||||
else:
|
||||
ctext = '(' + dns.rdatatype.to_text(covers) + ')'
|
||||
return '<DNS ' + dns.rdataclass.to_text(self.rdclass) + ' ' + \
|
||||
dns.rdatatype.to_text(self.rdtype) + ctext + ' rdata: ' + \
|
||||
str(self) + '>'
|
||||
|
||||
def __str__(self):
|
||||
return self.to_text()
|
||||
|
||||
def _cmp(self, other):
|
||||
"""Compare an rdata with another rdata of the same rdtype and
|
||||
rdclass.
|
||||
|
||||
Return < 0 if self < other in the DNSSEC ordering, 0 if self
|
||||
== other, and > 0 if self > other.
|
||||
|
||||
"""
|
||||
our = self.to_digestable(dns.name.root)
|
||||
their = other.to_digestable(dns.name.root)
|
||||
if our == their:
|
||||
return 0
|
||||
elif our > their:
|
||||
return 1
|
||||
else:
|
||||
return -1
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Rdata):
|
||||
return False
|
||||
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:
|
||||
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:
|
||||
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
return NotImplemented
|
||||
return self._cmp(other) > 0
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.to_digestable(dns.name.root))
|
||||
|
||||
@classmethod
|
||||
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True):
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def choose_relativity(self, origin=None, relativize=True):
|
||||
"""Convert any domain names in the rdata to the specified
|
||||
relativization.
|
||||
"""
|
||||
|
||||
class GenericRdata(Rdata):
|
||||
|
||||
"""Generic Rdata Class
|
||||
|
||||
This class is used for rdata types for which we have no better
|
||||
implementation. It implements the DNS "unknown RRs" scheme.
|
||||
"""
|
||||
|
||||
__slots__ = ['data']
|
||||
|
||||
def __init__(self, rdclass, rdtype, data):
|
||||
super(GenericRdata, self).__init__(rdclass, rdtype)
|
||||
self.data = data
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
return r'\# %d ' % len(self.data) + _hexify(self.data)
|
||||
|
||||
@classmethod
|
||||
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True):
|
||||
token = tok.get()
|
||||
if not token.is_identifier() or token.value != r'\#':
|
||||
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.encode())
|
||||
hex = b''.join(chunks)
|
||||
data = binascii.unhexlify(hex)
|
||||
if len(data) != length:
|
||||
raise dns.exception.SyntaxError(
|
||||
'generic rdata hex data has wrong length')
|
||||
return cls(rdclass, rdtype, data)
|
||||
|
||||
def to_wire(self, file, compress=None, origin=None):
|
||||
file.write(self.data)
|
||||
|
||||
@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'
|
||||
_import_lock = _threading.Lock()
|
||||
|
||||
def get_rdata_class(rdclass, rdtype):
|
||||
|
||||
def import_module(name):
|
||||
with _import_lock:
|
||||
mod = __import__(name)
|
||||
components = name.split('.')
|
||||
for comp in components[1:]:
|
||||
mod = getattr(mod, comp)
|
||||
return mod
|
||||
|
||||
mod = _rdata_modules.get((rdclass, rdtype))
|
||||
rdclass_text = dns.rdataclass.to_text(rdclass)
|
||||
rdtype_text = dns.rdatatype.to_text(rdtype)
|
||||
rdtype_text = rdtype_text.replace('-', '_')
|
||||
if not mod:
|
||||
mod = _rdata_modules.get((dns.rdatatype.ANY, rdtype))
|
||||
if not mod:
|
||||
try:
|
||||
mod = import_module('.'.join([_module_prefix,
|
||||
rdclass_text, rdtype_text]))
|
||||
_rdata_modules[(rdclass, rdtype)] = mod
|
||||
except ImportError:
|
||||
try:
|
||||
mod = import_module('.'.join([_module_prefix,
|
||||
'ANY', rdtype_text]))
|
||||
_rdata_modules[(dns.rdataclass.ANY, rdtype)] = mod
|
||||
except ImportError:
|
||||
mod = None
|
||||
if mod:
|
||||
cls = getattr(mod, rdtype_text)
|
||||
else:
|
||||
cls = GenericRdata
|
||||
return cls
|
||||
|
||||
|
||||
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
|
||||
implements the specified rdata class and type. If there is no
|
||||
class-and-type-specific implementation, the GenericRdata class
|
||||
is used.
|
||||
|
||||
Once a class is chosen, its from_text() class method is called
|
||||
with the parameters to this function.
|
||||
|
||||
If *tok* is a ``text``, then a tokenizer is created and the string
|
||||
is used as its input.
|
||||
|
||||
*rdclass*, an ``int``, the rdataclass.
|
||||
|
||||
*rdtype*, an ``int``, the rdatatype.
|
||||
|
||||
*tok*, a ``dns.tokenizer.Tokenizer`` or a ``text``.
|
||||
|
||||
*origin*, a ``dns.name.Name`` (or ``None``), the
|
||||
origin to use for relative names.
|
||||
|
||||
*relativize*, a ``bool``. If true, name will be relativized to
|
||||
the specified origin.
|
||||
|
||||
Returns an instance of the chosen Rdata subclass.
|
||||
"""
|
||||
|
||||
if isinstance(tok, string_types):
|
||||
tok = dns.tokenizer.Tokenizer(tok)
|
||||
cls = get_rdata_class(rdclass, rdtype)
|
||||
if cls != GenericRdata:
|
||||
# peek at first token
|
||||
token = tok.get()
|
||||
tok.unget(token)
|
||||
if token.is_identifier() and \
|
||||
token.value == r'\#':
|
||||
#
|
||||
# Known type using the generic syntax. Extract the
|
||||
# wire form from the generic syntax, and then run
|
||||
# from_wire on it.
|
||||
#
|
||||
rdata = GenericRdata.from_text(rdclass, rdtype, tok, origin,
|
||||
relativize)
|
||||
return from_wire(rdclass, rdtype, rdata.data, 0, len(rdata.data),
|
||||
origin)
|
||||
return cls.from_text(rdclass, rdtype, tok, origin, relativize)
|
||||
|
||||
|
||||
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
|
||||
implements the specified rdata class and type. If there is no
|
||||
class-and-type-specific implementation, the GenericRdata class
|
||||
is used.
|
||||
|
||||
Once a class is chosen, its from_wire() class method is called
|
||||
with the parameters to this function.
|
||||
|
||||
*rdclass*, an ``int``, the rdataclass.
|
||||
|
||||
*rdtype*, an ``int``, the rdatatype.
|
||||
|
||||
*wire*, a ``binary``, the wire-format message.
|
||||
|
||||
*current*, an ``int``, the offset in wire of the beginning of
|
||||
the rdata.
|
||||
|
||||
*rdlen*, an ``int``, the length of the wire-format rdata
|
||||
|
||||
*origin*, a ``dns.name.Name`` (or ``None``). If not ``None``,
|
||||
then names will be relativized to this origin.
|
||||
|
||||
Returns an instance of the chosen Rdata subclass.
|
||||
"""
|
||||
|
||||
wire = dns.wiredata.maybe_wrap(wire)
|
||||
cls = get_rdata_class(rdclass, rdtype)
|
||||
return cls.from_wire(rdclass, rdtype, wire, current, rdlen, origin)
|
||||
|
||||
|
||||
class RdatatypeExists(dns.exception.DNSException):
|
||||
"""DNS rdatatype already exists."""
|
||||
supp_kwargs = {'rdclass', 'rdtype'}
|
||||
fmt = "The rdata type with class {rdclass} and rdtype {rdtype} " + \
|
||||
"already exists."
|
||||
|
||||
|
||||
def register_type(implementation, rdtype, rdtype_text, is_singleton=False,
|
||||
rdclass=dns.rdataclass.IN):
|
||||
"""Dynamically register a module to handle an rdatatype.
|
||||
|
||||
*implementation*, a module implementing the type in the usual dnspython
|
||||
way.
|
||||
|
||||
*rdtype*, an ``int``, the rdatatype to register.
|
||||
|
||||
*rdtype_text*, a ``text``, the textual form of the rdatatype.
|
||||
|
||||
*is_singleton*, a ``bool``, indicating if the type is a singleton (i.e.
|
||||
RRsets of the type can have only one member.)
|
||||
|
||||
*rdclass*, the rdataclass of the type, or ``dns.rdataclass.ANY`` if
|
||||
it applies to all classes.
|
||||
"""
|
||||
|
||||
existing_cls = get_rdata_class(rdclass, rdtype)
|
||||
if existing_cls != GenericRdata:
|
||||
raise RdatatypeExists(rdclass=rdclass, rdtype=rdtype)
|
||||
_rdata_modules[(rdclass, rdtype)] = implementation
|
||||
dns.rdatatype.register_type(rdtype, rdtype_text, is_singleton)
|
||||
@@ -1,17 +0,0 @@
|
||||
from typing import Dict, Tuple, Any, Optional
|
||||
from .name import Name
|
||||
class Rdata:
|
||||
def __init__(self):
|
||||
self.address : str
|
||||
def to_wire(self, file, compress : Optional[Dict[Name,int]], origin : Optional[Name]) -> bytes:
|
||||
...
|
||||
@classmethod
|
||||
def from_text(cls, rdclass : int, rdtype : int, tok, origin=None, relativize=True):
|
||||
...
|
||||
_rdata_modules : Dict[Tuple[Any,Rdata],Any]
|
||||
|
||||
def from_text(rdclass : int, rdtype : int, tok : Optional[str], origin : Optional[Name] = None, relativize : bool = True):
|
||||
...
|
||||
|
||||
def from_wire(rdclass : int, rdtype : int, wire : bytes, current : int, rdlen : int, origin : Optional[Name] = None):
|
||||
...
|
||||
@@ -1,122 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2001-2017 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
"""DNS Rdata Classes."""
|
||||
|
||||
import re
|
||||
|
||||
import dns.exception
|
||||
|
||||
RESERVED0 = 0
|
||||
IN = 1
|
||||
CH = 3
|
||||
HS = 4
|
||||
NONE = 254
|
||||
ANY = 255
|
||||
|
||||
_by_text = {
|
||||
'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 = {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
|
||||
})
|
||||
|
||||
_metaclasses = {
|
||||
NONE: True,
|
||||
ANY: True
|
||||
}
|
||||
|
||||
_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.
|
||||
|
||||
The input text can be a defined DNS RR class mnemonic or
|
||||
instance of the DNS generic class syntax.
|
||||
|
||||
For example, "IN" and "CLASS1" will both result in a value of 1.
|
||||
|
||||
Raises ``dns.rdatatype.UnknownRdataclass`` if the class is unknown.
|
||||
|
||||
Raises ``ValueError`` if the rdata class value is not >= 0 and <= 65535.
|
||||
|
||||
Returns an ``int``.
|
||||
"""
|
||||
|
||||
value = _by_text.get(text.upper())
|
||||
if value is None:
|
||||
match = _unknown_class_pattern.match(text)
|
||||
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 type value to text.
|
||||
|
||||
If the value has a known mnemonic, it will be used, otherwise the
|
||||
DNS generic class syntax will be used.
|
||||
|
||||
Raises ``ValueError`` if the rdata class value is not >= 0 and <= 65535.
|
||||
|
||||
Returns a ``str``.
|
||||
"""
|
||||
|
||||
if value < 0 or value > 65535:
|
||||
raise ValueError("class must be between >= 0 and <= 65535")
|
||||
text = _by_value.get(value)
|
||||
if text is None:
|
||||
text = 'CLASS' + repr(value)
|
||||
return text
|
||||
|
||||
|
||||
def is_metaclass(rdclass):
|
||||
"""True if the specified class is a metaclass.
|
||||
|
||||
The currently defined metaclasses are ANY and NONE.
|
||||
|
||||
*rdclass* is an ``int``.
|
||||
"""
|
||||
|
||||
if rdclass in _metaclasses:
|
||||
return True
|
||||
return False
|
||||
@@ -1,347 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2001-2017 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
"""DNS rdatasets (an rdataset is a set of rdatas of a given type and class)"""
|
||||
|
||||
import random
|
||||
from io import StringIO
|
||||
import struct
|
||||
|
||||
import dns.exception
|
||||
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."""
|
||||
|
||||
__slots__ = ['rdclass', 'rdtype', 'covers', 'ttl']
|
||||
|
||||
def __init__(self, rdclass, rdtype, covers=dns.rdatatype.NONE, ttl=0):
|
||||
"""Create a new rdataset of the specified class and type.
|
||||
|
||||
*rdclass*, an ``int``, the rdataclass.
|
||||
|
||||
*rdtype*, an ``int``, the rdatatype.
|
||||
|
||||
*covers*, an ``int``, the covered rdatatype.
|
||||
|
||||
*ttl*, an ``int``, the TTL.
|
||||
"""
|
||||
|
||||
super(Rdataset, self).__init__()
|
||||
self.rdclass = rdclass
|
||||
self.rdtype = rdtype
|
||||
self.covers = covers
|
||||
self.ttl = ttl
|
||||
|
||||
def _clone(self):
|
||||
obj = super(Rdataset, self)._clone()
|
||||
obj.rdclass = self.rdclass
|
||||
obj.rdtype = self.rdtype
|
||||
obj.covers = self.covers
|
||||
obj.ttl = self.ttl
|
||||
return obj
|
||||
|
||||
def update_ttl(self, ttl):
|
||||
"""Perform TTL minimization.
|
||||
|
||||
Set the TTL of the rdataset to be the lesser of the set's current
|
||||
TTL or the specified TTL. If the set contains no rdatas, set the TTL
|
||||
to the specified TTL.
|
||||
|
||||
*ttl*, an ``int``.
|
||||
"""
|
||||
|
||||
if len(self) == 0:
|
||||
self.ttl = ttl
|
||||
elif ttl < self.ttl:
|
||||
self.ttl = ttl
|
||||
|
||||
def add(self, rd, ttl=None):
|
||||
"""Add the specified rdata to the rdataset.
|
||||
|
||||
If the optional *ttl* parameter is supplied, then
|
||||
``self.update_ttl(ttl)`` will be called prior to adding the rdata.
|
||||
|
||||
*rd*, a ``dns.rdata.Rdata``, the rdata
|
||||
|
||||
*ttl*, an ``int``, the TTL.
|
||||
|
||||
Raises ``dns.rdataset.IncompatibleTypes`` if the type and class
|
||||
do not match the type and class of the rdataset.
|
||||
|
||||
Raises ``dns.rdataset.DifferingCovers`` if the type is a signature
|
||||
type and the covered type does not match that of the rdataset.
|
||||
"""
|
||||
|
||||
#
|
||||
# If we're adding a signature, do some special handling to
|
||||
# check that the signature covers the same type as the
|
||||
# other rdatas in this rdataset. If this is the first rdata
|
||||
# in the set, initialize the covers field.
|
||||
#
|
||||
if self.rdclass != rd.rdclass or self.rdtype != rd.rdtype:
|
||||
raise IncompatibleTypes
|
||||
if ttl is not None:
|
||||
self.update_ttl(ttl)
|
||||
if self.rdtype == dns.rdatatype.RRSIG or \
|
||||
self.rdtype == dns.rdatatype.SIG:
|
||||
covers = rd.covers()
|
||||
if len(self) == 0 and self.covers == dns.rdatatype.NONE:
|
||||
self.covers = covers
|
||||
elif self.covers != covers:
|
||||
raise DifferingCovers
|
||||
if dns.rdatatype.is_singleton(rd.rdtype) and len(self) > 0:
|
||||
self.clear()
|
||||
super(Rdataset, self).add(rd)
|
||||
|
||||
def union_update(self, other):
|
||||
self.update_ttl(other.ttl)
|
||||
super(Rdataset, self).union_update(other)
|
||||
|
||||
def intersection_update(self, other):
|
||||
self.update_ttl(other.ttl)
|
||||
super(Rdataset, self).intersection_update(other)
|
||||
|
||||
def update(self, other):
|
||||
"""Add all rdatas in other to self.
|
||||
|
||||
*other*, a ``dns.rdataset.Rdataset``, the rdataset from which
|
||||
to update.
|
||||
"""
|
||||
|
||||
self.update_ttl(other.ttl)
|
||||
super(Rdataset, self).update(other)
|
||||
|
||||
def __repr__(self):
|
||||
if self.covers == 0:
|
||||
ctext = ''
|
||||
else:
|
||||
ctext = '(' + dns.rdatatype.to_text(self.covers) + ')'
|
||||
return '<DNS ' + dns.rdataclass.to_text(self.rdclass) + ' ' + \
|
||||
dns.rdatatype.to_text(self.rdtype) + ctext + ' rdataset>'
|
||||
|
||||
def __str__(self):
|
||||
return self.to_text()
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Rdataset):
|
||||
return False
|
||||
if self.rdclass != other.rdclass or \
|
||||
self.rdtype != other.rdtype or \
|
||||
self.covers != other.covers:
|
||||
return False
|
||||
return super(Rdataset, self).__eq__(other)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def to_text(self, name=None, origin=None, relativize=True,
|
||||
override_rdclass=None, **kw):
|
||||
"""Convert the rdataset into DNS master file format.
|
||||
|
||||
See ``dns.name.Name.choose_relativity`` for more information
|
||||
on how *origin* and *relativize* determine the way names
|
||||
are emitted.
|
||||
|
||||
Any additional keyword arguments are passed on to the rdata
|
||||
``to_text()`` method.
|
||||
|
||||
*name*, a ``dns.name.Name``. If name is not ``None``, emit RRs with
|
||||
*name* as the owner name.
|
||||
|
||||
*origin*, a ``dns.name.Name`` or ``None``, the origin for relative
|
||||
names.
|
||||
|
||||
*relativize*, a ``bool``. If ``True``, names will be relativized
|
||||
to *origin*.
|
||||
"""
|
||||
|
||||
if name is not None:
|
||||
name = name.choose_relativity(origin, relativize)
|
||||
ntext = str(name)
|
||||
pad = ' '
|
||||
else:
|
||||
ntext = ''
|
||||
pad = ''
|
||||
s = StringIO()
|
||||
if override_rdclass is not None:
|
||||
rdclass = override_rdclass
|
||||
else:
|
||||
rdclass = self.rdclass
|
||||
if len(self) == 0:
|
||||
#
|
||||
# Empty rdatasets are used for the question section, and in
|
||||
# some dynamic updates, so we don't need to print out the TTL
|
||||
# (which is meaningless anyway).
|
||||
#
|
||||
s.write(u'{}{}{} {}\n'.format(ntext, pad,
|
||||
dns.rdataclass.to_text(rdclass),
|
||||
dns.rdatatype.to_text(self.rdtype)))
|
||||
else:
|
||||
for rd in self:
|
||||
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
|
||||
#
|
||||
return s.getvalue()[:-1]
|
||||
|
||||
def to_wire(self, name, file, compress=None, origin=None,
|
||||
override_rdclass=None, want_shuffle=True):
|
||||
"""Convert the rdataset to wire format.
|
||||
|
||||
*name*, a ``dns.name.Name`` is the owner name to use.
|
||||
|
||||
*file* is the file where the name is emitted (typically a
|
||||
BytesIO file).
|
||||
|
||||
*compress*, a ``dict``, is the compression table to use. If
|
||||
``None`` (the default), names will not be compressed.
|
||||
|
||||
*origin* is a ``dns.name.Name`` or ``None``. If the name is
|
||||
relative and origin is not ``None``, then *origin* will be appended
|
||||
to it.
|
||||
|
||||
*override_rdclass*, an ``int``, is used as the class instead of the
|
||||
class of the rdataset. This is useful when rendering rdatasets
|
||||
associated with dynamic updates.
|
||||
|
||||
*want_shuffle*, a ``bool``. If ``True``, then the order of the
|
||||
Rdatas within the Rdataset will be shuffled before rendering.
|
||||
|
||||
Returns an ``int``, the number of records emitted.
|
||||
"""
|
||||
|
||||
if override_rdclass is not None:
|
||||
rdclass = override_rdclass
|
||||
want_shuffle = False
|
||||
else:
|
||||
rdclass = self.rdclass
|
||||
file.seek(0, 2)
|
||||
if len(self) == 0:
|
||||
name.to_wire(file, compress, origin)
|
||||
stuff = struct.pack("!HHIH", self.rdtype, rdclass, 0, 0)
|
||||
file.write(stuff)
|
||||
return 1
|
||||
else:
|
||||
if want_shuffle:
|
||||
l = list(self)
|
||||
random.shuffle(l)
|
||||
else:
|
||||
l = self
|
||||
for rd in l:
|
||||
name.to_wire(file, compress, origin)
|
||||
stuff = struct.pack("!HHIH", self.rdtype, rdclass,
|
||||
self.ttl, 0)
|
||||
file.write(stuff)
|
||||
start = file.tell()
|
||||
rd.to_wire(file, compress, origin)
|
||||
end = file.tell()
|
||||
assert end - start < 65536
|
||||
file.seek(start - 2)
|
||||
stuff = struct.pack("!H", end - start)
|
||||
file.write(stuff)
|
||||
file.seek(0, 2)
|
||||
return len(self)
|
||||
|
||||
def match(self, rdclass, rdtype, covers):
|
||||
"""Returns ``True`` if this rdataset matches the specified class,
|
||||
type, and covers.
|
||||
"""
|
||||
if self.rdclass == rdclass and \
|
||||
self.rdtype == rdtype and \
|
||||
self.covers == covers:
|
||||
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.
|
||||
|
||||
Returns a ``dns.rdataset.Rdataset`` object.
|
||||
"""
|
||||
|
||||
if isinstance(rdclass, string_types):
|
||||
rdclass = dns.rdataclass.from_text(rdclass)
|
||||
if isinstance(rdtype, string_types):
|
||||
rdtype = dns.rdatatype.from_text(rdtype)
|
||||
r = Rdataset(rdclass, rdtype)
|
||||
r.update_ttl(ttl)
|
||||
for t in text_rdatas:
|
||||
rd = dns.rdata.from_text(r.rdclass, r.rdtype, t)
|
||||
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.
|
||||
|
||||
Returns a ``dns.rdataset.Rdataset`` object.
|
||||
"""
|
||||
|
||||
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.
|
||||
|
||||
Returns a ``dns.rdataset.Rdataset`` object.
|
||||
"""
|
||||
|
||||
if len(rdatas) == 0:
|
||||
raise ValueError("rdata list must not be empty")
|
||||
r = None
|
||||
for rd in rdatas:
|
||||
if r is None:
|
||||
r = Rdataset(rd.rdclass, rd.rdtype)
|
||||
r.update_ttl(ttl)
|
||||
r.add(rd)
|
||||
return r
|
||||
|
||||
|
||||
def from_rdata(ttl, *rdatas):
|
||||
"""Create an rdataset with the specified TTL, and with
|
||||
the specified rdata objects.
|
||||
|
||||
Returns a ``dns.rdataset.Rdataset`` object.
|
||||
"""
|
||||
|
||||
return from_rdata_list(ttl, rdatas)
|
||||
@@ -1,58 +0,0 @@
|
||||
from typing import Optional, Dict, List, Union
|
||||
from io import BytesIO
|
||||
from . import exception, name, set, rdatatype, rdata, rdataset
|
||||
|
||||
class DifferingCovers(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(exception.DNSException):
|
||||
"""An attempt was made to add DNS RR data of an incompatible type."""
|
||||
|
||||
|
||||
class Rdataset(set.Set):
|
||||
def __init__(self, rdclass, rdtype, covers=rdatatype.NONE, ttl=0):
|
||||
self.rdclass : int = rdclass
|
||||
self.rdtype : int = rdtype
|
||||
self.covers : int = covers
|
||||
self.ttl : int = ttl
|
||||
|
||||
def update_ttl(self, ttl : int) -> None:
|
||||
...
|
||||
|
||||
def add(self, rd : rdata.Rdata, ttl : Optional[int] =None):
|
||||
...
|
||||
|
||||
def union_update(self, other : Rdataset):
|
||||
...
|
||||
|
||||
def intersection_update(self, other : Rdataset):
|
||||
...
|
||||
|
||||
def update(self, other : Rdataset):
|
||||
...
|
||||
|
||||
def to_text(self, name : Optional[name.Name] =None, origin : Optional[name.Name] =None, relativize=True,
|
||||
override_rdclass : Optional[int] =None, **kw) -> bytes:
|
||||
...
|
||||
|
||||
def to_wire(self, name : Optional[name.Name], file : BytesIO, compress : Optional[Dict[name.Name, int]] = None, origin : Optional[name.Name] = None,
|
||||
override_rdclass : Optional[int] = None, want_shuffle=True) -> int:
|
||||
...
|
||||
|
||||
def match(self, rdclass : int, rdtype : int, covers : int) -> bool:
|
||||
...
|
||||
|
||||
|
||||
def from_text_list(rdclass : Union[int,str], rdtype : Union[int,str], ttl : int, text_rdatas : str) -> rdataset.Rdataset:
|
||||
...
|
||||
|
||||
def from_text(rdclass : Union[int,str], rdtype : Union[int,str], ttl : int, *text_rdatas : str) -> rdataset.Rdataset:
|
||||
...
|
||||
|
||||
def from_rdata_list(ttl : int, rdatas : List[rdata.Rdata]) -> rdataset.Rdataset:
|
||||
...
|
||||
|
||||
def from_rdata(ttl : int, *rdatas : List[rdata.Rdata]) -> rdataset.Rdataset:
|
||||
...
|
||||
@@ -1,287 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2001-2017 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
"""DNS Rdata Types."""
|
||||
|
||||
import re
|
||||
|
||||
import dns.exception
|
||||
|
||||
NONE = 0
|
||||
A = 1
|
||||
NS = 2
|
||||
MD = 3
|
||||
MF = 4
|
||||
CNAME = 5
|
||||
SOA = 6
|
||||
MB = 7
|
||||
MG = 8
|
||||
MR = 9
|
||||
NULL = 10
|
||||
WKS = 11
|
||||
PTR = 12
|
||||
HINFO = 13
|
||||
MINFO = 14
|
||||
MX = 15
|
||||
TXT = 16
|
||||
RP = 17
|
||||
AFSDB = 18
|
||||
X25 = 19
|
||||
ISDN = 20
|
||||
RT = 21
|
||||
NSAP = 22
|
||||
NSAP_PTR = 23
|
||||
SIG = 24
|
||||
KEY = 25
|
||||
PX = 26
|
||||
GPOS = 27
|
||||
AAAA = 28
|
||||
LOC = 29
|
||||
NXT = 30
|
||||
SRV = 33
|
||||
NAPTR = 35
|
||||
KX = 36
|
||||
CERT = 37
|
||||
A6 = 38
|
||||
DNAME = 39
|
||||
OPT = 41
|
||||
APL = 42
|
||||
DS = 43
|
||||
SSHFP = 44
|
||||
IPSECKEY = 45
|
||||
RRSIG = 46
|
||||
NSEC = 47
|
||||
DNSKEY = 48
|
||||
DHCID = 49
|
||||
NSEC3 = 50
|
||||
NSEC3PARAM = 51
|
||||
TLSA = 52
|
||||
HIP = 55
|
||||
CDS = 59
|
||||
CDNSKEY = 60
|
||||
OPENPGPKEY = 61
|
||||
CSYNC = 62
|
||||
SPF = 99
|
||||
UNSPEC = 103
|
||||
EUI48 = 108
|
||||
EUI64 = 109
|
||||
TKEY = 249
|
||||
TSIG = 250
|
||||
IXFR = 251
|
||||
AXFR = 252
|
||||
MAILB = 253
|
||||
MAILA = 254
|
||||
ANY = 255
|
||||
URI = 256
|
||||
CAA = 257
|
||||
AVC = 258
|
||||
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,
|
||||
'OPENPGPKEY': OPENPGPKEY,
|
||||
'CSYNC': CSYNC,
|
||||
'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,
|
||||
'AVC': AVC,
|
||||
'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 = {y: x for x, y in _by_text.items()}
|
||||
|
||||
_metatypes = {
|
||||
OPT: True
|
||||
}
|
||||
|
||||
_singletons = {
|
||||
SOA: True,
|
||||
NXT: True,
|
||||
DNAME: True,
|
||||
NSEC: True,
|
||||
CNAME: True,
|
||||
}
|
||||
|
||||
_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.
|
||||
|
||||
The input text can be a defined DNS RR type mnemonic or
|
||||
instance of the DNS generic type syntax.
|
||||
|
||||
For example, "NS" and "TYPE2" will both result in a value of 2.
|
||||
|
||||
Raises ``dns.rdatatype.UnknownRdatatype`` if the type is unknown.
|
||||
|
||||
Raises ``ValueError`` if the rdata type value is not >= 0 and <= 65535.
|
||||
|
||||
Returns an ``int``.
|
||||
"""
|
||||
|
||||
value = _by_text.get(text.upper())
|
||||
if value is None:
|
||||
match = _unknown_type_pattern.match(text)
|
||||
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 value to text.
|
||||
|
||||
If the value has a known mnemonic, it will be used, otherwise the
|
||||
DNS generic type syntax will be used.
|
||||
|
||||
Raises ``ValueError`` if the rdata type value is not >= 0 and <= 65535.
|
||||
|
||||
Returns a ``str``.
|
||||
"""
|
||||
|
||||
if value < 0 or value > 65535:
|
||||
raise ValueError("type must be between >= 0 and <= 65535")
|
||||
text = _by_value.get(value)
|
||||
if text is None:
|
||||
text = 'TYPE' + repr(value)
|
||||
return text
|
||||
|
||||
|
||||
def is_metatype(rdtype):
|
||||
"""True if the specified type is a metatype.
|
||||
|
||||
*rdtype* is an ``int``.
|
||||
|
||||
The currently defined metatypes are TKEY, TSIG, IXFR, AXFR, MAILA,
|
||||
MAILB, ANY, and OPT.
|
||||
|
||||
Returns a ``bool``.
|
||||
"""
|
||||
|
||||
if rdtype >= TKEY and rdtype <= ANY or rdtype in _metatypes:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def is_singleton(rdtype):
|
||||
"""Is the specified type a singleton type?
|
||||
|
||||
Singleton types can only have a single rdata in an rdataset, or a single
|
||||
RR in an RRset.
|
||||
|
||||
The currently defined singleton types are CNAME, DNAME, NSEC, NXT, and
|
||||
SOA.
|
||||
|
||||
*rdtype* is an ``int``.
|
||||
|
||||
Returns a ``bool``.
|
||||
"""
|
||||
|
||||
if rdtype in _singletons:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def register_type(rdtype, rdtype_text, is_singleton=False): # pylint: disable=redefined-outer-name
|
||||
"""Dynamically register an rdatatype.
|
||||
|
||||
*rdtype*, an ``int``, the rdatatype to register.
|
||||
|
||||
*rdtype_text*, a ``text``, the textual form of the rdatatype.
|
||||
|
||||
*is_singleton*, a ``bool``, indicating if the type is a singleton (i.e.
|
||||
RRsets of the type can have only one member.)
|
||||
"""
|
||||
|
||||
_by_text[rdtype_text] = rdtype
|
||||
_by_value[rdtype] = rdtype_text
|
||||
if is_singleton:
|
||||
_singletons[rdtype] = True
|
||||
@@ -1,55 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.mxbase
|
||||
|
||||
|
||||
class AFSDB(dns.rdtypes.mxbase.UncompressedDowncasingMX):
|
||||
|
||||
"""AFSDB record
|
||||
|
||||
@ivar subtype: the subtype value
|
||||
@type subtype: int
|
||||
@ivar hostname: the hostname name
|
||||
@type hostname: dns.name.Name object"""
|
||||
|
||||
# Use the property mechanism to make "subtype" an alias for the
|
||||
# "preference" attribute, and "hostname" an alias for the "exchange"
|
||||
# attribute.
|
||||
#
|
||||
# This lets us inherit the UncompressedMX implementation but lets
|
||||
# the caller use appropriate attribute names for the rdata type.
|
||||
#
|
||||
# We probably lose some performance vs. a cut-and-paste
|
||||
# implementation, but this way we don't copy code, and that's
|
||||
# good.
|
||||
|
||||
def get_subtype(self):
|
||||
return self.preference
|
||||
|
||||
def set_subtype(self, subtype):
|
||||
self.preference = subtype
|
||||
|
||||
subtype = property(get_subtype, set_subtype)
|
||||
|
||||
def get_hostname(self):
|
||||
return self.exchange
|
||||
|
||||
def set_hostname(self, hostname):
|
||||
self.exchange = hostname
|
||||
|
||||
hostname = property(get_hostname, set_hostname)
|
||||
@@ -1,25 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2016 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.txtbase
|
||||
|
||||
|
||||
class AVC(dns.rdtypes.txtbase.TXTBase):
|
||||
|
||||
"""AVC record
|
||||
|
||||
@see: U{http://www.iana.org/assignments/dns-parameters/AVC/avc-completed-template}"""
|
||||
@@ -1,75 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# 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
|
||||
|
||||
|
||||
class CAA(dns.rdata.Rdata):
|
||||
|
||||
"""CAA (Certification Authority Authorization) record
|
||||
|
||||
@ivar flags: the flags
|
||||
@type flags: int
|
||||
@ivar tag: the tag
|
||||
@type tag: string
|
||||
@ivar value: the value
|
||||
@type value: string
|
||||
@see: RFC 6844"""
|
||||
|
||||
__slots__ = ['flags', 'tag', 'value']
|
||||
|
||||
def __init__(self, rdclass, rdtype, flags, tag, value):
|
||||
super(CAA, self).__init__(rdclass, rdtype)
|
||||
self.flags = flags
|
||||
self.tag = tag
|
||||
self.value = value
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
return '%u %s "%s"' % (self.flags,
|
||||
dns.rdata._escapify(self.tag),
|
||||
dns.rdata._escapify(self.value))
|
||||
|
||||
@classmethod
|
||||
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True):
|
||||
flags = tok.get_uint8()
|
||||
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().encode()
|
||||
return cls(rdclass, rdtype, flags, tag, value)
|
||||
|
||||
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(struct.pack('!B', l))
|
||||
file.write(self.tag)
|
||||
file.write(self.value)
|
||||
|
||||
@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]
|
||||
value = wire[current + l:current + rdlen - 2]
|
||||
return cls(rdclass, rdtype, flags, tag, value)
|
||||
@@ -1,27 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2004-2007, 2009-2011 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.dnskeybase
|
||||
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"""
|
||||
@@ -1,23 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.dsbase
|
||||
|
||||
|
||||
class CDS(dns.rdtypes.dsbase.DSBase):
|
||||
|
||||
"""CDS record"""
|
||||
@@ -1,123 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# 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 base64
|
||||
|
||||
import dns.exception
|
||||
import dns.dnssec
|
||||
import dns.rdata
|
||||
import dns.tokenizer
|
||||
|
||||
_ctype_by_value = {
|
||||
1: 'PKIX',
|
||||
2: 'SPKI',
|
||||
3: 'PGP',
|
||||
253: 'URI',
|
||||
254: 'OID',
|
||||
}
|
||||
|
||||
_ctype_by_name = {
|
||||
'PKIX': 1,
|
||||
'SPKI': 2,
|
||||
'PGP': 3,
|
||||
'URI': 253,
|
||||
'OID': 254,
|
||||
}
|
||||
|
||||
|
||||
def _ctype_from_text(what):
|
||||
v = _ctype_by_name.get(what)
|
||||
if v is not None:
|
||||
return v
|
||||
return int(what)
|
||||
|
||||
|
||||
def _ctype_to_text(what):
|
||||
v = _ctype_by_value.get(what)
|
||||
if v is not None:
|
||||
return v
|
||||
return str(what)
|
||||
|
||||
|
||||
class CERT(dns.rdata.Rdata):
|
||||
|
||||
"""CERT record
|
||||
|
||||
@ivar certificate_type: certificate type
|
||||
@type certificate_type: int
|
||||
@ivar key_tag: key tag
|
||||
@type key_tag: int
|
||||
@ivar algorithm: algorithm
|
||||
@type algorithm: int
|
||||
@ivar certificate: the certificate or CRL
|
||||
@type certificate: string
|
||||
@see: RFC 2538"""
|
||||
|
||||
__slots__ = ['certificate_type', 'key_tag', 'algorithm', 'certificate']
|
||||
|
||||
def __init__(self, rdclass, rdtype, certificate_type, key_tag, algorithm,
|
||||
certificate):
|
||||
super(CERT, self).__init__(rdclass, rdtype)
|
||||
self.certificate_type = certificate_type
|
||||
self.key_tag = key_tag
|
||||
self.algorithm = algorithm
|
||||
self.certificate = certificate
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
certificate_type = _ctype_to_text(self.certificate_type)
|
||||
return "%s %d %s %s" % (certificate_type, self.key_tag,
|
||||
dns.dnssec.algorithm_to_text(self.algorithm),
|
||||
dns.rdata._base64ify(self.certificate))
|
||||
|
||||
@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())
|
||||
if algorithm < 0 or algorithm > 255:
|
||||
raise dns.exception.SyntaxError("bad algorithm type")
|
||||
chunks = []
|
||||
while 1:
|
||||
t = tok.get().unescape()
|
||||
if t.is_eol_or_eof():
|
||||
break
|
||||
if not t.is_identifier():
|
||||
raise dns.exception.SyntaxError
|
||||
chunks.append(t.value.encode())
|
||||
b64 = b''.join(chunks)
|
||||
certificate = base64.b64decode(b64)
|
||||
return cls(rdclass, rdtype, certificate_type, key_tag,
|
||||
algorithm, certificate)
|
||||
|
||||
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)
|
||||
|
||||
@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()
|
||||
return cls(rdclass, rdtype, certificate_type, key_tag, algorithm,
|
||||
certificate)
|
||||
@@ -1,27 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.nsbase
|
||||
|
||||
|
||||
class CNAME(dns.rdtypes.nsbase.NSBase):
|
||||
|
||||
"""CNAME record
|
||||
|
||||
Note: although CNAME is officially a singleton type, dnspython allows
|
||||
non-singleton CNAME rdatasets because such sets have been commonly
|
||||
used by BIND and other nameservers for load balancing."""
|
||||
@@ -1,126 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2004-2007, 2009-2011, 2016 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# 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.rdatatype
|
||||
import dns.name
|
||||
from dns._compat import xrange
|
||||
|
||||
class CSYNC(dns.rdata.Rdata):
|
||||
|
||||
"""CSYNC record
|
||||
|
||||
@ivar serial: the SOA serial number
|
||||
@type serial: int
|
||||
@ivar flags: the CSYNC flags
|
||||
@type flags: int
|
||||
@ivar windows: the windowed bitmap list
|
||||
@type windows: list of (window number, string) tuples"""
|
||||
|
||||
__slots__ = ['serial', 'flags', 'windows']
|
||||
|
||||
def __init__(self, rdclass, rdtype, serial, flags, windows):
|
||||
super(CSYNC, self).__init__(rdclass, rdtype)
|
||||
self.serial = serial
|
||||
self.flags = flags
|
||||
self.windows = windows
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
text = ''
|
||||
for (window, bitmap) in self.windows:
|
||||
bits = []
|
||||
for i in xrange(0, len(bitmap)):
|
||||
byte = bitmap[i]
|
||||
for j in xrange(0, 8):
|
||||
if byte & (0x80 >> j):
|
||||
bits.append(dns.rdatatype.to_text(window * 256 +
|
||||
i * 8 + j))
|
||||
text += (' ' + ' '.join(bits))
|
||||
return '%d %d%s' % (self.serial, self.flags, text)
|
||||
|
||||
@classmethod
|
||||
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True):
|
||||
serial = tok.get_uint32()
|
||||
flags = tok.get_uint16()
|
||||
rdtypes = []
|
||||
while 1:
|
||||
token = tok.get().unescape()
|
||||
if token.is_eol_or_eof():
|
||||
break
|
||||
nrdtype = dns.rdatatype.from_text(token.value)
|
||||
if nrdtype == 0:
|
||||
raise dns.exception.SyntaxError("CSYNC with bit 0")
|
||||
if nrdtype > 65535:
|
||||
raise dns.exception.SyntaxError("CSYNC with bit > 65535")
|
||||
rdtypes.append(nrdtype)
|
||||
rdtypes.sort()
|
||||
window = 0
|
||||
octets = 0
|
||||
prior_rdtype = 0
|
||||
bitmap = bytearray(b'\0' * 32)
|
||||
windows = []
|
||||
for nrdtype in rdtypes:
|
||||
if nrdtype == prior_rdtype:
|
||||
continue
|
||||
prior_rdtype = nrdtype
|
||||
new_window = nrdtype // 256
|
||||
if new_window != window:
|
||||
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] = bitmap[byte] | (0x80 >> bit)
|
||||
|
||||
windows.append((window, bitmap[0:octets]))
|
||||
return cls(rdclass, rdtype, serial, flags, windows)
|
||||
|
||||
def to_wire(self, file, compress=None, origin=None):
|
||||
file.write(struct.pack('!IH', self.serial, self.flags))
|
||||
for (window, bitmap) in self.windows:
|
||||
file.write(struct.pack('!BB', window, len(bitmap)))
|
||||
file.write(bitmap)
|
||||
|
||||
@classmethod
|
||||
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None):
|
||||
if rdlen < 6:
|
||||
raise dns.exception.FormError("CSYNC too short")
|
||||
(serial, flags) = struct.unpack("!IH", wire[current: current + 6])
|
||||
current += 6
|
||||
rdlen -= 6
|
||||
windows = []
|
||||
while rdlen > 0:
|
||||
if rdlen < 3:
|
||||
raise dns.exception.FormError("CSYNC too short")
|
||||
window = wire[current]
|
||||
octets = wire[current + 1]
|
||||
if octets == 0 or octets > 32:
|
||||
raise dns.exception.FormError("bad CSYNC octets")
|
||||
current += 2
|
||||
rdlen -= 2
|
||||
if rdlen < octets:
|
||||
raise dns.exception.FormError("bad CSYNC bitmap length")
|
||||
bitmap = bytearray(wire[current: current + octets].unwrap())
|
||||
current += octets
|
||||
rdlen -= octets
|
||||
windows.append((window, bitmap))
|
||||
return cls(rdclass, rdtype, serial, flags, windows)
|
||||
@@ -1,23 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2009-2011 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.dsbase
|
||||
|
||||
|
||||
class DLV(dns.rdtypes.dsbase.DSBase):
|
||||
|
||||
"""DLV record"""
|
||||
@@ -1,26 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.nsbase
|
||||
|
||||
|
||||
class DNAME(dns.rdtypes.nsbase.UncompressedNS):
|
||||
|
||||
"""DNAME record"""
|
||||
|
||||
def to_digestable(self, origin=None):
|
||||
return self.target.to_digestable(origin)
|
||||
@@ -1,27 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2004-2007, 2009-2011 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.dnskeybase
|
||||
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"""
|
||||
@@ -1,23 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.dsbase
|
||||
|
||||
|
||||
class DS(dns.rdtypes.dsbase.DSBase):
|
||||
|
||||
"""DS record"""
|
||||
@@ -1,29 +0,0 @@
|
||||
# Copyright (C) 2015 Red Hat, Inc.
|
||||
# Author: Petr Spacek <pspacek@redhat.com>
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED 'AS IS' AND RED HAT DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.euibase
|
||||
|
||||
|
||||
class EUI48(dns.rdtypes.euibase.EUIBase):
|
||||
|
||||
"""EUI48 record
|
||||
|
||||
@ivar fingerprint: 48-bit Extended Unique Identifier (EUI-48)
|
||||
@type fingerprint: string
|
||||
@see: rfc7043.txt"""
|
||||
|
||||
byte_len = 6 # 0123456789ab (in hex)
|
||||
text_len = byte_len * 3 - 1 # 01-23-45-67-89-ab
|
||||
@@ -1,29 +0,0 @@
|
||||
# Copyright (C) 2015 Red Hat, Inc.
|
||||
# Author: Petr Spacek <pspacek@redhat.com>
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED 'AS IS' AND RED HAT DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.euibase
|
||||
|
||||
|
||||
class EUI64(dns.rdtypes.euibase.EUIBase):
|
||||
|
||||
"""EUI64 record
|
||||
|
||||
@ivar fingerprint: 64-bit Extended Unique Identifier (EUI-64)
|
||||
@type fingerprint: string
|
||||
@see: rfc7043.txt"""
|
||||
|
||||
byte_len = 8 # 0123456789abcdef (in hex)
|
||||
text_len = byte_len * 3 - 1 # 01-23-45-67-89-ab-cd-ef
|
||||
@@ -1,162 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# 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] == b'-'[0] or what[0] == b'+'[0]:
|
||||
what = what[1:]
|
||||
if what.isdigit():
|
||||
return
|
||||
(left, right) = what.split(b'.')
|
||||
if left == b'' and right == b'':
|
||||
raise dns.exception.FormError
|
||||
if not left == b'' and not left.decode().isdigit():
|
||||
raise dns.exception.FormError
|
||||
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
|
||||
@type latitude: string
|
||||
@ivar longitude: longitude
|
||||
@type longitude: string
|
||||
@ivar altitude: altitude
|
||||
@type altitude: string
|
||||
@see: RFC 1712"""
|
||||
|
||||
__slots__ = ['latitude', 'longitude', 'altitude']
|
||||
|
||||
def __init__(self, rdclass, rdtype, latitude, longitude, altitude):
|
||||
super(GPOS, self).__init__(rdclass, rdtype)
|
||||
if isinstance(latitude, float) or \
|
||||
isinstance(latitude, int) or \
|
||||
isinstance(latitude, long):
|
||||
latitude = str(latitude)
|
||||
if isinstance(longitude, float) or \
|
||||
isinstance(longitude, int) or \
|
||||
isinstance(longitude, long):
|
||||
longitude = str(longitude)
|
||||
if isinstance(altitude, float) or \
|
||||
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)
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
self.altitude = altitude
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
return '{} {} {}'.format(self.latitude.decode(),
|
||||
self.longitude.decode(),
|
||||
self.altitude.decode())
|
||||
|
||||
@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)
|
||||
|
||||
def to_wire(self, file, compress=None, origin=None):
|
||||
l = len(self.latitude)
|
||||
assert l < 256
|
||||
file.write(struct.pack('!B', l))
|
||||
file.write(self.latitude)
|
||||
l = len(self.longitude)
|
||||
assert l < 256
|
||||
file.write(struct.pack('!B', l))
|
||||
file.write(self.longitude)
|
||||
l = len(self.altitude)
|
||||
assert l < 256
|
||||
file.write(struct.pack('!B', l))
|
||||
file.write(self.altitude)
|
||||
|
||||
@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()
|
||||
current += l
|
||||
rdlen -= l
|
||||
l = wire[current]
|
||||
current += 1
|
||||
rdlen -= 1
|
||||
if l > rdlen:
|
||||
raise dns.exception.FormError
|
||||
longitude = wire[current: current + l].unwrap()
|
||||
current += l
|
||||
rdlen -= l
|
||||
l = wire[current]
|
||||
current += 1
|
||||
rdlen -= 1
|
||||
if l != rdlen:
|
||||
raise dns.exception.FormError
|
||||
altitude = wire[current: current + l].unwrap()
|
||||
return cls(rdclass, rdtype, latitude, longitude, altitude)
|
||||
|
||||
def _get_float_latitude(self):
|
||||
return float(self.latitude)
|
||||
|
||||
def _set_float_latitude(self, value):
|
||||
self.latitude = str(value)
|
||||
|
||||
float_latitude = property(_get_float_latitude, _set_float_latitude,
|
||||
doc="latitude as a floating point value")
|
||||
|
||||
def _get_float_longitude(self):
|
||||
return float(self.longitude)
|
||||
|
||||
def _set_float_longitude(self, value):
|
||||
self.longitude = str(value)
|
||||
|
||||
float_longitude = property(_get_float_longitude, _set_float_longitude,
|
||||
doc="longitude as a floating point value")
|
||||
|
||||
def _get_float_altitude(self):
|
||||
return float(self.altitude)
|
||||
|
||||
def _set_float_altitude(self, value):
|
||||
self.altitude = str(value)
|
||||
|
||||
float_altitude = property(_get_float_altitude, _set_float_altitude,
|
||||
doc="altitude as a floating point value")
|
||||
@@ -1,86 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# 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
|
||||
@type cpu: string
|
||||
@ivar os: the OS type
|
||||
@type os: string
|
||||
@see: RFC 1035"""
|
||||
|
||||
__slots__ = ['cpu', 'os']
|
||||
|
||||
def __init__(self, rdclass, rdtype, cpu, os):
|
||||
super(HINFO, self).__init__(rdclass, rdtype)
|
||||
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 '"{}" "{}"'.format(dns.rdata._escapify(self.cpu),
|
||||
dns.rdata._escapify(self.os))
|
||||
|
||||
@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)
|
||||
|
||||
def to_wire(self, file, compress=None, origin=None):
|
||||
l = len(self.cpu)
|
||||
assert l < 256
|
||||
file.write(struct.pack('!B', l))
|
||||
file.write(self.cpu)
|
||||
l = len(self.os)
|
||||
assert l < 256
|
||||
file.write(struct.pack('!B', l))
|
||||
file.write(self.os)
|
||||
|
||||
@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()
|
||||
current += l
|
||||
rdlen -= l
|
||||
l = wire[current]
|
||||
current += 1
|
||||
rdlen -= 1
|
||||
if l != rdlen:
|
||||
raise dns.exception.FormError
|
||||
os = wire[current: current + l].unwrap()
|
||||
return cls(rdclass, rdtype, cpu, os)
|
||||
@@ -1,115 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2010, 2011 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# 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 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
|
||||
@type hit: string
|
||||
@ivar algorithm: the public key cryptographic algorithm
|
||||
@type algorithm: int
|
||||
@ivar key: the public key
|
||||
@type key: string
|
||||
@ivar servers: the rendezvous servers
|
||||
@type servers: list of dns.name.Name objects
|
||||
@see: RFC 5205"""
|
||||
|
||||
__slots__ = ['hit', 'algorithm', 'key', 'servers']
|
||||
|
||||
def __init__(self, rdclass, rdtype, hit, algorithm, key, servers):
|
||||
super(HIP, self).__init__(rdclass, rdtype)
|
||||
self.hit = hit
|
||||
self.algorithm = algorithm
|
||||
self.key = key
|
||||
self.servers = servers
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
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(server.choose_relativity(origin, relativize))
|
||||
if len(servers) > 0:
|
||||
text += (u' ' + u' '.join((x.to_unicode() for x in servers)))
|
||||
return u'%u %s %s%s' % (self.algorithm, hit, key, text)
|
||||
|
||||
@classmethod
|
||||
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True):
|
||||
algorithm = tok.get_uint8()
|
||||
hit = binascii.unhexlify(tok.get_string().encode())
|
||||
if len(hit) > 255:
|
||||
raise dns.exception.SyntaxError("HIT too long")
|
||||
key = base64.b64decode(tok.get_string().encode())
|
||||
servers = []
|
||||
while 1:
|
||||
token = tok.get()
|
||||
if token.is_eol_or_eof():
|
||||
break
|
||||
server = dns.name.from_text(token.value, origin)
|
||||
server.choose_relativity(origin, relativize)
|
||||
servers.append(server)
|
||||
return cls(rdclass, rdtype, hit, algorithm, key, servers)
|
||||
|
||||
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))
|
||||
file.write(self.hit)
|
||||
file.write(self.key)
|
||||
for server in self.servers:
|
||||
server.to_wire(file, None, origin)
|
||||
|
||||
@classmethod
|
||||
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None):
|
||||
(lh, algorithm, lk) = struct.unpack('!BBH',
|
||||
wire[current: current + 4])
|
||||
current += 4
|
||||
rdlen -= 4
|
||||
hit = wire[current: current + lh].unwrap()
|
||||
current += lh
|
||||
rdlen -= lh
|
||||
key = wire[current: current + lk].unwrap()
|
||||
current += lk
|
||||
rdlen -= lk
|
||||
servers = []
|
||||
while rdlen > 0:
|
||||
(server, cused) = dns.name.from_wire(wire[: current + rdlen],
|
||||
current)
|
||||
current += cused
|
||||
rdlen -= cused
|
||||
if origin is not None:
|
||||
server = server.relativize(origin)
|
||||
servers.append(server)
|
||||
return cls(rdclass, rdtype, hit, algorithm, key, servers)
|
||||
|
||||
def choose_relativity(self, origin=None, relativize=True):
|
||||
servers = []
|
||||
for server in self.servers:
|
||||
server = server.choose_relativity(origin, relativize)
|
||||
servers.append(server)
|
||||
self.servers = servers
|
||||
@@ -1,99 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# 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
|
||||
@type address: string
|
||||
@ivar subaddress: the ISDN subaddress (or '' if not present)
|
||||
@type subaddress: string
|
||||
@see: RFC 1183"""
|
||||
|
||||
__slots__ = ['address', 'subaddress']
|
||||
|
||||
def __init__(self, rdclass, rdtype, address, subaddress):
|
||||
super(ISDN, self).__init__(rdclass, rdtype)
|
||||
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:
|
||||
return '"{}" "{}"'.format(dns.rdata._escapify(self.address),
|
||||
dns.rdata._escapify(self.subaddress))
|
||||
else:
|
||||
return '"%s"' % dns.rdata._escapify(self.address)
|
||||
|
||||
@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():
|
||||
tok.unget(t)
|
||||
subaddress = tok.get_string()
|
||||
else:
|
||||
tok.unget(t)
|
||||
subaddress = ''
|
||||
tok.get_eol()
|
||||
return cls(rdclass, rdtype, address, subaddress)
|
||||
|
||||
def to_wire(self, file, compress=None, origin=None):
|
||||
l = len(self.address)
|
||||
assert l < 256
|
||||
file.write(struct.pack('!B', l))
|
||||
file.write(self.address)
|
||||
l = len(self.subaddress)
|
||||
if l > 0:
|
||||
assert l < 256
|
||||
file.write(struct.pack('!B', l))
|
||||
file.write(self.subaddress)
|
||||
|
||||
@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()
|
||||
current += l
|
||||
rdlen -= l
|
||||
if rdlen > 0:
|
||||
l = wire[current]
|
||||
current += 1
|
||||
rdlen -= 1
|
||||
if l != rdlen:
|
||||
raise dns.exception.FormError
|
||||
subaddress = wire[current: current + l].unwrap()
|
||||
else:
|
||||
subaddress = ''
|
||||
return cls(rdclass, rdtype, address, subaddress)
|
||||
@@ -1,327 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
from __future__ import division
|
||||
|
||||
import struct
|
||||
|
||||
import dns.exception
|
||||
import dns.rdata
|
||||
from dns._compat import long, xrange, round_py2_compat
|
||||
|
||||
|
||||
_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] == 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
|
||||
what *= -1
|
||||
else:
|
||||
sign = 1
|
||||
what = round_py2_compat(what * 3600000)
|
||||
degrees = int(what // 3600000)
|
||||
what -= degrees * 3600000
|
||||
minutes = int(what // 60000)
|
||||
what -= minutes * 60000
|
||||
seconds = int(what // 1000)
|
||||
what -= int(seconds * 1000)
|
||||
what = int(what)
|
||||
return (degrees, minutes, seconds, what, sign)
|
||||
|
||||
|
||||
def _tuple_to_float(what):
|
||||
value = float(what[0])
|
||||
value += float(what[1]) / 60.0
|
||||
value += float(what[2]) / 3600.0
|
||||
value += float(what[3]) / 3600000.0
|
||||
return float(what[4]) * value
|
||||
|
||||
|
||||
def _encode_size(what, desc):
|
||||
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:
|
||||
raise dns.exception.SyntaxError("bad %s exponent" % desc)
|
||||
base = (what & 0xF0) >> 4
|
||||
if base > 9:
|
||||
raise dns.exception.SyntaxError("bad %s base" % desc)
|
||||
return long(base) * pow(10, exponent)
|
||||
|
||||
|
||||
class LOC(dns.rdata.Rdata):
|
||||
|
||||
"""LOC record
|
||||
|
||||
@ivar latitude: latitude
|
||||
@type latitude: (int, int, int, int, sign) tuple specifying the degrees, minutes,
|
||||
seconds, milliseconds, and sign of the coordinate.
|
||||
@ivar longitude: longitude
|
||||
@type longitude: (int, int, int, int, sign) tuple specifying the degrees,
|
||||
minutes, seconds, milliseconds, and sign of the coordinate.
|
||||
@ivar altitude: altitude
|
||||
@type altitude: float
|
||||
@ivar size: size of the sphere
|
||||
@type size: float
|
||||
@ivar horizontal_precision: horizontal precision
|
||||
@type horizontal_precision: float
|
||||
@ivar vertical_precision: vertical precision
|
||||
@type vertical_precision: float
|
||||
@see: RFC 1876"""
|
||||
|
||||
__slots__ = ['latitude', 'longitude', 'altitude', 'size',
|
||||
'horizontal_precision', 'vertical_precision']
|
||||
|
||||
def __init__(self, rdclass, rdtype, latitude, longitude, altitude,
|
||||
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
|
||||
of integers specifying (degrees, minutes, seconds, milliseconds),
|
||||
or they may be floating point values specifying the number of
|
||||
degrees. The other parameters are floats. Size, horizontal precision,
|
||||
and vertical precision are specified in centimeters."""
|
||||
|
||||
super(LOC, self).__init__(rdclass, rdtype)
|
||||
if isinstance(latitude, int) or isinstance(latitude, long):
|
||||
latitude = float(latitude)
|
||||
if isinstance(latitude, float):
|
||||
latitude = _float_to_tuple(latitude)
|
||||
self.latitude = latitude
|
||||
if isinstance(longitude, int) or isinstance(longitude, long):
|
||||
longitude = float(longitude)
|
||||
if isinstance(longitude, float):
|
||||
longitude = _float_to_tuple(longitude)
|
||||
self.longitude = longitude
|
||||
self.altitude = float(altitude)
|
||||
self.size = float(size)
|
||||
self.horizontal_precision = float(hprec)
|
||||
self.vertical_precision = float(vprec)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
if self.latitude[4] > 0:
|
||||
lat_hemisphere = 'N'
|
||||
else:
|
||||
lat_hemisphere = 'S'
|
||||
if self.longitude[4] > 0:
|
||||
long_hemisphere = 'E'
|
||||
else:
|
||||
long_hemisphere = 'W'
|
||||
text = "%d %d %d.%03d %s %d %d %d.%03d %s %0.2fm" % (
|
||||
self.latitude[0], self.latitude[1],
|
||||
self.latitude[2], self.latitude[3], lat_hemisphere,
|
||||
self.longitude[0], 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:
|
||||
text += " {:0.2f}m {:0.2f}m {:0.2f}m".format(
|
||||
self.size / 100.0, self.horizontal_precision / 100.0,
|
||||
self.vertical_precision / 100.0
|
||||
)
|
||||
return text
|
||||
|
||||
@classmethod
|
||||
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True):
|
||||
latitude = [0, 0, 0, 0, 1]
|
||||
longitude = [0, 0, 0, 0, 1]
|
||||
size = _default_size
|
||||
hprec = _default_hprec
|
||||
vprec = _default_vprec
|
||||
|
||||
latitude[0] = tok.get_int()
|
||||
t = tok.get_string()
|
||||
if t.isdigit():
|
||||
latitude[1] = int(t)
|
||||
t = tok.get_string()
|
||||
if '.' in t:
|
||||
(seconds, milliseconds) = t.split('.')
|
||||
if not seconds.isdigit():
|
||||
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')
|
||||
if l == 1:
|
||||
m = 100
|
||||
elif l == 2:
|
||||
m = 10
|
||||
else:
|
||||
m = 1
|
||||
latitude[3] = m * int(milliseconds)
|
||||
t = tok.get_string()
|
||||
elif t.isdigit():
|
||||
latitude[2] = int(t)
|
||||
t = tok.get_string()
|
||||
if t == 'S':
|
||||
latitude[4] = -1
|
||||
elif t != 'N':
|
||||
raise dns.exception.SyntaxError('bad latitude hemisphere value')
|
||||
|
||||
longitude[0] = tok.get_int()
|
||||
t = tok.get_string()
|
||||
if t.isdigit():
|
||||
longitude[1] = int(t)
|
||||
t = tok.get_string()
|
||||
if '.' in t:
|
||||
(seconds, milliseconds) = t.split('.')
|
||||
if not seconds.isdigit():
|
||||
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')
|
||||
if l == 1:
|
||||
m = 100
|
||||
elif l == 2:
|
||||
m = 10
|
||||
else:
|
||||
m = 1
|
||||
longitude[3] = m * int(milliseconds)
|
||||
t = tok.get_string()
|
||||
elif t.isdigit():
|
||||
longitude[2] = int(t)
|
||||
t = tok.get_string()
|
||||
if t == 'W':
|
||||
longitude[4] = -1
|
||||
elif t != 'E':
|
||||
raise dns.exception.SyntaxError('bad longitude hemisphere value')
|
||||
|
||||
t = tok.get_string()
|
||||
if t[-1] == 'm':
|
||||
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]
|
||||
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]
|
||||
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]
|
||||
vprec = float(value) * 100.0 # m -> cm
|
||||
tok.get_eol()
|
||||
|
||||
return cls(rdclass, rdtype, latitude, longitude, altitude,
|
||||
size, hprec, vprec)
|
||||
|
||||
def to_wire(self, file, compress=None, origin=None):
|
||||
milliseconds = (self.latitude[0] * 3600000 +
|
||||
self.latitude[1] * 60000 +
|
||||
self.latitude[2] * 1000 +
|
||||
self.latitude[3]) * self.latitude[4]
|
||||
latitude = long(0x80000000) + milliseconds
|
||||
milliseconds = (self.longitude[0] * 3600000 +
|
||||
self.longitude[1] * 60000 +
|
||||
self.longitude[2] * 1000 +
|
||||
self.longitude[3]) * self.longitude[4]
|
||||
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")
|
||||
wire = struct.pack("!BBBBIII", 0, size, hprec, vprec, latitude,
|
||||
longitude, altitude)
|
||||
file.write(wire)
|
||||
|
||||
@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 > long(0x80000000):
|
||||
latitude = float(latitude - long(0x80000000)) / 3600000
|
||||
else:
|
||||
latitude = -1 * float(long(0x80000000) - latitude) / 3600000
|
||||
if latitude < -90.0 or latitude > 90.0:
|
||||
raise dns.exception.FormError("bad latitude")
|
||||
if longitude > long(0x80000000):
|
||||
longitude = float(longitude - long(0x80000000)) / 3600000
|
||||
else:
|
||||
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
|
||||
size = _decode_size(size, "size")
|
||||
hprec = _decode_size(hprec, "horizontal precision")
|
||||
vprec = _decode_size(vprec, "vertical precision")
|
||||
return cls(rdclass, rdtype, latitude, longitude, altitude,
|
||||
size, hprec, vprec)
|
||||
|
||||
def _get_float_latitude(self):
|
||||
return _tuple_to_float(self.latitude)
|
||||
|
||||
def _set_float_latitude(self, value):
|
||||
self.latitude = _float_to_tuple(value)
|
||||
|
||||
float_latitude = property(_get_float_latitude, _set_float_latitude,
|
||||
doc="latitude as a floating point value")
|
||||
|
||||
def _get_float_longitude(self):
|
||||
return _tuple_to_float(self.longitude)
|
||||
|
||||
def _set_float_longitude(self, value):
|
||||
self.longitude = _float_to_tuple(value)
|
||||
|
||||
float_longitude = property(_get_float_longitude, _set_float_longitude,
|
||||
doc="longitude as a floating point value")
|
||||
@@ -1,23 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.mxbase
|
||||
|
||||
|
||||
class MX(dns.rdtypes.mxbase.MXBase):
|
||||
|
||||
"""MX record"""
|
||||
@@ -1,24 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2006, 2007, 2009-2011 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.txtbase
|
||||
|
||||
|
||||
class NINFO(dns.rdtypes.txtbase.TXTBase):
|
||||
|
||||
"""NINFO record
|
||||
@see: draft-reid-dnsext-zs-01"""
|
||||
@@ -1,23 +0,0 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.nsbase
|
||||
|
||||
|
||||
class NS(dns.rdtypes.nsbase.NSBase):
|
||||
|
||||
"""NS record"""
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user