rimossi cloudscraper, simplejson e torrentool, aggiornato sambatools
This commit is contained in:
@@ -0,0 +1 @@
|
||||
# This file is necessary to make this directory a package.
|
||||
@@ -0,0 +1,706 @@
|
||||
#
|
||||
# This file is part of pyasn1 software.
|
||||
#
|
||||
# Copyright (c) 2005-2020, Ilya Etingof <etingof@gmail.com>
|
||||
# License: http://snmplabs.com/pyasn1/license.html
|
||||
#
|
||||
import sys
|
||||
|
||||
from pyasn1 import error
|
||||
from pyasn1.type import constraint
|
||||
from pyasn1.type import tag
|
||||
from pyasn1.type import tagmap
|
||||
|
||||
__all__ = ['Asn1Item', 'Asn1Type', 'SimpleAsn1Type',
|
||||
'ConstructedAsn1Type']
|
||||
|
||||
|
||||
class Asn1Item(object):
|
||||
@classmethod
|
||||
def getTypeId(cls, increment=1):
|
||||
try:
|
||||
Asn1Item._typeCounter += increment
|
||||
except AttributeError:
|
||||
Asn1Item._typeCounter = increment
|
||||
return Asn1Item._typeCounter
|
||||
|
||||
|
||||
class Asn1Type(Asn1Item):
|
||||
"""Base class for all classes representing ASN.1 types.
|
||||
|
||||
In the user code, |ASN.1| class is normally used only for telling
|
||||
ASN.1 objects from others.
|
||||
|
||||
Note
|
||||
----
|
||||
For as long as ASN.1 is concerned, a way to compare ASN.1 types
|
||||
is to use :meth:`isSameTypeWith` and :meth:`isSuperTypeOf` methods.
|
||||
"""
|
||||
#: Set or return a :py:class:`~pyasn1.type.tag.TagSet` object representing
|
||||
#: ASN.1 tag(s) associated with |ASN.1| type.
|
||||
tagSet = tag.TagSet()
|
||||
|
||||
#: Default :py:class:`~pyasn1.type.constraint.ConstraintsIntersection`
|
||||
#: object imposing constraints on initialization values.
|
||||
subtypeSpec = constraint.ConstraintsIntersection()
|
||||
|
||||
# Disambiguation ASN.1 types identification
|
||||
typeId = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
readOnly = {
|
||||
'tagSet': self.tagSet,
|
||||
'subtypeSpec': self.subtypeSpec
|
||||
}
|
||||
|
||||
readOnly.update(kwargs)
|
||||
|
||||
self.__dict__.update(readOnly)
|
||||
|
||||
self._readOnly = readOnly
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if name[0] != '_' and name in self._readOnly:
|
||||
raise error.PyAsn1Error('read-only instance attribute "%s"' % name)
|
||||
|
||||
self.__dict__[name] = value
|
||||
|
||||
def __str__(self):
|
||||
return self.prettyPrint()
|
||||
|
||||
@property
|
||||
def readOnly(self):
|
||||
return self._readOnly
|
||||
|
||||
@property
|
||||
def effectiveTagSet(self):
|
||||
"""For |ASN.1| type is equivalent to *tagSet*
|
||||
"""
|
||||
return self.tagSet # used by untagged types
|
||||
|
||||
@property
|
||||
def tagMap(self):
|
||||
"""Return a :class:`~pyasn1.type.tagmap.TagMap` object mapping ASN.1 tags to ASN.1 objects within callee object.
|
||||
"""
|
||||
return tagmap.TagMap({self.tagSet: self})
|
||||
|
||||
def isSameTypeWith(self, other, matchTags=True, matchConstraints=True):
|
||||
"""Examine |ASN.1| type for equality with other ASN.1 type.
|
||||
|
||||
ASN.1 tags (:py:mod:`~pyasn1.type.tag`) and constraints
|
||||
(:py:mod:`~pyasn1.type.constraint`) are examined when carrying
|
||||
out ASN.1 types comparison.
|
||||
|
||||
Python class inheritance relationship is NOT considered.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
other: a pyasn1 type object
|
||||
Class instance representing ASN.1 type.
|
||||
|
||||
Returns
|
||||
-------
|
||||
: :class:`bool`
|
||||
:obj:`True` if *other* is |ASN.1| type,
|
||||
:obj:`False` otherwise.
|
||||
"""
|
||||
return (self is other or
|
||||
(not matchTags or self.tagSet == other.tagSet) and
|
||||
(not matchConstraints or self.subtypeSpec == other.subtypeSpec))
|
||||
|
||||
def isSuperTypeOf(self, other, matchTags=True, matchConstraints=True):
|
||||
"""Examine |ASN.1| type for subtype relationship with other ASN.1 type.
|
||||
|
||||
ASN.1 tags (:py:mod:`~pyasn1.type.tag`) and constraints
|
||||
(:py:mod:`~pyasn1.type.constraint`) are examined when carrying
|
||||
out ASN.1 types comparison.
|
||||
|
||||
Python class inheritance relationship is NOT considered.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
other: a pyasn1 type object
|
||||
Class instance representing ASN.1 type.
|
||||
|
||||
Returns
|
||||
-------
|
||||
: :class:`bool`
|
||||
:obj:`True` if *other* is a subtype of |ASN.1| type,
|
||||
:obj:`False` otherwise.
|
||||
"""
|
||||
return (not matchTags or
|
||||
(self.tagSet.isSuperTagSetOf(other.tagSet)) and
|
||||
(not matchConstraints or self.subtypeSpec.isSuperTypeOf(other.subtypeSpec)))
|
||||
|
||||
@staticmethod
|
||||
def isNoValue(*values):
|
||||
for value in values:
|
||||
if value is not noValue:
|
||||
return False
|
||||
return True
|
||||
|
||||
def prettyPrint(self, scope=0):
|
||||
raise NotImplementedError()
|
||||
|
||||
# backward compatibility
|
||||
|
||||
def getTagSet(self):
|
||||
return self.tagSet
|
||||
|
||||
def getEffectiveTagSet(self):
|
||||
return self.effectiveTagSet
|
||||
|
||||
def getTagMap(self):
|
||||
return self.tagMap
|
||||
|
||||
def getSubtypeSpec(self):
|
||||
return self.subtypeSpec
|
||||
|
||||
# backward compatibility
|
||||
def hasValue(self):
|
||||
return self.isValue
|
||||
|
||||
# Backward compatibility
|
||||
Asn1ItemBase = Asn1Type
|
||||
|
||||
|
||||
class NoValue(object):
|
||||
"""Create a singleton instance of NoValue class.
|
||||
|
||||
The *NoValue* sentinel object represents an instance of ASN.1 schema
|
||||
object as opposed to ASN.1 value object.
|
||||
|
||||
Only ASN.1 schema-related operations can be performed on ASN.1
|
||||
schema objects.
|
||||
|
||||
Warning
|
||||
-------
|
||||
Any operation attempted on the *noValue* object will raise the
|
||||
*PyAsn1Error* exception.
|
||||
"""
|
||||
skipMethods = {
|
||||
'__slots__',
|
||||
# attributes
|
||||
'__getattribute__',
|
||||
'__getattr__',
|
||||
'__setattr__',
|
||||
'__delattr__',
|
||||
# class instance
|
||||
'__class__',
|
||||
'__init__',
|
||||
'__del__',
|
||||
'__new__',
|
||||
'__repr__',
|
||||
'__qualname__',
|
||||
'__objclass__',
|
||||
'im_class',
|
||||
'__sizeof__',
|
||||
# pickle protocol
|
||||
'__reduce__',
|
||||
'__reduce_ex__',
|
||||
'__getnewargs__',
|
||||
'__getinitargs__',
|
||||
'__getstate__',
|
||||
'__setstate__',
|
||||
}
|
||||
|
||||
_instance = None
|
||||
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
def getPlug(name):
|
||||
def plug(self, *args, **kw):
|
||||
raise error.PyAsn1Error('Attempted "%s" operation on ASN.1 schema object' % name)
|
||||
return plug
|
||||
|
||||
op_names = [name
|
||||
for typ in (str, int, list, dict)
|
||||
for name in dir(typ)
|
||||
if (name not in cls.skipMethods and
|
||||
name.startswith('__') and
|
||||
name.endswith('__') and
|
||||
callable(getattr(typ, name)))]
|
||||
|
||||
for name in set(op_names):
|
||||
setattr(cls, name, getPlug(name))
|
||||
|
||||
cls._instance = object.__new__(cls)
|
||||
|
||||
return cls._instance
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr in self.skipMethods:
|
||||
raise AttributeError('Attribute %s not present' % attr)
|
||||
|
||||
raise error.PyAsn1Error('Attempted "%s" operation on ASN.1 schema object' % attr)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s object>' % self.__class__.__name__
|
||||
|
||||
|
||||
noValue = NoValue()
|
||||
|
||||
|
||||
class SimpleAsn1Type(Asn1Type):
|
||||
"""Base class for all simple classes representing ASN.1 types.
|
||||
|
||||
ASN.1 distinguishes types by their ability to hold other objects.
|
||||
Scalar types are known as *simple* in ASN.1.
|
||||
|
||||
In the user code, |ASN.1| class is normally used only for telling
|
||||
ASN.1 objects from others.
|
||||
|
||||
Note
|
||||
----
|
||||
For as long as ASN.1 is concerned, a way to compare ASN.1 types
|
||||
is to use :meth:`isSameTypeWith` and :meth:`isSuperTypeOf` methods.
|
||||
"""
|
||||
#: Default payload value
|
||||
defaultValue = noValue
|
||||
|
||||
def __init__(self, value=noValue, **kwargs):
|
||||
Asn1Type.__init__(self, **kwargs)
|
||||
if value is noValue:
|
||||
value = self.defaultValue
|
||||
else:
|
||||
value = self.prettyIn(value)
|
||||
try:
|
||||
self.subtypeSpec(value)
|
||||
|
||||
except error.PyAsn1Error:
|
||||
exType, exValue, exTb = sys.exc_info()
|
||||
raise exType('%s at %s' % (exValue, self.__class__.__name__))
|
||||
|
||||
self._value = value
|
||||
|
||||
def __repr__(self):
|
||||
representation = '%s %s object' % (
|
||||
self.__class__.__name__, self.isValue and 'value' or 'schema')
|
||||
|
||||
for attr, value in self.readOnly.items():
|
||||
if value:
|
||||
representation += ', %s %s' % (attr, value)
|
||||
|
||||
if self.isValue:
|
||||
value = self.prettyPrint()
|
||||
if len(value) > 32:
|
||||
value = value[:16] + '...' + value[-16:]
|
||||
representation += ', payload [%s]' % value
|
||||
|
||||
return '<%s>' % representation
|
||||
|
||||
def __eq__(self, other):
|
||||
return self is other and True or self._value == other
|
||||
|
||||
def __ne__(self, other):
|
||||
return self._value != other
|
||||
|
||||
def __lt__(self, other):
|
||||
return self._value < other
|
||||
|
||||
def __le__(self, other):
|
||||
return self._value <= other
|
||||
|
||||
def __gt__(self, other):
|
||||
return self._value > other
|
||||
|
||||
def __ge__(self, other):
|
||||
return self._value >= other
|
||||
|
||||
if sys.version_info[0] <= 2:
|
||||
def __nonzero__(self):
|
||||
return self._value and True or False
|
||||
else:
|
||||
def __bool__(self):
|
||||
return self._value and True or False
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self._value)
|
||||
|
||||
@property
|
||||
def isValue(self):
|
||||
"""Indicate that |ASN.1| object represents ASN.1 value.
|
||||
|
||||
If *isValue* is :obj:`False` then this object represents just
|
||||
ASN.1 schema.
|
||||
|
||||
If *isValue* is :obj:`True` then, in addition to its ASN.1 schema
|
||||
features, this object can also be used like a Python built-in object
|
||||
(e.g. :class:`int`, :class:`str`, :class:`dict` etc.).
|
||||
|
||||
Returns
|
||||
-------
|
||||
: :class:`bool`
|
||||
:obj:`False` if object represents just ASN.1 schema.
|
||||
:obj:`True` if object represents ASN.1 schema and can be used as a normal value.
|
||||
|
||||
Note
|
||||
----
|
||||
There is an important distinction between PyASN1 schema and value objects.
|
||||
The PyASN1 schema objects can only participate in ASN.1 schema-related
|
||||
operations (e.g. defining or testing the structure of the data). Most
|
||||
obvious uses of ASN.1 schema is to guide serialisation codecs whilst
|
||||
encoding/decoding serialised ASN.1 contents.
|
||||
|
||||
The PyASN1 value objects can **additionally** participate in many operations
|
||||
involving regular Python objects (e.g. arithmetic, comprehension etc).
|
||||
"""
|
||||
return self._value is not noValue
|
||||
|
||||
def clone(self, value=noValue, **kwargs):
|
||||
"""Create a modified version of |ASN.1| schema or value object.
|
||||
|
||||
The `clone()` method accepts the same set arguments as |ASN.1|
|
||||
class takes on instantiation except that all arguments
|
||||
of the `clone()` method are optional.
|
||||
|
||||
Whatever arguments are supplied, they are used to create a copy
|
||||
of `self` taking precedence over the ones used to instantiate `self`.
|
||||
|
||||
Note
|
||||
----
|
||||
Due to the immutable nature of the |ASN.1| object, if no arguments
|
||||
are supplied, no new |ASN.1| object will be created and `self` will
|
||||
be returned instead.
|
||||
"""
|
||||
if value is noValue:
|
||||
if not kwargs:
|
||||
return self
|
||||
|
||||
value = self._value
|
||||
|
||||
initializers = self.readOnly.copy()
|
||||
initializers.update(kwargs)
|
||||
|
||||
return self.__class__(value, **initializers)
|
||||
|
||||
def subtype(self, value=noValue, **kwargs):
|
||||
"""Create a specialization of |ASN.1| schema or value object.
|
||||
|
||||
The subtype relationship between ASN.1 types has no correlation with
|
||||
subtype relationship between Python types. ASN.1 type is mainly identified
|
||||
by its tag(s) (:py:class:`~pyasn1.type.tag.TagSet`) and value range
|
||||
constraints (:py:class:`~pyasn1.type.constraint.ConstraintsIntersection`).
|
||||
These ASN.1 type properties are implemented as |ASN.1| attributes.
|
||||
|
||||
The `subtype()` method accepts the same set arguments as |ASN.1|
|
||||
class takes on instantiation except that all parameters
|
||||
of the `subtype()` method are optional.
|
||||
|
||||
With the exception of the arguments described below, the rest of
|
||||
supplied arguments they are used to create a copy of `self` taking
|
||||
precedence over the ones used to instantiate `self`.
|
||||
|
||||
The following arguments to `subtype()` create a ASN.1 subtype out of
|
||||
|ASN.1| type:
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
implicitTag: :py:class:`~pyasn1.type.tag.Tag`
|
||||
Implicitly apply given ASN.1 tag object to `self`'s
|
||||
:py:class:`~pyasn1.type.tag.TagSet`, then use the result as
|
||||
new object's ASN.1 tag(s).
|
||||
|
||||
explicitTag: :py:class:`~pyasn1.type.tag.Tag`
|
||||
Explicitly apply given ASN.1 tag object to `self`'s
|
||||
:py:class:`~pyasn1.type.tag.TagSet`, then use the result as
|
||||
new object's ASN.1 tag(s).
|
||||
|
||||
subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection`
|
||||
Add ASN.1 constraints object to one of the `self`'s, then
|
||||
use the result as new object's ASN.1 constraints.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:
|
||||
new instance of |ASN.1| schema or value object
|
||||
|
||||
Note
|
||||
----
|
||||
Due to the immutable nature of the |ASN.1| object, if no arguments
|
||||
are supplied, no new |ASN.1| object will be created and `self` will
|
||||
be returned instead.
|
||||
"""
|
||||
if value is noValue:
|
||||
if not kwargs:
|
||||
return self
|
||||
|
||||
value = self._value
|
||||
|
||||
initializers = self.readOnly.copy()
|
||||
|
||||
implicitTag = kwargs.pop('implicitTag', None)
|
||||
if implicitTag is not None:
|
||||
initializers['tagSet'] = self.tagSet.tagImplicitly(implicitTag)
|
||||
|
||||
explicitTag = kwargs.pop('explicitTag', None)
|
||||
if explicitTag is not None:
|
||||
initializers['tagSet'] = self.tagSet.tagExplicitly(explicitTag)
|
||||
|
||||
for arg, option in kwargs.items():
|
||||
initializers[arg] += option
|
||||
|
||||
return self.__class__(value, **initializers)
|
||||
|
||||
def prettyIn(self, value):
|
||||
return value
|
||||
|
||||
def prettyOut(self, value):
|
||||
return str(value)
|
||||
|
||||
def prettyPrint(self, scope=0):
|
||||
return self.prettyOut(self._value)
|
||||
|
||||
def prettyPrintType(self, scope=0):
|
||||
return '%s -> %s' % (self.tagSet, self.__class__.__name__)
|
||||
|
||||
# Backward compatibility
|
||||
AbstractSimpleAsn1Item = SimpleAsn1Type
|
||||
|
||||
#
|
||||
# Constructed types:
|
||||
# * There are five of them: Sequence, SequenceOf/SetOf, Set and Choice
|
||||
# * ASN1 types and values are represened by Python class instances
|
||||
# * Value initialization is made for defaulted components only
|
||||
# * Primary method of component addressing is by-position. Data model for base
|
||||
# type is Python sequence. Additional type-specific addressing methods
|
||||
# may be implemented for particular types.
|
||||
# * SequenceOf and SetOf types do not implement any additional methods
|
||||
# * Sequence, Set and Choice types also implement by-identifier addressing
|
||||
# * Sequence, Set and Choice types also implement by-asn1-type (tag) addressing
|
||||
# * Sequence and Set types may include optional and defaulted
|
||||
# components
|
||||
# * Constructed types hold a reference to component types used for value
|
||||
# verification and ordering.
|
||||
# * Component type is a scalar type for SequenceOf/SetOf types and a list
|
||||
# of types for Sequence/Set/Choice.
|
||||
#
|
||||
|
||||
|
||||
class ConstructedAsn1Type(Asn1Type):
|
||||
"""Base class for all constructed classes representing ASN.1 types.
|
||||
|
||||
ASN.1 distinguishes types by their ability to hold other objects.
|
||||
Those "nesting" types are known as *constructed* in ASN.1.
|
||||
|
||||
In the user code, |ASN.1| class is normally used only for telling
|
||||
ASN.1 objects from others.
|
||||
|
||||
Note
|
||||
----
|
||||
For as long as ASN.1 is concerned, a way to compare ASN.1 types
|
||||
is to use :meth:`isSameTypeWith` and :meth:`isSuperTypeOf` methods.
|
||||
"""
|
||||
|
||||
#: If :obj:`True`, requires exact component type matching,
|
||||
#: otherwise subtype relation is only enforced
|
||||
strictConstraints = False
|
||||
|
||||
componentType = None
|
||||
|
||||
# backward compatibility, unused
|
||||
sizeSpec = constraint.ConstraintsIntersection()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
readOnly = {
|
||||
'componentType': self.componentType,
|
||||
# backward compatibility, unused
|
||||
'sizeSpec': self.sizeSpec
|
||||
}
|
||||
|
||||
# backward compatibility: preserve legacy sizeSpec support
|
||||
kwargs = self._moveSizeSpec(**kwargs)
|
||||
|
||||
readOnly.update(kwargs)
|
||||
|
||||
Asn1Type.__init__(self, **readOnly)
|
||||
|
||||
def _moveSizeSpec(self, **kwargs):
|
||||
# backward compatibility, unused
|
||||
sizeSpec = kwargs.pop('sizeSpec', self.sizeSpec)
|
||||
if sizeSpec:
|
||||
subtypeSpec = kwargs.pop('subtypeSpec', self.subtypeSpec)
|
||||
if subtypeSpec:
|
||||
subtypeSpec = sizeSpec
|
||||
|
||||
else:
|
||||
subtypeSpec += sizeSpec
|
||||
|
||||
kwargs['subtypeSpec'] = subtypeSpec
|
||||
|
||||
return kwargs
|
||||
|
||||
def __repr__(self):
|
||||
representation = '%s %s object' % (
|
||||
self.__class__.__name__, self.isValue and 'value' or 'schema'
|
||||
)
|
||||
|
||||
for attr, value in self.readOnly.items():
|
||||
if value is not noValue:
|
||||
representation += ', %s=%r' % (attr, value)
|
||||
|
||||
if self.isValue and self.components:
|
||||
representation += ', payload [%s]' % ', '.join(
|
||||
[repr(x) for x in self.components])
|
||||
|
||||
return '<%s>' % representation
|
||||
|
||||
def __eq__(self, other):
|
||||
return self is other or self.components == other
|
||||
|
||||
def __ne__(self, other):
|
||||
return self.components != other
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.components < other
|
||||
|
||||
def __le__(self, other):
|
||||
return self.components <= other
|
||||
|
||||
def __gt__(self, other):
|
||||
return self.components > other
|
||||
|
||||
def __ge__(self, other):
|
||||
return self.components >= other
|
||||
|
||||
if sys.version_info[0] <= 2:
|
||||
def __nonzero__(self):
|
||||
return bool(self.components)
|
||||
else:
|
||||
def __bool__(self):
|
||||
return bool(self.components)
|
||||
|
||||
@property
|
||||
def components(self):
|
||||
raise error.PyAsn1Error('Method not implemented')
|
||||
|
||||
def _cloneComponentValues(self, myClone, cloneValueFlag):
|
||||
pass
|
||||
|
||||
def clone(self, **kwargs):
|
||||
"""Create a modified version of |ASN.1| schema object.
|
||||
|
||||
The `clone()` method accepts the same set arguments as |ASN.1|
|
||||
class takes on instantiation except that all arguments
|
||||
of the `clone()` method are optional.
|
||||
|
||||
Whatever arguments are supplied, they are used to create a copy
|
||||
of `self` taking precedence over the ones used to instantiate `self`.
|
||||
|
||||
Possible values of `self` are never copied over thus `clone()` can
|
||||
only create a new schema object.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:
|
||||
new instance of |ASN.1| type/value
|
||||
|
||||
Note
|
||||
----
|
||||
Due to the mutable nature of the |ASN.1| object, even if no arguments
|
||||
are supplied, a new |ASN.1| object will be created and returned.
|
||||
"""
|
||||
cloneValueFlag = kwargs.pop('cloneValueFlag', False)
|
||||
|
||||
initializers = self.readOnly.copy()
|
||||
initializers.update(kwargs)
|
||||
|
||||
clone = self.__class__(**initializers)
|
||||
|
||||
if cloneValueFlag:
|
||||
self._cloneComponentValues(clone, cloneValueFlag)
|
||||
|
||||
return clone
|
||||
|
||||
def subtype(self, **kwargs):
|
||||
"""Create a specialization of |ASN.1| schema object.
|
||||
|
||||
The `subtype()` method accepts the same set arguments as |ASN.1|
|
||||
class takes on instantiation except that all parameters
|
||||
of the `subtype()` method are optional.
|
||||
|
||||
With the exception of the arguments described below, the rest of
|
||||
supplied arguments they are used to create a copy of `self` taking
|
||||
precedence over the ones used to instantiate `self`.
|
||||
|
||||
The following arguments to `subtype()` create a ASN.1 subtype out of
|
||||
|ASN.1| type.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
implicitTag: :py:class:`~pyasn1.type.tag.Tag`
|
||||
Implicitly apply given ASN.1 tag object to `self`'s
|
||||
:py:class:`~pyasn1.type.tag.TagSet`, then use the result as
|
||||
new object's ASN.1 tag(s).
|
||||
|
||||
explicitTag: :py:class:`~pyasn1.type.tag.Tag`
|
||||
Explicitly apply given ASN.1 tag object to `self`'s
|
||||
:py:class:`~pyasn1.type.tag.TagSet`, then use the result as
|
||||
new object's ASN.1 tag(s).
|
||||
|
||||
subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection`
|
||||
Add ASN.1 constraints object to one of the `self`'s, then
|
||||
use the result as new object's ASN.1 constraints.
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
:
|
||||
new instance of |ASN.1| type/value
|
||||
|
||||
Note
|
||||
----
|
||||
Due to the mutable nature of the |ASN.1| object, even if no arguments
|
||||
are supplied, a new |ASN.1| object will be created and returned.
|
||||
"""
|
||||
|
||||
initializers = self.readOnly.copy()
|
||||
|
||||
cloneValueFlag = kwargs.pop('cloneValueFlag', False)
|
||||
|
||||
implicitTag = kwargs.pop('implicitTag', None)
|
||||
if implicitTag is not None:
|
||||
initializers['tagSet'] = self.tagSet.tagImplicitly(implicitTag)
|
||||
|
||||
explicitTag = kwargs.pop('explicitTag', None)
|
||||
if explicitTag is not None:
|
||||
initializers['tagSet'] = self.tagSet.tagExplicitly(explicitTag)
|
||||
|
||||
for arg, option in kwargs.items():
|
||||
initializers[arg] += option
|
||||
|
||||
clone = self.__class__(**initializers)
|
||||
|
||||
if cloneValueFlag:
|
||||
self._cloneComponentValues(clone, cloneValueFlag)
|
||||
|
||||
return clone
|
||||
|
||||
def getComponentByPosition(self, idx):
|
||||
raise error.PyAsn1Error('Method not implemented')
|
||||
|
||||
def setComponentByPosition(self, idx, value, verifyConstraints=True):
|
||||
raise error.PyAsn1Error('Method not implemented')
|
||||
|
||||
def setComponents(self, *args, **kwargs):
|
||||
for idx, value in enumerate(args):
|
||||
self[idx] = value
|
||||
for k in kwargs:
|
||||
self[k] = kwargs[k]
|
||||
return self
|
||||
|
||||
# backward compatibility
|
||||
|
||||
def setDefaultComponents(self):
|
||||
pass
|
||||
|
||||
def getComponentType(self):
|
||||
return self.componentType
|
||||
|
||||
# backward compatibility, unused
|
||||
def verifySizeSpec(self):
|
||||
self.subtypeSpec(self)
|
||||
|
||||
|
||||
# Backward compatibility
|
||||
AbstractConstructedAsn1Item = ConstructedAsn1Type
|
||||
@@ -0,0 +1,335 @@
|
||||
#
|
||||
# This file is part of pyasn1 software.
|
||||
#
|
||||
# Copyright (c) 2005-2020, Ilya Etingof <etingof@gmail.com>
|
||||
# License: http://snmplabs.com/pyasn1/license.html
|
||||
#
|
||||
import sys
|
||||
|
||||
from pyasn1 import error
|
||||
from pyasn1.type import tag
|
||||
from pyasn1.type import univ
|
||||
|
||||
__all__ = ['NumericString', 'PrintableString', 'TeletexString', 'T61String', 'VideotexString',
|
||||
'IA5String', 'GraphicString', 'VisibleString', 'ISO646String',
|
||||
'GeneralString', 'UniversalString', 'BMPString', 'UTF8String']
|
||||
|
||||
NoValue = univ.NoValue
|
||||
noValue = univ.noValue
|
||||
|
||||
|
||||
class AbstractCharacterString(univ.OctetString):
|
||||
"""Creates |ASN.1| schema or value object.
|
||||
|
||||
|ASN.1| class is based on :class:`~pyasn1.type.base.SimpleAsn1Type`,
|
||||
its objects are immutable and duck-type Python 2 :class:`str` or Python 3
|
||||
:class:`bytes`. When used in octet-stream context, |ASN.1| type assumes
|
||||
"|encoding|" encoding.
|
||||
|
||||
Keyword Args
|
||||
------------
|
||||
value: :class:`unicode`, :class:`str`, :class:`bytes` or |ASN.1| object
|
||||
:class:`unicode` object (Python 2) or :class:`str` (Python 3),
|
||||
alternatively :class:`str` (Python 2) or :class:`bytes` (Python 3)
|
||||
representing octet-stream of serialised unicode string
|
||||
(note `encoding` parameter) or |ASN.1| class instance.
|
||||
If `value` is not given, schema object will be created.
|
||||
|
||||
tagSet: :py:class:`~pyasn1.type.tag.TagSet`
|
||||
Object representing non-default ASN.1 tag(s)
|
||||
|
||||
subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection`
|
||||
Object representing non-default ASN.1 subtype constraint(s). Constraints
|
||||
verification for |ASN.1| type occurs automatically on object
|
||||
instantiation.
|
||||
|
||||
encoding: :py:class:`str`
|
||||
Unicode codec ID to encode/decode :class:`unicode` (Python 2) or
|
||||
:class:`str` (Python 3) the payload when |ASN.1| object is used
|
||||
in octet-stream context.
|
||||
|
||||
Raises
|
||||
------
|
||||
~pyasn1.error.ValueConstraintError, ~pyasn1.error.PyAsn1Error
|
||||
On constraint violation or bad initializer.
|
||||
"""
|
||||
|
||||
if sys.version_info[0] <= 2:
|
||||
def __str__(self):
|
||||
try:
|
||||
# `str` is Py2 text representation
|
||||
return self._value.encode(self.encoding)
|
||||
|
||||
except UnicodeEncodeError:
|
||||
exc = sys.exc_info()[1]
|
||||
raise error.PyAsn1UnicodeEncodeError(
|
||||
"Can't encode string '%s' with codec "
|
||||
"%s" % (self._value, self.encoding), exc
|
||||
)
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(self._value)
|
||||
|
||||
def prettyIn(self, value):
|
||||
try:
|
||||
if isinstance(value, unicode):
|
||||
return value
|
||||
elif isinstance(value, str):
|
||||
return value.decode(self.encoding)
|
||||
elif isinstance(value, (tuple, list)):
|
||||
return self.prettyIn(''.join([chr(x) for x in value]))
|
||||
elif isinstance(value, univ.OctetString):
|
||||
return value.asOctets().decode(self.encoding)
|
||||
else:
|
||||
return unicode(value)
|
||||
|
||||
except (UnicodeDecodeError, LookupError):
|
||||
exc = sys.exc_info()[1]
|
||||
raise error.PyAsn1UnicodeDecodeError(
|
||||
"Can't decode string '%s' with codec "
|
||||
"%s" % (value, self.encoding), exc
|
||||
)
|
||||
|
||||
def asOctets(self, padding=True):
|
||||
return str(self)
|
||||
|
||||
def asNumbers(self, padding=True):
|
||||
return tuple([ord(x) for x in str(self)])
|
||||
|
||||
else:
|
||||
def __str__(self):
|
||||
# `unicode` is Py3 text representation
|
||||
return str(self._value)
|
||||
|
||||
def __bytes__(self):
|
||||
try:
|
||||
return self._value.encode(self.encoding)
|
||||
except UnicodeEncodeError:
|
||||
exc = sys.exc_info()[1]
|
||||
raise error.PyAsn1UnicodeEncodeError(
|
||||
"Can't encode string '%s' with codec "
|
||||
"%s" % (self._value, self.encoding), exc
|
||||
)
|
||||
|
||||
def prettyIn(self, value):
|
||||
try:
|
||||
if isinstance(value, str):
|
||||
return value
|
||||
elif isinstance(value, bytes):
|
||||
return value.decode(self.encoding)
|
||||
elif isinstance(value, (tuple, list)):
|
||||
return self.prettyIn(bytes(value))
|
||||
elif isinstance(value, univ.OctetString):
|
||||
return value.asOctets().decode(self.encoding)
|
||||
else:
|
||||
return str(value)
|
||||
|
||||
except (UnicodeDecodeError, LookupError):
|
||||
exc = sys.exc_info()[1]
|
||||
raise error.PyAsn1UnicodeDecodeError(
|
||||
"Can't decode string '%s' with codec "
|
||||
"%s" % (value, self.encoding), exc
|
||||
)
|
||||
|
||||
def asOctets(self, padding=True):
|
||||
return bytes(self)
|
||||
|
||||
def asNumbers(self, padding=True):
|
||||
return tuple(bytes(self))
|
||||
|
||||
#
|
||||
# See OctetString.prettyPrint() for the explanation
|
||||
#
|
||||
|
||||
def prettyOut(self, value):
|
||||
return value
|
||||
|
||||
def prettyPrint(self, scope=0):
|
||||
# first see if subclass has its own .prettyOut()
|
||||
value = self.prettyOut(self._value)
|
||||
|
||||
if value is not self._value:
|
||||
return value
|
||||
|
||||
return AbstractCharacterString.__str__(self)
|
||||
|
||||
def __reversed__(self):
|
||||
return reversed(self._value)
|
||||
|
||||
|
||||
class NumericString(AbstractCharacterString):
|
||||
__doc__ = AbstractCharacterString.__doc__
|
||||
|
||||
#: Set (on class, not on instance) or return a
|
||||
#: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
|
||||
#: associated with |ASN.1| type.
|
||||
tagSet = AbstractCharacterString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 18)
|
||||
)
|
||||
encoding = 'us-ascii'
|
||||
|
||||
# Optimization for faster codec lookup
|
||||
typeId = AbstractCharacterString.getTypeId()
|
||||
|
||||
|
||||
class PrintableString(AbstractCharacterString):
|
||||
__doc__ = AbstractCharacterString.__doc__
|
||||
|
||||
#: Set (on class, not on instance) or return a
|
||||
#: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
|
||||
#: associated with |ASN.1| type.
|
||||
tagSet = AbstractCharacterString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 19)
|
||||
)
|
||||
encoding = 'us-ascii'
|
||||
|
||||
# Optimization for faster codec lookup
|
||||
typeId = AbstractCharacterString.getTypeId()
|
||||
|
||||
|
||||
class TeletexString(AbstractCharacterString):
|
||||
__doc__ = AbstractCharacterString.__doc__
|
||||
|
||||
#: Set (on class, not on instance) or return a
|
||||
#: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
|
||||
#: associated with |ASN.1| type.
|
||||
tagSet = AbstractCharacterString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 20)
|
||||
)
|
||||
encoding = 'iso-8859-1'
|
||||
|
||||
# Optimization for faster codec lookup
|
||||
typeId = AbstractCharacterString.getTypeId()
|
||||
|
||||
|
||||
class T61String(TeletexString):
|
||||
__doc__ = TeletexString.__doc__
|
||||
|
||||
# Optimization for faster codec lookup
|
||||
typeId = AbstractCharacterString.getTypeId()
|
||||
|
||||
|
||||
class VideotexString(AbstractCharacterString):
|
||||
__doc__ = AbstractCharacterString.__doc__
|
||||
|
||||
#: Set (on class, not on instance) or return a
|
||||
#: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
|
||||
#: associated with |ASN.1| type.
|
||||
tagSet = AbstractCharacterString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 21)
|
||||
)
|
||||
encoding = 'iso-8859-1'
|
||||
|
||||
# Optimization for faster codec lookup
|
||||
typeId = AbstractCharacterString.getTypeId()
|
||||
|
||||
|
||||
class IA5String(AbstractCharacterString):
|
||||
__doc__ = AbstractCharacterString.__doc__
|
||||
|
||||
#: Set (on class, not on instance) or return a
|
||||
#: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
|
||||
#: associated with |ASN.1| type.
|
||||
tagSet = AbstractCharacterString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 22)
|
||||
)
|
||||
encoding = 'us-ascii'
|
||||
|
||||
# Optimization for faster codec lookup
|
||||
typeId = AbstractCharacterString.getTypeId()
|
||||
|
||||
|
||||
class GraphicString(AbstractCharacterString):
|
||||
__doc__ = AbstractCharacterString.__doc__
|
||||
|
||||
#: Set (on class, not on instance) or return a
|
||||
#: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
|
||||
#: associated with |ASN.1| type.
|
||||
tagSet = AbstractCharacterString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 25)
|
||||
)
|
||||
encoding = 'iso-8859-1'
|
||||
|
||||
# Optimization for faster codec lookup
|
||||
typeId = AbstractCharacterString.getTypeId()
|
||||
|
||||
|
||||
class VisibleString(AbstractCharacterString):
|
||||
__doc__ = AbstractCharacterString.__doc__
|
||||
|
||||
#: Set (on class, not on instance) or return a
|
||||
#: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
|
||||
#: associated with |ASN.1| type.
|
||||
tagSet = AbstractCharacterString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 26)
|
||||
)
|
||||
encoding = 'us-ascii'
|
||||
|
||||
# Optimization for faster codec lookup
|
||||
typeId = AbstractCharacterString.getTypeId()
|
||||
|
||||
|
||||
class ISO646String(VisibleString):
|
||||
__doc__ = VisibleString.__doc__
|
||||
|
||||
# Optimization for faster codec lookup
|
||||
typeId = AbstractCharacterString.getTypeId()
|
||||
|
||||
class GeneralString(AbstractCharacterString):
|
||||
__doc__ = AbstractCharacterString.__doc__
|
||||
|
||||
#: Set (on class, not on instance) or return a
|
||||
#: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
|
||||
#: associated with |ASN.1| type.
|
||||
tagSet = AbstractCharacterString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 27)
|
||||
)
|
||||
encoding = 'iso-8859-1'
|
||||
|
||||
# Optimization for faster codec lookup
|
||||
typeId = AbstractCharacterString.getTypeId()
|
||||
|
||||
|
||||
class UniversalString(AbstractCharacterString):
|
||||
__doc__ = AbstractCharacterString.__doc__
|
||||
|
||||
#: Set (on class, not on instance) or return a
|
||||
#: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
|
||||
#: associated with |ASN.1| type.
|
||||
tagSet = AbstractCharacterString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 28)
|
||||
)
|
||||
encoding = "utf-32-be"
|
||||
|
||||
# Optimization for faster codec lookup
|
||||
typeId = AbstractCharacterString.getTypeId()
|
||||
|
||||
|
||||
class BMPString(AbstractCharacterString):
|
||||
__doc__ = AbstractCharacterString.__doc__
|
||||
|
||||
#: Set (on class, not on instance) or return a
|
||||
#: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
|
||||
#: associated with |ASN.1| type.
|
||||
tagSet = AbstractCharacterString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 30)
|
||||
)
|
||||
encoding = "utf-16-be"
|
||||
|
||||
# Optimization for faster codec lookup
|
||||
typeId = AbstractCharacterString.getTypeId()
|
||||
|
||||
|
||||
class UTF8String(AbstractCharacterString):
|
||||
__doc__ = AbstractCharacterString.__doc__
|
||||
|
||||
#: Set (on class, not on instance) or return a
|
||||
#: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
|
||||
#: associated with |ASN.1| type.
|
||||
tagSet = AbstractCharacterString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 12)
|
||||
)
|
||||
encoding = "utf-8"
|
||||
|
||||
# Optimization for faster codec lookup
|
||||
typeId = AbstractCharacterString.getTypeId()
|
||||
@@ -0,0 +1,756 @@
|
||||
#
|
||||
# This file is part of pyasn1 software.
|
||||
#
|
||||
# Copyright (c) 2005-2020, Ilya Etingof <etingof@gmail.com>
|
||||
# License: http://snmplabs.com/pyasn1/license.html
|
||||
#
|
||||
# Original concept and code by Mike C. Fletcher.
|
||||
#
|
||||
import sys
|
||||
|
||||
from pyasn1.type import error
|
||||
|
||||
__all__ = ['SingleValueConstraint', 'ContainedSubtypeConstraint',
|
||||
'ValueRangeConstraint', 'ValueSizeConstraint',
|
||||
'PermittedAlphabetConstraint', 'InnerTypeConstraint',
|
||||
'ConstraintsExclusion', 'ConstraintsIntersection',
|
||||
'ConstraintsUnion']
|
||||
|
||||
|
||||
class AbstractConstraint(object):
|
||||
|
||||
def __init__(self, *values):
|
||||
self._valueMap = set()
|
||||
self._setValues(values)
|
||||
self.__hash = hash((self.__class__.__name__, self._values))
|
||||
|
||||
def __call__(self, value, idx=None):
|
||||
if not self._values:
|
||||
return
|
||||
|
||||
try:
|
||||
self._testValue(value, idx)
|
||||
|
||||
except error.ValueConstraintError:
|
||||
raise error.ValueConstraintError(
|
||||
'%s failed at: %r' % (self, sys.exc_info()[1])
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
representation = '%s object' % (self.__class__.__name__)
|
||||
|
||||
if self._values:
|
||||
representation += ', consts %s' % ', '.join(
|
||||
[repr(x) for x in self._values])
|
||||
|
||||
return '<%s>' % representation
|
||||
|
||||
def __eq__(self, other):
|
||||
return self is other and True or self._values == other
|
||||
|
||||
def __ne__(self, other):
|
||||
return self._values != other
|
||||
|
||||
def __lt__(self, other):
|
||||
return self._values < other
|
||||
|
||||
def __le__(self, other):
|
||||
return self._values <= other
|
||||
|
||||
def __gt__(self, other):
|
||||
return self._values > other
|
||||
|
||||
def __ge__(self, other):
|
||||
return self._values >= other
|
||||
|
||||
if sys.version_info[0] <= 2:
|
||||
def __nonzero__(self):
|
||||
return self._values and True or False
|
||||
else:
|
||||
def __bool__(self):
|
||||
return self._values and True or False
|
||||
|
||||
def __hash__(self):
|
||||
return self.__hash
|
||||
|
||||
def _setValues(self, values):
|
||||
self._values = values
|
||||
|
||||
def _testValue(self, value, idx):
|
||||
raise error.ValueConstraintError(value)
|
||||
|
||||
# Constraints derivation logic
|
||||
def getValueMap(self):
|
||||
return self._valueMap
|
||||
|
||||
def isSuperTypeOf(self, otherConstraint):
|
||||
# TODO: fix possible comparison of set vs scalars here
|
||||
return (otherConstraint is self or
|
||||
not self._values or
|
||||
otherConstraint == self or
|
||||
self in otherConstraint.getValueMap())
|
||||
|
||||
def isSubTypeOf(self, otherConstraint):
|
||||
return (otherConstraint is self or
|
||||
not self or
|
||||
otherConstraint == self or
|
||||
otherConstraint in self._valueMap)
|
||||
|
||||
|
||||
class SingleValueConstraint(AbstractConstraint):
|
||||
"""Create a SingleValueConstraint object.
|
||||
|
||||
The SingleValueConstraint satisfies any value that
|
||||
is present in the set of permitted values.
|
||||
|
||||
Objects of this type are iterable (emitting constraint values) and
|
||||
can act as operands for some arithmetic operations e.g. addition
|
||||
and subtraction. The latter can be used for combining multiple
|
||||
SingleValueConstraint objects into one.
|
||||
|
||||
The SingleValueConstraint object can be applied to
|
||||
any ASN.1 type.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
*values: :class:`int`
|
||||
Full set of values permitted by this constraint object.
|
||||
|
||||
Examples
|
||||
--------
|
||||
.. code-block:: python
|
||||
|
||||
class DivisorOfSix(Integer):
|
||||
'''
|
||||
ASN.1 specification:
|
||||
|
||||
Divisor-Of-6 ::= INTEGER (1 | 2 | 3 | 6)
|
||||
'''
|
||||
subtypeSpec = SingleValueConstraint(1, 2, 3, 6)
|
||||
|
||||
# this will succeed
|
||||
divisor_of_six = DivisorOfSix(1)
|
||||
|
||||
# this will raise ValueConstraintError
|
||||
divisor_of_six = DivisorOfSix(7)
|
||||
"""
|
||||
def _setValues(self, values):
|
||||
self._values = values
|
||||
self._set = set(values)
|
||||
|
||||
def _testValue(self, value, idx):
|
||||
if value not in self._set:
|
||||
raise error.ValueConstraintError(value)
|
||||
|
||||
# Constrains can be merged or reduced
|
||||
|
||||
def __contains__(self, item):
|
||||
return item in self._set
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._set)
|
||||
|
||||
def __sub__(self, constraint):
|
||||
return self.__class__(*(self._set.difference(constraint)))
|
||||
|
||||
def __add__(self, constraint):
|
||||
return self.__class__(*(self._set.union(constraint)))
|
||||
|
||||
def __sub__(self, constraint):
|
||||
return self.__class__(*(self._set.difference(constraint)))
|
||||
|
||||
|
||||
class ContainedSubtypeConstraint(AbstractConstraint):
|
||||
"""Create a ContainedSubtypeConstraint object.
|
||||
|
||||
The ContainedSubtypeConstraint satisfies any value that
|
||||
is present in the set of permitted values and also
|
||||
satisfies included constraints.
|
||||
|
||||
The ContainedSubtypeConstraint object can be applied to
|
||||
any ASN.1 type.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
*values:
|
||||
Full set of values and constraint objects permitted
|
||||
by this constraint object.
|
||||
|
||||
Examples
|
||||
--------
|
||||
.. code-block:: python
|
||||
|
||||
class DivisorOfEighteen(Integer):
|
||||
'''
|
||||
ASN.1 specification:
|
||||
|
||||
Divisors-of-18 ::= INTEGER (INCLUDES Divisors-of-6 | 9 | 18)
|
||||
'''
|
||||
subtypeSpec = ContainedSubtypeConstraint(
|
||||
SingleValueConstraint(1, 2, 3, 6), 9, 18
|
||||
)
|
||||
|
||||
# this will succeed
|
||||
divisor_of_eighteen = DivisorOfEighteen(9)
|
||||
|
||||
# this will raise ValueConstraintError
|
||||
divisor_of_eighteen = DivisorOfEighteen(10)
|
||||
"""
|
||||
def _testValue(self, value, idx):
|
||||
for constraint in self._values:
|
||||
if isinstance(constraint, AbstractConstraint):
|
||||
constraint(value, idx)
|
||||
elif value not in self._set:
|
||||
raise error.ValueConstraintError(value)
|
||||
|
||||
|
||||
class ValueRangeConstraint(AbstractConstraint):
|
||||
"""Create a ValueRangeConstraint object.
|
||||
|
||||
The ValueRangeConstraint satisfies any value that
|
||||
falls in the range of permitted values.
|
||||
|
||||
The ValueRangeConstraint object can only be applied
|
||||
to :class:`~pyasn1.type.univ.Integer` and
|
||||
:class:`~pyasn1.type.univ.Real` types.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
start: :class:`int`
|
||||
Minimum permitted value in the range (inclusive)
|
||||
|
||||
end: :class:`int`
|
||||
Maximum permitted value in the range (inclusive)
|
||||
|
||||
Examples
|
||||
--------
|
||||
.. code-block:: python
|
||||
|
||||
class TeenAgeYears(Integer):
|
||||
'''
|
||||
ASN.1 specification:
|
||||
|
||||
TeenAgeYears ::= INTEGER (13 .. 19)
|
||||
'''
|
||||
subtypeSpec = ValueRangeConstraint(13, 19)
|
||||
|
||||
# this will succeed
|
||||
teen_year = TeenAgeYears(18)
|
||||
|
||||
# this will raise ValueConstraintError
|
||||
teen_year = TeenAgeYears(20)
|
||||
"""
|
||||
def _testValue(self, value, idx):
|
||||
if value < self.start or value > self.stop:
|
||||
raise error.ValueConstraintError(value)
|
||||
|
||||
def _setValues(self, values):
|
||||
if len(values) != 2:
|
||||
raise error.PyAsn1Error(
|
||||
'%s: bad constraint values' % (self.__class__.__name__,)
|
||||
)
|
||||
self.start, self.stop = values
|
||||
if self.start > self.stop:
|
||||
raise error.PyAsn1Error(
|
||||
'%s: screwed constraint values (start > stop): %s > %s' % (
|
||||
self.__class__.__name__,
|
||||
self.start, self.stop
|
||||
)
|
||||
)
|
||||
AbstractConstraint._setValues(self, values)
|
||||
|
||||
|
||||
class ValueSizeConstraint(ValueRangeConstraint):
|
||||
"""Create a ValueSizeConstraint object.
|
||||
|
||||
The ValueSizeConstraint satisfies any value for
|
||||
as long as its size falls within the range of
|
||||
permitted sizes.
|
||||
|
||||
The ValueSizeConstraint object can be applied
|
||||
to :class:`~pyasn1.type.univ.BitString`,
|
||||
:class:`~pyasn1.type.univ.OctetString` (including
|
||||
all :ref:`character ASN.1 types <type.char>`),
|
||||
:class:`~pyasn1.type.univ.SequenceOf`
|
||||
and :class:`~pyasn1.type.univ.SetOf` types.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
minimum: :class:`int`
|
||||
Minimum permitted size of the value (inclusive)
|
||||
|
||||
maximum: :class:`int`
|
||||
Maximum permitted size of the value (inclusive)
|
||||
|
||||
Examples
|
||||
--------
|
||||
.. code-block:: python
|
||||
|
||||
class BaseballTeamRoster(SetOf):
|
||||
'''
|
||||
ASN.1 specification:
|
||||
|
||||
BaseballTeamRoster ::= SET SIZE (1..25) OF PlayerNames
|
||||
'''
|
||||
componentType = PlayerNames()
|
||||
subtypeSpec = ValueSizeConstraint(1, 25)
|
||||
|
||||
# this will succeed
|
||||
team = BaseballTeamRoster()
|
||||
team.extend(['Jan', 'Matej'])
|
||||
encode(team)
|
||||
|
||||
# this will raise ValueConstraintError
|
||||
team = BaseballTeamRoster()
|
||||
team.extend(['Jan'] * 26)
|
||||
encode(team)
|
||||
|
||||
Note
|
||||
----
|
||||
Whenever ValueSizeConstraint is applied to mutable types
|
||||
(e.g. :class:`~pyasn1.type.univ.SequenceOf`,
|
||||
:class:`~pyasn1.type.univ.SetOf`), constraint
|
||||
validation only happens at the serialisation phase rather
|
||||
than schema instantiation phase (as it is with immutable
|
||||
types).
|
||||
"""
|
||||
def _testValue(self, value, idx):
|
||||
valueSize = len(value)
|
||||
if valueSize < self.start or valueSize > self.stop:
|
||||
raise error.ValueConstraintError(value)
|
||||
|
||||
|
||||
class PermittedAlphabetConstraint(SingleValueConstraint):
|
||||
"""Create a PermittedAlphabetConstraint object.
|
||||
|
||||
The PermittedAlphabetConstraint satisfies any character
|
||||
string for as long as all its characters are present in
|
||||
the set of permitted characters.
|
||||
|
||||
Objects of this type are iterable (emitting constraint values) and
|
||||
can act as operands for some arithmetic operations e.g. addition
|
||||
and subtraction.
|
||||
|
||||
The PermittedAlphabetConstraint object can only be applied
|
||||
to the :ref:`character ASN.1 types <type.char>` such as
|
||||
:class:`~pyasn1.type.char.IA5String`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
*alphabet: :class:`str`
|
||||
Full set of characters permitted by this constraint object.
|
||||
|
||||
Example
|
||||
-------
|
||||
.. code-block:: python
|
||||
|
||||
class BooleanValue(IA5String):
|
||||
'''
|
||||
ASN.1 specification:
|
||||
|
||||
BooleanValue ::= IA5String (FROM ('T' | 'F'))
|
||||
'''
|
||||
subtypeSpec = PermittedAlphabetConstraint('T', 'F')
|
||||
|
||||
# this will succeed
|
||||
truth = BooleanValue('T')
|
||||
truth = BooleanValue('TF')
|
||||
|
||||
# this will raise ValueConstraintError
|
||||
garbage = BooleanValue('TAF')
|
||||
|
||||
ASN.1 `FROM ... EXCEPT ...` clause can be modelled by combining multiple
|
||||
PermittedAlphabetConstraint objects into one:
|
||||
|
||||
Example
|
||||
-------
|
||||
.. code-block:: python
|
||||
|
||||
class Lipogramme(IA5String):
|
||||
'''
|
||||
ASN.1 specification:
|
||||
|
||||
Lipogramme ::=
|
||||
IA5String (FROM (ALL EXCEPT ("e"|"E")))
|
||||
'''
|
||||
subtypeSpec = (
|
||||
PermittedAlphabetConstraint(*string.printable) -
|
||||
PermittedAlphabetConstraint('e', 'E')
|
||||
)
|
||||
|
||||
# this will succeed
|
||||
lipogramme = Lipogramme('A work of fiction?')
|
||||
|
||||
# this will raise ValueConstraintError
|
||||
lipogramme = Lipogramme('Eel')
|
||||
|
||||
Note
|
||||
----
|
||||
Although `ConstraintsExclusion` object could seemingly be used for this
|
||||
purpose, practically, for it to work, it needs to represent its operand
|
||||
constraints as sets and intersect one with the other. That would require
|
||||
the insight into the constraint values (and their types) that are otherwise
|
||||
hidden inside the constraint object.
|
||||
|
||||
Therefore it's more practical to model `EXCEPT` clause at
|
||||
`PermittedAlphabetConstraint` level instead.
|
||||
"""
|
||||
def _setValues(self, values):
|
||||
self._values = values
|
||||
self._set = set(values)
|
||||
|
||||
def _testValue(self, value, idx):
|
||||
if not self._set.issuperset(value):
|
||||
raise error.ValueConstraintError(value)
|
||||
|
||||
|
||||
class ComponentPresentConstraint(AbstractConstraint):
|
||||
"""Create a ComponentPresentConstraint object.
|
||||
|
||||
The ComponentPresentConstraint is only satisfied when the value
|
||||
is not `None`.
|
||||
|
||||
The ComponentPresentConstraint object is typically used with
|
||||
`WithComponentsConstraint`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
.. code-block:: python
|
||||
|
||||
present = ComponentPresentConstraint()
|
||||
|
||||
# this will succeed
|
||||
present('whatever')
|
||||
|
||||
# this will raise ValueConstraintError
|
||||
present(None)
|
||||
"""
|
||||
def _setValues(self, values):
|
||||
self._values = ('<must be present>',)
|
||||
|
||||
if values:
|
||||
raise error.PyAsn1Error('No arguments expected')
|
||||
|
||||
def _testValue(self, value, idx):
|
||||
if value is None:
|
||||
raise error.ValueConstraintError(
|
||||
'Component is not present:')
|
||||
|
||||
|
||||
class ComponentAbsentConstraint(AbstractConstraint):
|
||||
"""Create a ComponentAbsentConstraint object.
|
||||
|
||||
The ComponentAbsentConstraint is only satisfied when the value
|
||||
is `None`.
|
||||
|
||||
The ComponentAbsentConstraint object is typically used with
|
||||
`WithComponentsConstraint`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
.. code-block:: python
|
||||
|
||||
absent = ComponentAbsentConstraint()
|
||||
|
||||
# this will succeed
|
||||
absent(None)
|
||||
|
||||
# this will raise ValueConstraintError
|
||||
absent('whatever')
|
||||
"""
|
||||
def _setValues(self, values):
|
||||
self._values = ('<must be absent>',)
|
||||
|
||||
if values:
|
||||
raise error.PyAsn1Error('No arguments expected')
|
||||
|
||||
def _testValue(self, value, idx):
|
||||
if value is not None:
|
||||
raise error.ValueConstraintError(
|
||||
'Component is not absent: %r' % value)
|
||||
|
||||
|
||||
class WithComponentsConstraint(AbstractConstraint):
|
||||
"""Create a WithComponentsConstraint object.
|
||||
|
||||
The `WithComponentsConstraint` satisfies any mapping object that has
|
||||
constrained fields present or absent, what is indicated by
|
||||
`ComponentPresentConstraint` and `ComponentAbsentConstraint`
|
||||
objects respectively.
|
||||
|
||||
The `WithComponentsConstraint` object is typically applied
|
||||
to :class:`~pyasn1.type.univ.Set` or
|
||||
:class:`~pyasn1.type.univ.Sequence` types.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
*fields: :class:`tuple`
|
||||
Zero or more tuples of (`field`, `constraint`) indicating constrained
|
||||
fields.
|
||||
|
||||
Notes
|
||||
-----
|
||||
On top of the primary use of `WithComponentsConstraint` (ensuring presence
|
||||
or absence of particular components of a :class:`~pyasn1.type.univ.Set` or
|
||||
:class:`~pyasn1.type.univ.Sequence`), it is also possible to pass any other
|
||||
constraint objects or their combinations. In case of scalar fields, these
|
||||
constraints will be verified in addition to the constraints belonging to
|
||||
scalar components themselves. However, formally, these additional
|
||||
constraints do not change the type of these ASN.1 objects.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Item(Sequence): # Set is similar
|
||||
'''
|
||||
ASN.1 specification:
|
||||
|
||||
Item ::= SEQUENCE {
|
||||
id INTEGER OPTIONAL,
|
||||
name OCTET STRING OPTIONAL
|
||||
} WITH COMPONENTS id PRESENT, name ABSENT | id ABSENT, name PRESENT
|
||||
'''
|
||||
componentType = NamedTypes(
|
||||
OptionalNamedType('id', Integer()),
|
||||
OptionalNamedType('name', OctetString())
|
||||
)
|
||||
withComponents = ConstraintsUnion(
|
||||
WithComponentsConstraint(
|
||||
('id', ComponentPresentConstraint()),
|
||||
('name', ComponentAbsentConstraint())
|
||||
),
|
||||
WithComponentsConstraint(
|
||||
('id', ComponentAbsentConstraint()),
|
||||
('name', ComponentPresentConstraint())
|
||||
)
|
||||
)
|
||||
|
||||
item = Item()
|
||||
|
||||
# This will succeed
|
||||
item['id'] = 1
|
||||
|
||||
# This will succeed
|
||||
item.reset()
|
||||
item['name'] = 'John'
|
||||
|
||||
# This will fail (on encoding)
|
||||
item.reset()
|
||||
descr['id'] = 1
|
||||
descr['name'] = 'John'
|
||||
"""
|
||||
def _testValue(self, value, idx):
|
||||
for field, constraint in self._values:
|
||||
constraint(value.get(field))
|
||||
|
||||
def _setValues(self, values):
|
||||
AbstractConstraint._setValues(self, values)
|
||||
|
||||
|
||||
# This is a bit kludgy, meaning two op modes within a single constraint
|
||||
class InnerTypeConstraint(AbstractConstraint):
|
||||
"""Value must satisfy the type and presence constraints"""
|
||||
|
||||
def _testValue(self, value, idx):
|
||||
if self.__singleTypeConstraint:
|
||||
self.__singleTypeConstraint(value)
|
||||
elif self.__multipleTypeConstraint:
|
||||
if idx not in self.__multipleTypeConstraint:
|
||||
raise error.ValueConstraintError(value)
|
||||
constraint, status = self.__multipleTypeConstraint[idx]
|
||||
if status == 'ABSENT': # XXX presence is not checked!
|
||||
raise error.ValueConstraintError(value)
|
||||
constraint(value)
|
||||
|
||||
def _setValues(self, values):
|
||||
self.__multipleTypeConstraint = {}
|
||||
self.__singleTypeConstraint = None
|
||||
for v in values:
|
||||
if isinstance(v, tuple):
|
||||
self.__multipleTypeConstraint[v[0]] = v[1], v[2]
|
||||
else:
|
||||
self.__singleTypeConstraint = v
|
||||
AbstractConstraint._setValues(self, values)
|
||||
|
||||
|
||||
# Logic operations on constraints
|
||||
|
||||
class ConstraintsExclusion(AbstractConstraint):
|
||||
"""Create a ConstraintsExclusion logic operator object.
|
||||
|
||||
The ConstraintsExclusion logic operator succeeds when the
|
||||
value does *not* satisfy the operand constraint.
|
||||
|
||||
The ConstraintsExclusion object can be applied to
|
||||
any constraint and logic operator object.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
*constraints:
|
||||
Constraint or logic operator objects.
|
||||
|
||||
Examples
|
||||
--------
|
||||
.. code-block:: python
|
||||
|
||||
class LuckyNumber(Integer):
|
||||
subtypeSpec = ConstraintsExclusion(
|
||||
SingleValueConstraint(13)
|
||||
)
|
||||
|
||||
# this will succeed
|
||||
luckyNumber = LuckyNumber(12)
|
||||
|
||||
# this will raise ValueConstraintError
|
||||
luckyNumber = LuckyNumber(13)
|
||||
|
||||
Note
|
||||
----
|
||||
The `FROM ... EXCEPT ...` ASN.1 clause should be modeled by combining
|
||||
constraint objects into one. See `PermittedAlphabetConstraint` for more
|
||||
information.
|
||||
"""
|
||||
def _testValue(self, value, idx):
|
||||
for constraint in self._values:
|
||||
try:
|
||||
constraint(value, idx)
|
||||
|
||||
except error.ValueConstraintError:
|
||||
continue
|
||||
|
||||
raise error.ValueConstraintError(value)
|
||||
|
||||
def _setValues(self, values):
|
||||
AbstractConstraint._setValues(self, values)
|
||||
|
||||
|
||||
class AbstractConstraintSet(AbstractConstraint):
|
||||
|
||||
def __getitem__(self, idx):
|
||||
return self._values[idx]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._values)
|
||||
|
||||
def __add__(self, value):
|
||||
return self.__class__(*(self._values + (value,)))
|
||||
|
||||
def __radd__(self, value):
|
||||
return self.__class__(*((value,) + self._values))
|
||||
|
||||
def __len__(self):
|
||||
return len(self._values)
|
||||
|
||||
# Constraints inclusion in sets
|
||||
|
||||
def _setValues(self, values):
|
||||
self._values = values
|
||||
for constraint in values:
|
||||
if constraint:
|
||||
self._valueMap.add(constraint)
|
||||
self._valueMap.update(constraint.getValueMap())
|
||||
|
||||
|
||||
class ConstraintsIntersection(AbstractConstraintSet):
|
||||
"""Create a ConstraintsIntersection logic operator object.
|
||||
|
||||
The ConstraintsIntersection logic operator only succeeds
|
||||
if *all* its operands succeed.
|
||||
|
||||
The ConstraintsIntersection object can be applied to
|
||||
any constraint and logic operator objects.
|
||||
|
||||
The ConstraintsIntersection object duck-types the immutable
|
||||
container object like Python :py:class:`tuple`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
*constraints:
|
||||
Constraint or logic operator objects.
|
||||
|
||||
Examples
|
||||
--------
|
||||
.. code-block:: python
|
||||
|
||||
class CapitalAndSmall(IA5String):
|
||||
'''
|
||||
ASN.1 specification:
|
||||
|
||||
CapitalAndSmall ::=
|
||||
IA5String (FROM ("A".."Z"|"a".."z"))
|
||||
'''
|
||||
subtypeSpec = ConstraintsIntersection(
|
||||
PermittedAlphabetConstraint('A', 'Z'),
|
||||
PermittedAlphabetConstraint('a', 'z')
|
||||
)
|
||||
|
||||
# this will succeed
|
||||
capital_and_small = CapitalAndSmall('Hello')
|
||||
|
||||
# this will raise ValueConstraintError
|
||||
capital_and_small = CapitalAndSmall('hello')
|
||||
"""
|
||||
def _testValue(self, value, idx):
|
||||
for constraint in self._values:
|
||||
constraint(value, idx)
|
||||
|
||||
|
||||
class ConstraintsUnion(AbstractConstraintSet):
|
||||
"""Create a ConstraintsUnion logic operator object.
|
||||
|
||||
The ConstraintsUnion logic operator succeeds if
|
||||
*at least* a single operand succeeds.
|
||||
|
||||
The ConstraintsUnion object can be applied to
|
||||
any constraint and logic operator objects.
|
||||
|
||||
The ConstraintsUnion object duck-types the immutable
|
||||
container object like Python :py:class:`tuple`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
*constraints:
|
||||
Constraint or logic operator objects.
|
||||
|
||||
Examples
|
||||
--------
|
||||
.. code-block:: python
|
||||
|
||||
class CapitalOrSmall(IA5String):
|
||||
'''
|
||||
ASN.1 specification:
|
||||
|
||||
CapitalOrSmall ::=
|
||||
IA5String (FROM ("A".."Z") | FROM ("a".."z"))
|
||||
'''
|
||||
subtypeSpec = ConstraintsUnion(
|
||||
PermittedAlphabetConstraint('A', 'Z'),
|
||||
PermittedAlphabetConstraint('a', 'z')
|
||||
)
|
||||
|
||||
# this will succeed
|
||||
capital_or_small = CapitalAndSmall('Hello')
|
||||
|
||||
# this will raise ValueConstraintError
|
||||
capital_or_small = CapitalOrSmall('hello!')
|
||||
"""
|
||||
def _testValue(self, value, idx):
|
||||
for constraint in self._values:
|
||||
try:
|
||||
constraint(value, idx)
|
||||
except error.ValueConstraintError:
|
||||
pass
|
||||
else:
|
||||
return
|
||||
|
||||
raise error.ValueConstraintError(
|
||||
'all of %s failed for "%s"' % (self._values, value)
|
||||
)
|
||||
|
||||
# TODO:
|
||||
# refactor InnerTypeConstraint
|
||||
# add tests for type check
|
||||
# implement other constraint types
|
||||
# make constraint validation easy to skip
|
||||
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# This file is part of pyasn1 software.
|
||||
#
|
||||
# Copyright (c) 2005-2020, Ilya Etingof <etingof@gmail.com>
|
||||
# License: http://snmplabs.com/pyasn1/license.html
|
||||
#
|
||||
from pyasn1.error import PyAsn1Error
|
||||
|
||||
|
||||
class ValueConstraintError(PyAsn1Error):
|
||||
pass
|
||||
@@ -0,0 +1,561 @@
|
||||
#
|
||||
# This file is part of pyasn1 software.
|
||||
#
|
||||
# Copyright (c) 2005-2020, Ilya Etingof <etingof@gmail.com>
|
||||
# License: http://snmplabs.com/pyasn1/license.html
|
||||
#
|
||||
import sys
|
||||
|
||||
from pyasn1 import error
|
||||
from pyasn1.type import tag
|
||||
from pyasn1.type import tagmap
|
||||
|
||||
__all__ = ['NamedType', 'OptionalNamedType', 'DefaultedNamedType',
|
||||
'NamedTypes']
|
||||
|
||||
try:
|
||||
any
|
||||
|
||||
except NameError:
|
||||
any = lambda x: bool(filter(bool, x))
|
||||
|
||||
|
||||
class NamedType(object):
|
||||
"""Create named field object for a constructed ASN.1 type.
|
||||
|
||||
The |NamedType| object represents a single name and ASN.1 type of a constructed ASN.1 type.
|
||||
|
||||
|NamedType| objects are immutable and duck-type Python :class:`tuple` objects
|
||||
holding *name* and *asn1Object* components.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name: :py:class:`str`
|
||||
Field name
|
||||
|
||||
asn1Object:
|
||||
ASN.1 type object
|
||||
"""
|
||||
isOptional = False
|
||||
isDefaulted = False
|
||||
|
||||
def __init__(self, name, asn1Object, openType=None):
|
||||
self.__name = name
|
||||
self.__type = asn1Object
|
||||
self.__nameAndType = name, asn1Object
|
||||
self.__openType = openType
|
||||
|
||||
def __repr__(self):
|
||||
representation = '%s=%r' % (self.name, self.asn1Object)
|
||||
|
||||
if self.openType:
|
||||
representation += ', open type %r' % self.openType
|
||||
|
||||
return '<%s object, type %s>' % (
|
||||
self.__class__.__name__, representation)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.__nameAndType == other
|
||||
|
||||
def __ne__(self, other):
|
||||
return self.__nameAndType != other
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.__nameAndType < other
|
||||
|
||||
def __le__(self, other):
|
||||
return self.__nameAndType <= other
|
||||
|
||||
def __gt__(self, other):
|
||||
return self.__nameAndType > other
|
||||
|
||||
def __ge__(self, other):
|
||||
return self.__nameAndType >= other
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.__nameAndType)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
return self.__nameAndType[idx]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.__nameAndType)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.__name
|
||||
|
||||
@property
|
||||
def asn1Object(self):
|
||||
return self.__type
|
||||
|
||||
@property
|
||||
def openType(self):
|
||||
return self.__openType
|
||||
|
||||
# Backward compatibility
|
||||
|
||||
def getName(self):
|
||||
return self.name
|
||||
|
||||
def getType(self):
|
||||
return self.asn1Object
|
||||
|
||||
|
||||
class OptionalNamedType(NamedType):
|
||||
__doc__ = NamedType.__doc__
|
||||
|
||||
isOptional = True
|
||||
|
||||
|
||||
class DefaultedNamedType(NamedType):
|
||||
__doc__ = NamedType.__doc__
|
||||
|
||||
isDefaulted = True
|
||||
|
||||
|
||||
class NamedTypes(object):
|
||||
"""Create a collection of named fields for a constructed ASN.1 type.
|
||||
|
||||
The NamedTypes object represents a collection of named fields of a constructed ASN.1 type.
|
||||
|
||||
*NamedTypes* objects are immutable and duck-type Python :class:`dict` objects
|
||||
holding *name* as keys and ASN.1 type object as values.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
*namedTypes: :class:`~pyasn1.type.namedtype.NamedType`
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Description(Sequence):
|
||||
'''
|
||||
ASN.1 specification:
|
||||
|
||||
Description ::= SEQUENCE {
|
||||
surname IA5String,
|
||||
first-name IA5String OPTIONAL,
|
||||
age INTEGER DEFAULT 40
|
||||
}
|
||||
'''
|
||||
componentType = NamedTypes(
|
||||
NamedType('surname', IA5String()),
|
||||
OptionalNamedType('first-name', IA5String()),
|
||||
DefaultedNamedType('age', Integer(40))
|
||||
)
|
||||
|
||||
descr = Description()
|
||||
descr['surname'] = 'Smith'
|
||||
descr['first-name'] = 'John'
|
||||
"""
|
||||
def __init__(self, *namedTypes, **kwargs):
|
||||
self.__namedTypes = namedTypes
|
||||
self.__namedTypesLen = len(self.__namedTypes)
|
||||
self.__minTagSet = self.__computeMinTagSet()
|
||||
self.__nameToPosMap = self.__computeNameToPosMap()
|
||||
self.__tagToPosMap = self.__computeTagToPosMap()
|
||||
self.__ambiguousTypes = 'terminal' not in kwargs and self.__computeAmbiguousTypes() or {}
|
||||
self.__uniqueTagMap = self.__computeTagMaps(unique=True)
|
||||
self.__nonUniqueTagMap = self.__computeTagMaps(unique=False)
|
||||
self.__hasOptionalOrDefault = any([True for namedType in self.__namedTypes
|
||||
if namedType.isDefaulted or namedType.isOptional])
|
||||
self.__hasOpenTypes = any([True for namedType in self.__namedTypes
|
||||
if namedType.openType])
|
||||
|
||||
self.__requiredComponents = frozenset(
|
||||
[idx for idx, nt in enumerate(self.__namedTypes) if not nt.isOptional and not nt.isDefaulted]
|
||||
)
|
||||
self.__keys = frozenset([namedType.name for namedType in self.__namedTypes])
|
||||
self.__values = tuple([namedType.asn1Object for namedType in self.__namedTypes])
|
||||
self.__items = tuple([(namedType.name, namedType.asn1Object) for namedType in self.__namedTypes])
|
||||
|
||||
def __repr__(self):
|
||||
representation = ', '.join(['%r' % x for x in self.__namedTypes])
|
||||
return '<%s object, types %s>' % (
|
||||
self.__class__.__name__, representation)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.__namedTypes == other
|
||||
|
||||
def __ne__(self, other):
|
||||
return self.__namedTypes != other
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.__namedTypes < other
|
||||
|
||||
def __le__(self, other):
|
||||
return self.__namedTypes <= other
|
||||
|
||||
def __gt__(self, other):
|
||||
return self.__namedTypes > other
|
||||
|
||||
def __ge__(self, other):
|
||||
return self.__namedTypes >= other
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.__namedTypes)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
try:
|
||||
return self.__namedTypes[idx]
|
||||
|
||||
except TypeError:
|
||||
return self.__namedTypes[self.__nameToPosMap[idx]]
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self.__nameToPosMap
|
||||
|
||||
def __iter__(self):
|
||||
return (x[0] for x in self.__namedTypes)
|
||||
|
||||
if sys.version_info[0] <= 2:
|
||||
def __nonzero__(self):
|
||||
return self.__namedTypesLen > 0
|
||||
else:
|
||||
def __bool__(self):
|
||||
return self.__namedTypesLen > 0
|
||||
|
||||
def __len__(self):
|
||||
return self.__namedTypesLen
|
||||
|
||||
# Python dict protocol
|
||||
|
||||
def values(self):
|
||||
return self.__values
|
||||
|
||||
def keys(self):
|
||||
return self.__keys
|
||||
|
||||
def items(self):
|
||||
return self.__items
|
||||
|
||||
def clone(self):
|
||||
return self.__class__(*self.__namedTypes)
|
||||
|
||||
class PostponedError(object):
|
||||
def __init__(self, errorMsg):
|
||||
self.__errorMsg = errorMsg
|
||||
|
||||
def __getitem__(self, item):
|
||||
raise error.PyAsn1Error(self.__errorMsg)
|
||||
|
||||
def __computeTagToPosMap(self):
|
||||
tagToPosMap = {}
|
||||
for idx, namedType in enumerate(self.__namedTypes):
|
||||
tagMap = namedType.asn1Object.tagMap
|
||||
if isinstance(tagMap, NamedTypes.PostponedError):
|
||||
return tagMap
|
||||
if not tagMap:
|
||||
continue
|
||||
for _tagSet in tagMap.presentTypes:
|
||||
if _tagSet in tagToPosMap:
|
||||
return NamedTypes.PostponedError('Duplicate component tag %s at %s' % (_tagSet, namedType))
|
||||
tagToPosMap[_tagSet] = idx
|
||||
|
||||
return tagToPosMap
|
||||
|
||||
def __computeNameToPosMap(self):
|
||||
nameToPosMap = {}
|
||||
for idx, namedType in enumerate(self.__namedTypes):
|
||||
if namedType.name in nameToPosMap:
|
||||
return NamedTypes.PostponedError('Duplicate component name %s at %s' % (namedType.name, namedType))
|
||||
nameToPosMap[namedType.name] = idx
|
||||
|
||||
return nameToPosMap
|
||||
|
||||
def __computeAmbiguousTypes(self):
|
||||
ambiguousTypes = {}
|
||||
partialAmbiguousTypes = ()
|
||||
for idx, namedType in reversed(tuple(enumerate(self.__namedTypes))):
|
||||
if namedType.isOptional or namedType.isDefaulted:
|
||||
partialAmbiguousTypes = (namedType,) + partialAmbiguousTypes
|
||||
else:
|
||||
partialAmbiguousTypes = (namedType,)
|
||||
if len(partialAmbiguousTypes) == len(self.__namedTypes):
|
||||
ambiguousTypes[idx] = self
|
||||
else:
|
||||
ambiguousTypes[idx] = NamedTypes(*partialAmbiguousTypes, **dict(terminal=True))
|
||||
return ambiguousTypes
|
||||
|
||||
def getTypeByPosition(self, idx):
|
||||
"""Return ASN.1 type object by its position in fields set.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
idx: :py:class:`int`
|
||||
Field index
|
||||
|
||||
Returns
|
||||
-------
|
||||
:
|
||||
ASN.1 type
|
||||
|
||||
Raises
|
||||
------
|
||||
~pyasn1.error.PyAsn1Error
|
||||
If given position is out of fields range
|
||||
"""
|
||||
try:
|
||||
return self.__namedTypes[idx].asn1Object
|
||||
|
||||
except IndexError:
|
||||
raise error.PyAsn1Error('Type position out of range')
|
||||
|
||||
def getPositionByType(self, tagSet):
|
||||
"""Return field position by its ASN.1 type.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tagSet: :class:`~pysnmp.type.tag.TagSet`
|
||||
ASN.1 tag set distinguishing one ASN.1 type from others.
|
||||
|
||||
Returns
|
||||
-------
|
||||
: :py:class:`int`
|
||||
ASN.1 type position in fields set
|
||||
|
||||
Raises
|
||||
------
|
||||
~pyasn1.error.PyAsn1Error
|
||||
If *tagSet* is not present or ASN.1 types are not unique within callee *NamedTypes*
|
||||
"""
|
||||
try:
|
||||
return self.__tagToPosMap[tagSet]
|
||||
|
||||
except KeyError:
|
||||
raise error.PyAsn1Error('Type %s not found' % (tagSet,))
|
||||
|
||||
def getNameByPosition(self, idx):
|
||||
"""Return field name by its position in fields set.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
idx: :py:class:`idx`
|
||||
Field index
|
||||
|
||||
Returns
|
||||
-------
|
||||
: :py:class:`str`
|
||||
Field name
|
||||
|
||||
Raises
|
||||
------
|
||||
~pyasn1.error.PyAsn1Error
|
||||
If given field name is not present in callee *NamedTypes*
|
||||
"""
|
||||
try:
|
||||
return self.__namedTypes[idx].name
|
||||
|
||||
except IndexError:
|
||||
raise error.PyAsn1Error('Type position out of range')
|
||||
|
||||
def getPositionByName(self, name):
|
||||
"""Return field position by filed name.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name: :py:class:`str`
|
||||
Field name
|
||||
|
||||
Returns
|
||||
-------
|
||||
: :py:class:`int`
|
||||
Field position in fields set
|
||||
|
||||
Raises
|
||||
------
|
||||
~pyasn1.error.PyAsn1Error
|
||||
If *name* is not present or not unique within callee *NamedTypes*
|
||||
"""
|
||||
try:
|
||||
return self.__nameToPosMap[name]
|
||||
|
||||
except KeyError:
|
||||
raise error.PyAsn1Error('Name %s not found' % (name,))
|
||||
|
||||
def getTagMapNearPosition(self, idx):
|
||||
"""Return ASN.1 types that are allowed at or past given field position.
|
||||
|
||||
Some ASN.1 serialisation allow for skipping optional and defaulted fields.
|
||||
Some constructed ASN.1 types allow reordering of the fields. When recovering
|
||||
such objects it may be important to know which types can possibly be
|
||||
present at any given position in the field sets.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
idx: :py:class:`int`
|
||||
Field index
|
||||
|
||||
Returns
|
||||
-------
|
||||
: :class:`~pyasn1.type.tagmap.TagMap`
|
||||
Map if ASN.1 types allowed at given field position
|
||||
|
||||
Raises
|
||||
------
|
||||
~pyasn1.error.PyAsn1Error
|
||||
If given position is out of fields range
|
||||
"""
|
||||
try:
|
||||
return self.__ambiguousTypes[idx].tagMap
|
||||
|
||||
except KeyError:
|
||||
raise error.PyAsn1Error('Type position out of range')
|
||||
|
||||
def getPositionNearType(self, tagSet, idx):
|
||||
"""Return the closest field position where given ASN.1 type is allowed.
|
||||
|
||||
Some ASN.1 serialisation allow for skipping optional and defaulted fields.
|
||||
Some constructed ASN.1 types allow reordering of the fields. When recovering
|
||||
such objects it may be important to know at which field position, in field set,
|
||||
given *tagSet* is allowed at or past *idx* position.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tagSet: :class:`~pyasn1.type.tag.TagSet`
|
||||
ASN.1 type which field position to look up
|
||||
|
||||
idx: :py:class:`int`
|
||||
Field position at or past which to perform ASN.1 type look up
|
||||
|
||||
Returns
|
||||
-------
|
||||
: :py:class:`int`
|
||||
Field position in fields set
|
||||
|
||||
Raises
|
||||
------
|
||||
~pyasn1.error.PyAsn1Error
|
||||
If *tagSet* is not present or not unique within callee *NamedTypes*
|
||||
or *idx* is out of fields range
|
||||
"""
|
||||
try:
|
||||
return idx + self.__ambiguousTypes[idx].getPositionByType(tagSet)
|
||||
|
||||
except KeyError:
|
||||
raise error.PyAsn1Error('Type position out of range')
|
||||
|
||||
def __computeMinTagSet(self):
|
||||
minTagSet = None
|
||||
for namedType in self.__namedTypes:
|
||||
asn1Object = namedType.asn1Object
|
||||
|
||||
try:
|
||||
tagSet = asn1Object.minTagSet
|
||||
|
||||
except AttributeError:
|
||||
tagSet = asn1Object.tagSet
|
||||
|
||||
if minTagSet is None or tagSet < minTagSet:
|
||||
minTagSet = tagSet
|
||||
|
||||
return minTagSet or tag.TagSet()
|
||||
|
||||
@property
|
||||
def minTagSet(self):
|
||||
"""Return the minimal TagSet among ASN.1 type in callee *NamedTypes*.
|
||||
|
||||
Some ASN.1 types/serialisation protocols require ASN.1 types to be
|
||||
arranged based on their numerical tag value. The *minTagSet* property
|
||||
returns that.
|
||||
|
||||
Returns
|
||||
-------
|
||||
: :class:`~pyasn1.type.tagset.TagSet`
|
||||
Minimal TagSet among ASN.1 types in callee *NamedTypes*
|
||||
"""
|
||||
return self.__minTagSet
|
||||
|
||||
def __computeTagMaps(self, unique):
|
||||
presentTypes = {}
|
||||
skipTypes = {}
|
||||
defaultType = None
|
||||
for namedType in self.__namedTypes:
|
||||
tagMap = namedType.asn1Object.tagMap
|
||||
if isinstance(tagMap, NamedTypes.PostponedError):
|
||||
return tagMap
|
||||
for tagSet in tagMap:
|
||||
if unique and tagSet in presentTypes:
|
||||
return NamedTypes.PostponedError('Non-unique tagSet %s of %s at %s' % (tagSet, namedType, self))
|
||||
presentTypes[tagSet] = namedType.asn1Object
|
||||
skipTypes.update(tagMap.skipTypes)
|
||||
|
||||
if defaultType is None:
|
||||
defaultType = tagMap.defaultType
|
||||
elif tagMap.defaultType is not None:
|
||||
return NamedTypes.PostponedError('Duplicate default ASN.1 type at %s' % (self,))
|
||||
|
||||
return tagmap.TagMap(presentTypes, skipTypes, defaultType)
|
||||
|
||||
@property
|
||||
def tagMap(self):
|
||||
"""Return a *TagMap* object from tags and types recursively.
|
||||
|
||||
Return a :class:`~pyasn1.type.tagmap.TagMap` object by
|
||||
combining tags from *TagMap* objects of children types and
|
||||
associating them with their immediate child type.
|
||||
|
||||
Example
|
||||
-------
|
||||
.. code-block:: python
|
||||
|
||||
OuterType ::= CHOICE {
|
||||
innerType INTEGER
|
||||
}
|
||||
|
||||
Calling *.tagMap* on *OuterType* will yield a map like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
Integer.tagSet -> Choice
|
||||
"""
|
||||
return self.__nonUniqueTagMap
|
||||
|
||||
@property
|
||||
def tagMapUnique(self):
|
||||
"""Return a *TagMap* object from unique tags and types recursively.
|
||||
|
||||
Return a :class:`~pyasn1.type.tagmap.TagMap` object by
|
||||
combining tags from *TagMap* objects of children types and
|
||||
associating them with their immediate child type.
|
||||
|
||||
Example
|
||||
-------
|
||||
.. code-block:: python
|
||||
|
||||
OuterType ::= CHOICE {
|
||||
innerType INTEGER
|
||||
}
|
||||
|
||||
Calling *.tagMapUnique* on *OuterType* will yield a map like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
Integer.tagSet -> Choice
|
||||
|
||||
Note
|
||||
----
|
||||
|
||||
Duplicate *TagSet* objects found in the tree of children
|
||||
types would cause error.
|
||||
"""
|
||||
return self.__uniqueTagMap
|
||||
|
||||
@property
|
||||
def hasOptionalOrDefault(self):
|
||||
return self.__hasOptionalOrDefault
|
||||
|
||||
@property
|
||||
def hasOpenTypes(self):
|
||||
return self.__hasOpenTypes
|
||||
|
||||
@property
|
||||
def namedTypes(self):
|
||||
return tuple(self.__namedTypes)
|
||||
|
||||
@property
|
||||
def requiredComponents(self):
|
||||
return self.__requiredComponents
|
||||
@@ -0,0 +1,192 @@
|
||||
#
|
||||
# This file is part of pyasn1 software.
|
||||
#
|
||||
# Copyright (c) 2005-2020, Ilya Etingof <etingof@gmail.com>
|
||||
# License: http://snmplabs.com/pyasn1/license.html
|
||||
#
|
||||
# ASN.1 named integers
|
||||
#
|
||||
from pyasn1 import error
|
||||
|
||||
__all__ = ['NamedValues']
|
||||
|
||||
|
||||
class NamedValues(object):
|
||||
"""Create named values object.
|
||||
|
||||
The |NamedValues| object represents a collection of string names
|
||||
associated with numeric IDs. These objects are used for giving
|
||||
names to otherwise numerical values.
|
||||
|
||||
|NamedValues| objects are immutable and duck-type Python
|
||||
:class:`dict` object mapping ID to name and vice-versa.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
*args: variable number of two-element :py:class:`tuple`
|
||||
|
||||
name: :py:class:`str`
|
||||
Value label
|
||||
|
||||
value: :py:class:`int`
|
||||
Numeric value
|
||||
|
||||
Keyword Args
|
||||
------------
|
||||
name: :py:class:`str`
|
||||
Value label
|
||||
|
||||
value: :py:class:`int`
|
||||
Numeric value
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> nv = NamedValues('a', 'b', ('c', 0), d=1)
|
||||
>>> nv
|
||||
>>> {'c': 0, 'd': 1, 'a': 2, 'b': 3}
|
||||
>>> nv[0]
|
||||
'c'
|
||||
>>> nv['a']
|
||||
2
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.__names = {}
|
||||
self.__numbers = {}
|
||||
|
||||
anonymousNames = []
|
||||
|
||||
for namedValue in args:
|
||||
if isinstance(namedValue, (tuple, list)):
|
||||
try:
|
||||
name, number = namedValue
|
||||
|
||||
except ValueError:
|
||||
raise error.PyAsn1Error('Not a proper attribute-value pair %r' % (namedValue,))
|
||||
|
||||
else:
|
||||
anonymousNames.append(namedValue)
|
||||
continue
|
||||
|
||||
if name in self.__names:
|
||||
raise error.PyAsn1Error('Duplicate name %s' % (name,))
|
||||
|
||||
if number in self.__numbers:
|
||||
raise error.PyAsn1Error('Duplicate number %s=%s' % (name, number))
|
||||
|
||||
self.__names[name] = number
|
||||
self.__numbers[number] = name
|
||||
|
||||
for name, number in kwargs.items():
|
||||
if name in self.__names:
|
||||
raise error.PyAsn1Error('Duplicate name %s' % (name,))
|
||||
|
||||
if number in self.__numbers:
|
||||
raise error.PyAsn1Error('Duplicate number %s=%s' % (name, number))
|
||||
|
||||
self.__names[name] = number
|
||||
self.__numbers[number] = name
|
||||
|
||||
if anonymousNames:
|
||||
|
||||
number = self.__numbers and max(self.__numbers) + 1 or 0
|
||||
|
||||
for name in anonymousNames:
|
||||
|
||||
if name in self.__names:
|
||||
raise error.PyAsn1Error('Duplicate name %s' % (name,))
|
||||
|
||||
self.__names[name] = number
|
||||
self.__numbers[number] = name
|
||||
|
||||
number += 1
|
||||
|
||||
def __repr__(self):
|
||||
representation = ', '.join(['%s=%d' % x for x in self.items()])
|
||||
|
||||
if len(representation) > 64:
|
||||
representation = representation[:32] + '...' + representation[-32:]
|
||||
|
||||
return '<%s object, enums %s>' % (
|
||||
self.__class__.__name__, representation)
|
||||
|
||||
def __eq__(self, other):
|
||||
return dict(self) == other
|
||||
|
||||
def __ne__(self, other):
|
||||
return dict(self) != other
|
||||
|
||||
def __lt__(self, other):
|
||||
return dict(self) < other
|
||||
|
||||
def __le__(self, other):
|
||||
return dict(self) <= other
|
||||
|
||||
def __gt__(self, other):
|
||||
return dict(self) > other
|
||||
|
||||
def __ge__(self, other):
|
||||
return dict(self) >= other
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.items())
|
||||
|
||||
# Python dict protocol (read-only)
|
||||
|
||||
def __getitem__(self, key):
|
||||
try:
|
||||
return self.__numbers[key]
|
||||
|
||||
except KeyError:
|
||||
return self.__names[key]
|
||||
|
||||
def __len__(self):
|
||||
return len(self.__names)
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self.__names or key in self.__numbers
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.__names)
|
||||
|
||||
def values(self):
|
||||
return iter(self.__numbers)
|
||||
|
||||
def keys(self):
|
||||
return iter(self.__names)
|
||||
|
||||
def items(self):
|
||||
for name in self.__names:
|
||||
yield name, self.__names[name]
|
||||
|
||||
# support merging
|
||||
|
||||
def __add__(self, namedValues):
|
||||
return self.__class__(*tuple(self.items()) + tuple(namedValues.items()))
|
||||
|
||||
# XXX clone/subtype?
|
||||
|
||||
def clone(self, *args, **kwargs):
|
||||
new = self.__class__(*args, **kwargs)
|
||||
return self + new
|
||||
|
||||
# legacy protocol
|
||||
|
||||
def getName(self, value):
|
||||
if value in self.__numbers:
|
||||
return self.__numbers[value]
|
||||
|
||||
def getValue(self, name):
|
||||
if name in self.__names:
|
||||
return self.__names[name]
|
||||
|
||||
def getValues(self, *names):
|
||||
try:
|
||||
return [self.__names[name] for name in names]
|
||||
|
||||
except KeyError:
|
||||
raise error.PyAsn1Error(
|
||||
'Unknown bit identifier(s): %s' % (set(names).difference(self.__names),)
|
||||
)
|
||||
@@ -0,0 +1,104 @@
|
||||
#
|
||||
# This file is part of pyasn1 software.
|
||||
#
|
||||
# Copyright (c) 2005-2020, Ilya Etingof <etingof@gmail.com>
|
||||
# License: http://snmplabs.com/pyasn1/license.html
|
||||
#
|
||||
|
||||
__all__ = ['OpenType']
|
||||
|
||||
|
||||
class OpenType(object):
|
||||
"""Create ASN.1 type map indexed by a value
|
||||
|
||||
The *OpenType* object models an untyped field of a constructed ASN.1
|
||||
type. In ASN.1 syntax it is usually represented by the
|
||||
`ANY DEFINED BY` for scalars or `SET OF ANY DEFINED BY`,
|
||||
`SEQUENCE OF ANY DEFINED BY` for container types clauses. Typically
|
||||
used together with :class:`~pyasn1.type.univ.Any` object.
|
||||
|
||||
OpenType objects duck-type a read-only Python :class:`dict` objects,
|
||||
however the passed `typeMap` is not copied, but stored by reference.
|
||||
That means the user can manipulate `typeMap` at run time having this
|
||||
reflected on *OpenType* object behavior.
|
||||
|
||||
The |OpenType| class models an untyped field of a constructed ASN.1
|
||||
type. In ASN.1 syntax it is usually represented by the
|
||||
`ANY DEFINED BY` for scalars or `SET OF ANY DEFINED BY`,
|
||||
`SEQUENCE OF ANY DEFINED BY` for container types clauses. Typically
|
||||
used with :class:`~pyasn1.type.univ.Any` type.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name: :py:class:`str`
|
||||
Field name
|
||||
|
||||
typeMap: :py:class:`dict`
|
||||
A map of value->ASN.1 type. It's stored by reference and can be
|
||||
mutated later to register new mappings.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
For untyped scalars:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
openType = OpenType(
|
||||
'id', {1: Integer(),
|
||||
2: OctetString()}
|
||||
)
|
||||
Sequence(
|
||||
componentType=NamedTypes(
|
||||
NamedType('id', Integer()),
|
||||
NamedType('blob', Any(), openType=openType)
|
||||
)
|
||||
)
|
||||
|
||||
For untyped `SET OF` or `SEQUENCE OF` vectors:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
openType = OpenType(
|
||||
'id', {1: Integer(),
|
||||
2: OctetString()}
|
||||
)
|
||||
Sequence(
|
||||
componentType=NamedTypes(
|
||||
NamedType('id', Integer()),
|
||||
NamedType('blob', SetOf(componentType=Any()),
|
||||
openType=openType)
|
||||
)
|
||||
)
|
||||
"""
|
||||
|
||||
def __init__(self, name, typeMap=None):
|
||||
self.__name = name
|
||||
if typeMap is None:
|
||||
self.__typeMap = {}
|
||||
else:
|
||||
self.__typeMap = typeMap
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.__name
|
||||
|
||||
# Python dict protocol
|
||||
|
||||
def values(self):
|
||||
return self.__typeMap.values()
|
||||
|
||||
def keys(self):
|
||||
return self.__typeMap.keys()
|
||||
|
||||
def items(self):
|
||||
return self.__typeMap.items()
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self.__typeMap
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.__typeMap[key]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.__typeMap)
|
||||
@@ -0,0 +1,335 @@
|
||||
#
|
||||
# This file is part of pyasn1 software.
|
||||
#
|
||||
# Copyright (c) 2005-2020, Ilya Etingof <etingof@gmail.com>
|
||||
# License: http://snmplabs.com/pyasn1/license.html
|
||||
#
|
||||
from pyasn1 import error
|
||||
|
||||
__all__ = ['tagClassUniversal', 'tagClassApplication', 'tagClassContext',
|
||||
'tagClassPrivate', 'tagFormatSimple', 'tagFormatConstructed',
|
||||
'tagCategoryImplicit', 'tagCategoryExplicit',
|
||||
'tagCategoryUntagged', 'Tag', 'TagSet']
|
||||
|
||||
#: Identifier for ASN.1 class UNIVERSAL
|
||||
tagClassUniversal = 0x00
|
||||
|
||||
#: Identifier for ASN.1 class APPLICATION
|
||||
tagClassApplication = 0x40
|
||||
|
||||
#: Identifier for ASN.1 class context-specific
|
||||
tagClassContext = 0x80
|
||||
|
||||
#: Identifier for ASN.1 class private
|
||||
tagClassPrivate = 0xC0
|
||||
|
||||
#: Identifier for "simple" ASN.1 structure (e.g. scalar)
|
||||
tagFormatSimple = 0x00
|
||||
|
||||
#: Identifier for "constructed" ASN.1 structure (e.g. may have inner components)
|
||||
tagFormatConstructed = 0x20
|
||||
|
||||
tagCategoryImplicit = 0x01
|
||||
tagCategoryExplicit = 0x02
|
||||
tagCategoryUntagged = 0x04
|
||||
|
||||
|
||||
class Tag(object):
|
||||
"""Create ASN.1 tag
|
||||
|
||||
Represents ASN.1 tag that can be attached to a ASN.1 type to make
|
||||
types distinguishable from each other.
|
||||
|
||||
*Tag* objects are immutable and duck-type Python :class:`tuple` objects
|
||||
holding three integer components of a tag.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tagClass: :py:class:`int`
|
||||
Tag *class* value
|
||||
|
||||
tagFormat: :py:class:`int`
|
||||
Tag *format* value
|
||||
|
||||
tagId: :py:class:`int`
|
||||
Tag ID value
|
||||
"""
|
||||
def __init__(self, tagClass, tagFormat, tagId):
|
||||
if tagId < 0:
|
||||
raise error.PyAsn1Error('Negative tag ID (%s) not allowed' % tagId)
|
||||
self.__tagClass = tagClass
|
||||
self.__tagFormat = tagFormat
|
||||
self.__tagId = tagId
|
||||
self.__tagClassId = tagClass, tagId
|
||||
self.__hash = hash(self.__tagClassId)
|
||||
|
||||
def __repr__(self):
|
||||
representation = '[%s:%s:%s]' % (
|
||||
self.__tagClass, self.__tagFormat, self.__tagId)
|
||||
return '<%s object, tag %s>' % (
|
||||
self.__class__.__name__, representation)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.__tagClassId == other
|
||||
|
||||
def __ne__(self, other):
|
||||
return self.__tagClassId != other
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.__tagClassId < other
|
||||
|
||||
def __le__(self, other):
|
||||
return self.__tagClassId <= other
|
||||
|
||||
def __gt__(self, other):
|
||||
return self.__tagClassId > other
|
||||
|
||||
def __ge__(self, other):
|
||||
return self.__tagClassId >= other
|
||||
|
||||
def __hash__(self):
|
||||
return self.__hash
|
||||
|
||||
def __getitem__(self, idx):
|
||||
if idx == 0:
|
||||
return self.__tagClass
|
||||
elif idx == 1:
|
||||
return self.__tagFormat
|
||||
elif idx == 2:
|
||||
return self.__tagId
|
||||
else:
|
||||
raise IndexError()
|
||||
|
||||
def __iter__(self):
|
||||
yield self.__tagClass
|
||||
yield self.__tagFormat
|
||||
yield self.__tagId
|
||||
|
||||
def __and__(self, otherTag):
|
||||
return self.__class__(self.__tagClass & otherTag.tagClass,
|
||||
self.__tagFormat & otherTag.tagFormat,
|
||||
self.__tagId & otherTag.tagId)
|
||||
|
||||
def __or__(self, otherTag):
|
||||
return self.__class__(self.__tagClass | otherTag.tagClass,
|
||||
self.__tagFormat | otherTag.tagFormat,
|
||||
self.__tagId | otherTag.tagId)
|
||||
|
||||
@property
|
||||
def tagClass(self):
|
||||
"""ASN.1 tag class
|
||||
|
||||
Returns
|
||||
-------
|
||||
: :py:class:`int`
|
||||
Tag class
|
||||
"""
|
||||
return self.__tagClass
|
||||
|
||||
@property
|
||||
def tagFormat(self):
|
||||
"""ASN.1 tag format
|
||||
|
||||
Returns
|
||||
-------
|
||||
: :py:class:`int`
|
||||
Tag format
|
||||
"""
|
||||
return self.__tagFormat
|
||||
|
||||
@property
|
||||
def tagId(self):
|
||||
"""ASN.1 tag ID
|
||||
|
||||
Returns
|
||||
-------
|
||||
: :py:class:`int`
|
||||
Tag ID
|
||||
"""
|
||||
return self.__tagId
|
||||
|
||||
|
||||
class TagSet(object):
|
||||
"""Create a collection of ASN.1 tags
|
||||
|
||||
Represents a combination of :class:`~pyasn1.type.tag.Tag` objects
|
||||
that can be attached to a ASN.1 type to make types distinguishable
|
||||
from each other.
|
||||
|
||||
*TagSet* objects are immutable and duck-type Python :class:`tuple` objects
|
||||
holding arbitrary number of :class:`~pyasn1.type.tag.Tag` objects.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
baseTag: :class:`~pyasn1.type.tag.Tag`
|
||||
Base *Tag* object. This tag survives IMPLICIT tagging.
|
||||
|
||||
*superTags: :class:`~pyasn1.type.tag.Tag`
|
||||
Additional *Tag* objects taking part in subtyping.
|
||||
|
||||
Examples
|
||||
--------
|
||||
.. code-block:: python
|
||||
|
||||
class OrderNumber(NumericString):
|
||||
'''
|
||||
ASN.1 specification
|
||||
|
||||
Order-number ::=
|
||||
[APPLICATION 5] IMPLICIT NumericString
|
||||
'''
|
||||
tagSet = NumericString.tagSet.tagImplicitly(
|
||||
Tag(tagClassApplication, tagFormatSimple, 5)
|
||||
)
|
||||
|
||||
orderNumber = OrderNumber('1234')
|
||||
"""
|
||||
def __init__(self, baseTag=(), *superTags):
|
||||
self.__baseTag = baseTag
|
||||
self.__superTags = superTags
|
||||
self.__superTagsClassId = tuple(
|
||||
[(superTag.tagClass, superTag.tagId) for superTag in superTags]
|
||||
)
|
||||
self.__lenOfSuperTags = len(superTags)
|
||||
self.__hash = hash(self.__superTagsClassId)
|
||||
|
||||
def __repr__(self):
|
||||
representation = '-'.join(['%s:%s:%s' % (x.tagClass, x.tagFormat, x.tagId)
|
||||
for x in self.__superTags])
|
||||
if representation:
|
||||
representation = 'tags ' + representation
|
||||
else:
|
||||
representation = 'untagged'
|
||||
|
||||
return '<%s object, %s>' % (self.__class__.__name__, representation)
|
||||
|
||||
def __add__(self, superTag):
|
||||
return self.__class__(self.__baseTag, *self.__superTags + (superTag,))
|
||||
|
||||
def __radd__(self, superTag):
|
||||
return self.__class__(self.__baseTag, *(superTag,) + self.__superTags)
|
||||
|
||||
def __getitem__(self, i):
|
||||
if i.__class__ is slice:
|
||||
return self.__class__(self.__baseTag, *self.__superTags[i])
|
||||
else:
|
||||
return self.__superTags[i]
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.__superTagsClassId == other
|
||||
|
||||
def __ne__(self, other):
|
||||
return self.__superTagsClassId != other
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.__superTagsClassId < other
|
||||
|
||||
def __le__(self, other):
|
||||
return self.__superTagsClassId <= other
|
||||
|
||||
def __gt__(self, other):
|
||||
return self.__superTagsClassId > other
|
||||
|
||||
def __ge__(self, other):
|
||||
return self.__superTagsClassId >= other
|
||||
|
||||
def __hash__(self):
|
||||
return self.__hash
|
||||
|
||||
def __len__(self):
|
||||
return self.__lenOfSuperTags
|
||||
|
||||
@property
|
||||
def baseTag(self):
|
||||
"""Return base ASN.1 tag
|
||||
|
||||
Returns
|
||||
-------
|
||||
: :class:`~pyasn1.type.tag.Tag`
|
||||
Base tag of this *TagSet*
|
||||
"""
|
||||
return self.__baseTag
|
||||
|
||||
@property
|
||||
def superTags(self):
|
||||
"""Return ASN.1 tags
|
||||
|
||||
Returns
|
||||
-------
|
||||
: :py:class:`tuple`
|
||||
Tuple of :class:`~pyasn1.type.tag.Tag` objects that this *TagSet* contains
|
||||
"""
|
||||
return self.__superTags
|
||||
|
||||
def tagExplicitly(self, superTag):
|
||||
"""Return explicitly tagged *TagSet*
|
||||
|
||||
Create a new *TagSet* representing callee *TagSet* explicitly tagged
|
||||
with passed tag(s). With explicit tagging mode, new tags are appended
|
||||
to existing tag(s).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
superTag: :class:`~pyasn1.type.tag.Tag`
|
||||
*Tag* object to tag this *TagSet*
|
||||
|
||||
Returns
|
||||
-------
|
||||
: :class:`~pyasn1.type.tag.TagSet`
|
||||
New *TagSet* object
|
||||
"""
|
||||
if superTag.tagClass == tagClassUniversal:
|
||||
raise error.PyAsn1Error("Can't tag with UNIVERSAL class tag")
|
||||
if superTag.tagFormat != tagFormatConstructed:
|
||||
superTag = Tag(superTag.tagClass, tagFormatConstructed, superTag.tagId)
|
||||
return self + superTag
|
||||
|
||||
def tagImplicitly(self, superTag):
|
||||
"""Return implicitly tagged *TagSet*
|
||||
|
||||
Create a new *TagSet* representing callee *TagSet* implicitly tagged
|
||||
with passed tag(s). With implicit tagging mode, new tag(s) replace the
|
||||
last existing tag.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
superTag: :class:`~pyasn1.type.tag.Tag`
|
||||
*Tag* object to tag this *TagSet*
|
||||
|
||||
Returns
|
||||
-------
|
||||
: :class:`~pyasn1.type.tag.TagSet`
|
||||
New *TagSet* object
|
||||
"""
|
||||
if self.__superTags:
|
||||
superTag = Tag(superTag.tagClass, self.__superTags[-1].tagFormat, superTag.tagId)
|
||||
return self[:-1] + superTag
|
||||
|
||||
def isSuperTagSetOf(self, tagSet):
|
||||
"""Test type relationship against given *TagSet*
|
||||
|
||||
The callee is considered to be a supertype of given *TagSet*
|
||||
tag-wise if all tags in *TagSet* are present in the callee and
|
||||
they are in the same order.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tagSet: :class:`~pyasn1.type.tag.TagSet`
|
||||
*TagSet* object to evaluate against the callee
|
||||
|
||||
Returns
|
||||
-------
|
||||
: :py:class:`bool`
|
||||
:obj:`True` if callee is a supertype of *tagSet*
|
||||
"""
|
||||
if len(tagSet) < self.__lenOfSuperTags:
|
||||
return False
|
||||
return self.__superTags == tagSet[:self.__lenOfSuperTags]
|
||||
|
||||
# Backward compatibility
|
||||
|
||||
def getBaseTag(self):
|
||||
return self.__baseTag
|
||||
|
||||
def initTagSet(tag):
|
||||
return TagSet(tag, tag)
|
||||
@@ -0,0 +1,96 @@
|
||||
#
|
||||
# This file is part of pyasn1 software.
|
||||
#
|
||||
# Copyright (c) 2005-2020, Ilya Etingof <etingof@gmail.com>
|
||||
# License: http://snmplabs.com/pyasn1/license.html
|
||||
#
|
||||
from pyasn1 import error
|
||||
|
||||
__all__ = ['TagMap']
|
||||
|
||||
|
||||
class TagMap(object):
|
||||
"""Map *TagSet* objects to ASN.1 types
|
||||
|
||||
Create an object mapping *TagSet* object to ASN.1 type.
|
||||
|
||||
*TagMap* objects are immutable and duck-type read-only Python
|
||||
:class:`dict` objects holding *TagSet* objects as keys and ASN.1
|
||||
type objects as values.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
presentTypes: :py:class:`dict`
|
||||
Map of :class:`~pyasn1.type.tag.TagSet` to ASN.1 objects considered
|
||||
as being unconditionally present in the *TagMap*.
|
||||
|
||||
skipTypes: :py:class:`dict`
|
||||
A collection of :class:`~pyasn1.type.tag.TagSet` objects considered
|
||||
as absent in the *TagMap* even when *defaultType* is present.
|
||||
|
||||
defaultType: ASN.1 type object
|
||||
An ASN.1 type object callee *TagMap* returns for any *TagSet* key not present
|
||||
in *presentTypes* (unless given key is present in *skipTypes*).
|
||||
"""
|
||||
def __init__(self, presentTypes=None, skipTypes=None, defaultType=None):
|
||||
self.__presentTypes = presentTypes or {}
|
||||
self.__skipTypes = skipTypes or {}
|
||||
self.__defaultType = defaultType
|
||||
|
||||
def __contains__(self, tagSet):
|
||||
return (tagSet in self.__presentTypes or
|
||||
self.__defaultType is not None and tagSet not in self.__skipTypes)
|
||||
|
||||
def __getitem__(self, tagSet):
|
||||
try:
|
||||
return self.__presentTypes[tagSet]
|
||||
except KeyError:
|
||||
if self.__defaultType is None:
|
||||
raise KeyError()
|
||||
elif tagSet in self.__skipTypes:
|
||||
raise error.PyAsn1Error('Key in negative map')
|
||||
else:
|
||||
return self.__defaultType
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.__presentTypes)
|
||||
|
||||
def __repr__(self):
|
||||
representation = '%s object' % self.__class__.__name__
|
||||
|
||||
if self.__presentTypes:
|
||||
representation += ', present %s' % repr(self.__presentTypes)
|
||||
|
||||
if self.__skipTypes:
|
||||
representation += ', skip %s' % repr(self.__skipTypes)
|
||||
|
||||
if self.__defaultType is not None:
|
||||
representation += ', default %s' % repr(self.__defaultType)
|
||||
|
||||
return '<%s>' % representation
|
||||
|
||||
@property
|
||||
def presentTypes(self):
|
||||
"""Return *TagSet* to ASN.1 type map present in callee *TagMap*"""
|
||||
return self.__presentTypes
|
||||
|
||||
@property
|
||||
def skipTypes(self):
|
||||
"""Return *TagSet* collection unconditionally absent in callee *TagMap*"""
|
||||
return self.__skipTypes
|
||||
|
||||
@property
|
||||
def defaultType(self):
|
||||
"""Return default ASN.1 type being returned for any missing *TagSet*"""
|
||||
return self.__defaultType
|
||||
|
||||
# Backward compatibility
|
||||
|
||||
def getPosMap(self):
|
||||
return self.presentTypes
|
||||
|
||||
def getNegMap(self):
|
||||
return self.skipTypes
|
||||
|
||||
def getDef(self):
|
||||
return self.defaultType
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,189 @@
|
||||
#
|
||||
# This file is part of pyasn1 software.
|
||||
#
|
||||
# Copyright (c) 2005-2020, Ilya Etingof <etingof@gmail.com>
|
||||
# License: http://snmplabs.com/pyasn1/license.html
|
||||
#
|
||||
import datetime
|
||||
|
||||
from pyasn1 import error
|
||||
from pyasn1.type import char
|
||||
from pyasn1.type import tag
|
||||
from pyasn1.type import univ
|
||||
|
||||
__all__ = ['ObjectDescriptor', 'GeneralizedTime', 'UTCTime']
|
||||
|
||||
NoValue = univ.NoValue
|
||||
noValue = univ.noValue
|
||||
|
||||
|
||||
class ObjectDescriptor(char.GraphicString):
|
||||
__doc__ = char.GraphicString.__doc__
|
||||
|
||||
#: Default :py:class:`~pyasn1.type.tag.TagSet` object for |ASN.1| objects
|
||||
tagSet = char.GraphicString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 7)
|
||||
)
|
||||
|
||||
# Optimization for faster codec lookup
|
||||
typeId = char.GraphicString.getTypeId()
|
||||
|
||||
|
||||
class TimeMixIn(object):
|
||||
|
||||
_yearsDigits = 4
|
||||
_hasSubsecond = False
|
||||
_optionalMinutes = False
|
||||
_shortTZ = False
|
||||
|
||||
class FixedOffset(datetime.tzinfo):
|
||||
"""Fixed offset in minutes east from UTC."""
|
||||
|
||||
# defaulted arguments required
|
||||
# https: // docs.python.org / 2.3 / lib / datetime - tzinfo.html
|
||||
def __init__(self, offset=0, name='UTC'):
|
||||
self.__offset = datetime.timedelta(minutes=offset)
|
||||
self.__name = name
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return self.__offset
|
||||
|
||||
def tzname(self, dt):
|
||||
return self.__name
|
||||
|
||||
def dst(self, dt):
|
||||
return datetime.timedelta(0)
|
||||
|
||||
UTC = FixedOffset()
|
||||
|
||||
@property
|
||||
def asDateTime(self):
|
||||
"""Create :py:class:`datetime.datetime` object from a |ASN.1| object.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:
|
||||
new instance of :py:class:`datetime.datetime` object
|
||||
"""
|
||||
text = str(self)
|
||||
if text.endswith('Z'):
|
||||
tzinfo = TimeMixIn.UTC
|
||||
text = text[:-1]
|
||||
|
||||
elif '-' in text or '+' in text:
|
||||
if '+' in text:
|
||||
text, plusminus, tz = text.partition('+')
|
||||
else:
|
||||
text, plusminus, tz = text.partition('-')
|
||||
|
||||
if self._shortTZ and len(tz) == 2:
|
||||
tz += '00'
|
||||
|
||||
if len(tz) != 4:
|
||||
raise error.PyAsn1Error('malformed time zone offset %s' % tz)
|
||||
|
||||
try:
|
||||
minutes = int(tz[:2]) * 60 + int(tz[2:])
|
||||
if plusminus == '-':
|
||||
minutes *= -1
|
||||
|
||||
except ValueError:
|
||||
raise error.PyAsn1Error('unknown time specification %s' % self)
|
||||
|
||||
tzinfo = TimeMixIn.FixedOffset(minutes, '?')
|
||||
|
||||
else:
|
||||
tzinfo = None
|
||||
|
||||
if '.' in text or ',' in text:
|
||||
if '.' in text:
|
||||
text, _, ms = text.partition('.')
|
||||
else:
|
||||
text, _, ms = text.partition(',')
|
||||
|
||||
try:
|
||||
ms = int(ms) * 1000
|
||||
|
||||
except ValueError:
|
||||
raise error.PyAsn1Error('bad sub-second time specification %s' % self)
|
||||
|
||||
else:
|
||||
ms = 0
|
||||
|
||||
if self._optionalMinutes and len(text) - self._yearsDigits == 6:
|
||||
text += '0000'
|
||||
elif len(text) - self._yearsDigits == 8:
|
||||
text += '00'
|
||||
|
||||
try:
|
||||
dt = datetime.datetime.strptime(text, self._yearsDigits == 4 and '%Y%m%d%H%M%S' or '%y%m%d%H%M%S')
|
||||
|
||||
except ValueError:
|
||||
raise error.PyAsn1Error('malformed datetime format %s' % self)
|
||||
|
||||
return dt.replace(microsecond=ms, tzinfo=tzinfo)
|
||||
|
||||
@classmethod
|
||||
def fromDateTime(cls, dt):
|
||||
"""Create |ASN.1| object from a :py:class:`datetime.datetime` object.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
dt: :py:class:`datetime.datetime` object
|
||||
The `datetime.datetime` object to initialize the |ASN.1| object
|
||||
from
|
||||
|
||||
Returns
|
||||
-------
|
||||
:
|
||||
new instance of |ASN.1| value
|
||||
"""
|
||||
text = dt.strftime(cls._yearsDigits == 4 and '%Y%m%d%H%M%S' or '%y%m%d%H%M%S')
|
||||
if cls._hasSubsecond:
|
||||
text += '.%d' % (dt.microsecond // 1000)
|
||||
|
||||
if dt.utcoffset():
|
||||
seconds = dt.utcoffset().seconds
|
||||
if seconds < 0:
|
||||
text += '-'
|
||||
else:
|
||||
text += '+'
|
||||
text += '%.2d%.2d' % (seconds // 3600, seconds % 3600)
|
||||
else:
|
||||
text += 'Z'
|
||||
|
||||
return cls(text)
|
||||
|
||||
|
||||
class GeneralizedTime(char.VisibleString, TimeMixIn):
|
||||
__doc__ = char.VisibleString.__doc__
|
||||
|
||||
#: Default :py:class:`~pyasn1.type.tag.TagSet` object for |ASN.1| objects
|
||||
tagSet = char.VisibleString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 24)
|
||||
)
|
||||
|
||||
# Optimization for faster codec lookup
|
||||
typeId = char.VideotexString.getTypeId()
|
||||
|
||||
_yearsDigits = 4
|
||||
_hasSubsecond = True
|
||||
_optionalMinutes = True
|
||||
_shortTZ = True
|
||||
|
||||
|
||||
class UTCTime(char.VisibleString, TimeMixIn):
|
||||
__doc__ = char.VisibleString.__doc__
|
||||
|
||||
#: Default :py:class:`~pyasn1.type.tag.TagSet` object for |ASN.1| objects
|
||||
tagSet = char.VisibleString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 23)
|
||||
)
|
||||
|
||||
# Optimization for faster codec lookup
|
||||
typeId = char.VideotexString.getTypeId()
|
||||
|
||||
_yearsDigits = 2
|
||||
_hasSubsecond = False
|
||||
_optionalMinutes = False
|
||||
_shortTZ = False
|
||||
Reference in New Issue
Block a user