]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - pyderasn.py
Change order of value sanitizers
[pyderasn.git] / pyderasn.py
index 482b3456cc2eca5e6b5ea3936ee9927a89744d4f..50faa0be870ced74ab9d33afb4ac0f4008846421 100755 (executable)
@@ -384,7 +384,8 @@ constructed primitive types should be parsed successfully.
 
 * If object is encoded in BER form (not the DER one), then ``ber_encoded``
   attribute is set to True. Only ``BOOLEAN``, ``BIT STRING``, ``OCTET
-  STRING``, ``SEQUENCE``, ``SET``, ``SET OF`` can contain it.
+  STRING``, ``OBJECT IDENTIFIER``, ``SEQUENCE``, ``SET``, ``SET OF``
+  can contain it.
 * If object has an indefinite length encoding, then its ``lenindef``
   attribute is set to True. Only ``BIT STRING``, ``OCTET STRING``,
   ``SEQUENCE``, ``SET``, ``SEQUENCE OF``, ``SET OF``, ``ANY`` can
@@ -554,6 +555,8 @@ from six import indexbytes
 from six import int2byte
 from six import integer_types
 from six import iterbytes
+from six import iteritems
+from six import itervalues
 from six import PY2
 from six import string_types
 from six import text_type
@@ -1324,7 +1327,7 @@ def _colourize(what, colour, with_colours, attrs=("bold",)):
 def colonize_hex(hexed):
     """Separate hexadecimal string with colons
     """
-    return ":".join(hexed[i:i + 2] for i in range(0, len(hexed), 2))
+    return ":".join(hexed[i:i + 2] for i in six_xrange(0, len(hexed), 2))
 
 
 def pp_console_row(
@@ -1431,7 +1434,7 @@ def pp_console_blob(pp, decode_path_len_decrease=0):
         cols.append(" ." * (decode_path_len + 1))
     if isinstance(pp.blob, binary_type):
         blob = hexenc(pp.blob).upper()
-        for i in range(0, len(blob), 32):
+        for i in six_xrange(0, len(blob), 32):
             chunk = blob[i:i + 32]
             yield " ".join(cols + [colonize_hex(chunk)])
     elif isinstance(pp.blob, tuple):
@@ -1548,10 +1551,10 @@ class Boolean(Obj):
                 self._value = default
 
     def _value_sanitize(self, value):
-        if issubclass(value.__class__, Boolean):
-            return value._value
         if isinstance(value, bool):
             return value
+        if issubclass(value.__class__, Boolean):
+            return value._value
         raise InvalidValueType((self.__class__, bool))
 
     @property
@@ -1568,6 +1571,9 @@ class Boolean(Obj):
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
+        obj.expl_lenindef = self.expl_lenindef
+        obj.lenindef = self.lenindef
+        obj.ber_encoded = self.ber_encoded
         return obj
 
     def __nonzero__(self):
@@ -1794,10 +1800,10 @@ class Integer(Obj):
                 self._value = default
 
     def _value_sanitize(self, value):
-        if issubclass(value.__class__, Integer):
-            value = value._value
-        elif isinstance(value, integer_types):
+        if isinstance(value, integer_types):
             pass
+        elif issubclass(value.__class__, Integer):
+            value = value._value
         elif isinstance(value, str):
             value = self.specs.get(value)
             if value is None:
@@ -1824,6 +1830,9 @@ class Integer(Obj):
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
+        obj.expl_lenindef = self.expl_lenindef
+        obj.lenindef = self.lenindef
+        obj.ber_encoded = self.ber_encoded
         return obj
 
     def __int__(self):
@@ -1854,7 +1863,7 @@ class Integer(Obj):
 
     @property
     def named(self):
-        for name, value in self.specs.items():
+        for name, value in iteritems(self.specs):
             if value == self._value:
                 return name
 
@@ -2038,6 +2047,9 @@ class Integer(Obj):
             yield pp
 
 
+SET01 = frozenset(("0", "1"))
+
+
 class BitString(Obj):
     """``BIT STRING`` bit string type
 
@@ -2143,8 +2155,6 @@ class BitString(Obj):
         return bit_len, bytes(octets)
 
     def _value_sanitize(self, value):
-        if issubclass(value.__class__, BitString):
-            return value._value
         if isinstance(value, (string_types, binary_type)):
             if (
                     isinstance(value, string_types) and
@@ -2152,7 +2162,7 @@ class BitString(Obj):
             ):
                 if value.endswith("'B"):
                     value = value[1:-2]
-                    if not set(value) <= set(("0", "1")):
+                    if not frozenset(value) <= SET01:
                         raise ValueError("B's coding contains unacceptable chars")
                     return self._bits2octets(value)
                 elif value.endswith("'H"):
@@ -2180,11 +2190,13 @@ class BitString(Obj):
                 bits.append(bit)
             if len(bits) == 0:
                 return self._bits2octets("")
-            bits = set(bits)
+            bits = frozenset(bits)
             return self._bits2octets("".join(
                 ("1" if bit in bits else "0")
                 for bit in six_xrange(max(bits) + 1)
             ))
+        if issubclass(value.__class__, BitString):
+            return value._value
         raise InvalidValueType((self.__class__, binary_type, string_types))
 
     @property
@@ -2204,6 +2216,9 @@ class BitString(Obj):
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
+        obj.expl_lenindef = self.expl_lenindef
+        obj.lenindef = self.lenindef
+        obj.ber_encoded = self.ber_encoded
         return obj
 
     def __iter__(self):
@@ -2233,7 +2248,7 @@ class BitString(Obj):
 
     @property
     def named(self):
-        return [name for name, bit in self.specs.items() if self[bit]]
+        return [name for name, bit in iteritems(self.specs) if self[bit]]
 
     def __call__(
             self,
@@ -2590,10 +2605,10 @@ class OctetString(Obj):
         )
 
     def _value_sanitize(self, value):
-        if issubclass(value.__class__, OctetString):
-            value = value._value
-        elif isinstance(value, binary_type):
+        if isinstance(value, binary_type):
             pass
+        elif issubclass(value.__class__, OctetString):
+            value = value._value
         else:
             raise InvalidValueType((self.__class__, bytes))
         if not self._bound_min <= len(value) <= self._bound_max:
@@ -2616,6 +2631,9 @@ class OctetString(Obj):
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
+        obj.expl_lenindef = self.expl_lenindef
+        obj.lenindef = self.lenindef
+        obj.ber_encoded = self.ber_encoded
         return obj
 
     def __bytes__(self):
@@ -2902,6 +2920,9 @@ class Null(Obj):
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
+        obj.expl_lenindef = self.expl_lenindef
+        obj.lenindef = self.lenindef
+        obj.ber_encoded = self.ber_encoded
         return obj
 
     def __eq__(self, their):
@@ -3112,6 +3133,9 @@ class ObjectIdentifier(Obj):
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
+        obj.expl_lenindef = self.expl_lenindef
+        obj.lenindef = self.lenindef
+        obj.ber_encoded = self.ber_encoded
         return obj
 
     def __iter__(self):
@@ -3223,11 +3247,17 @@ class ObjectIdentifier(Obj):
             )
         v, tail = v[:l], v[l:]
         arcs = []
+        ber_encoded = False
         while len(v) > 0:
             i = 0
             arc = 0
             while True:
                 octet = indexbytes(v, i)
+                if i == 0 and octet == 0x80:
+                    if ctx.get("bered", False):
+                        ber_encoded = True
+                    else:
+                        raise DecodeError("non normalized arc encoding")
                 arc = (arc << 7) | (octet & 0x7F)
                 if octet & 0x80 == 0:
                     arcs.append(arc)
@@ -3259,6 +3289,8 @@ class ObjectIdentifier(Obj):
             optional=self.optional,
             _decoded=(offset, llen, l),
         )
+        if ber_encoded:
+            obj.ber_encoded = True
         return obj, tail
 
     def __repr__(self):
@@ -3284,6 +3316,7 @@ class ObjectIdentifier(Obj):
             expl_llen=self.expl_llen if self.expled else None,
             expl_vlen=self.expl_vlen if self.expled else None,
             expl_lenindef=self.expl_lenindef,
+            ber_encoded=self.ber_encoded,
             bered=self.bered,
         )
         for pp in self.pps_lenindef(decode_path):
@@ -3327,7 +3360,10 @@ class Enumerated(Integer):
         if isinstance(value, self.__class__):
             value = value._value
         elif isinstance(value, integer_types):
-            if value not in list(self.specs.values()):
+            for _value in itervalues(self.specs):
+                if _value == value:
+                    break
+            else:
                 raise DecodeError(
                     "unknown integer value: %s" % value,
                     klass=self.__class__,
@@ -3352,6 +3388,9 @@ class Enumerated(Integer):
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
+        obj.expl_lenindef = self.expl_lenindef
+        obj.lenindef = self.lenindef
+        obj.ber_encoded = self.ber_encoded
         return obj
 
     def __call__(
@@ -3530,7 +3569,7 @@ class AllowableCharsMixin(object):
     def allowable_chars(self):
         if PY2:
             return self._allowable_chars
-        return set(six_unichr(c) for c in self._allowable_chars)
+        return frozenset(six_unichr(c) for c in self._allowable_chars)
 
 
 class NumericString(AllowableCharsMixin, CommonString):
@@ -3546,11 +3585,11 @@ class NumericString(AllowableCharsMixin, CommonString):
     tag_default = tag_encode(18)
     encoding = "ascii"
     asn1_type_name = "NumericString"
-    _allowable_chars = set(digits.encode("ascii") + b" ")
+    _allowable_chars = frozenset(digits.encode("ascii") + b" ")
 
     def _value_sanitize(self, value):
         value = super(NumericString, self)._value_sanitize(value)
-        if not set(value) <= self._allowable_chars:
+        if not frozenset(value) <= self._allowable_chars:
             raise DecodeError("non-numeric value")
         return value
 
@@ -3567,13 +3606,13 @@ class PrintableString(AllowableCharsMixin, CommonString):
     tag_default = tag_encode(19)
     encoding = "ascii"
     asn1_type_name = "PrintableString"
-    _allowable_chars = set(
+    _allowable_chars = frozenset(
         (ascii_letters + digits + " '()+,-./:=?").encode("ascii")
     )
 
     def _value_sanitize(self, value):
         value = super(PrintableString, self)._value_sanitize(value)
-        if not set(value) <= self._allowable_chars:
+        if not frozenset(value) <= self._allowable_chars:
             raise DecodeError("non-printable value")
         return value
 
@@ -3669,10 +3708,6 @@ class UTCTime(CommonString):
                 self._value = default
 
     def _value_sanitize(self, value):
-        if isinstance(value, self.__class__):
-            return value._value
-        if isinstance(value, datetime):
-            return value.strftime(self.fmt).encode("ascii")
         if isinstance(value, binary_type):
             try:
                 value_decoded = value.decode("ascii")
@@ -3686,6 +3721,10 @@ class UTCTime(CommonString):
                 return value
             else:
                 raise DecodeError("invalid UTCTime length")
+        if isinstance(value, self.__class__):
+            return value._value
+        if isinstance(value, datetime):
+            return value.strftime(self.fmt).encode("ascii")
         raise InvalidValueType((self.__class__, datetime))
 
     def __eq__(self, their):
@@ -3771,12 +3810,6 @@ class GeneralizedTime(UTCTime):
     fmt_ms = "%Y%m%d%H%M%S.%fZ"
 
     def _value_sanitize(self, value):
-        if isinstance(value, self.__class__):
-            return value._value
-        if isinstance(value, datetime):
-            return value.strftime(
-                self.fmt_ms if value.microsecond > 0 else self.fmt
-            ).encode("ascii")
         if isinstance(value, binary_type):
             try:
                 value_decoded = value.decode("ascii")
@@ -3803,6 +3836,12 @@ class GeneralizedTime(UTCTime):
                     "invalid GeneralizedTime length",
                     klass=self.__class__,
                 )
+        if isinstance(value, self.__class__):
+            return value._value
+        if isinstance(value, datetime):
+            return value.strftime(
+                self.fmt_ms if value.microsecond > 0 else self.fmt
+            ).encode("ascii")
         raise InvalidValueType((self.__class__, datetime))
 
     def todatetime(self):
@@ -3928,8 +3967,6 @@ class Choice(Obj):
                 self._value = default_obj.copy()._value
 
     def _value_sanitize(self, value):
-        if isinstance(value, self.__class__):
-            return value._value
         if isinstance(value, tuple) and len(value) == 2:
             choice, obj = value
             spec = self.specs.get(choice)
@@ -3938,6 +3975,8 @@ class Choice(Obj):
             if not isinstance(obj, spec.__class__):
                 raise InvalidValueType((spec,))
             return (choice, spec(obj))
+        if isinstance(value, self.__class__):
+            return value._value
         raise InvalidValueType((self.__class__, tuple))
 
     @property
@@ -3959,6 +3998,9 @@ class Choice(Obj):
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
+        obj.expl_lenindef = self.expl_lenindef
+        obj.lenindef = self.lenindef
+        obj.ber_encoded = self.ber_encoded
         value = self._value
         if value is not None:
             obj._value = (value[0], value[1].copy())
@@ -4030,7 +4072,7 @@ class Choice(Obj):
         return self._value[1].encode()
 
     def _decode(self, tlv, offset, decode_path, ctx, tag_only):
-        for choice, spec in self.specs.items():
+        for choice, spec in iteritems(self.specs):
             sub_decode_path = decode_path + (choice,)
             try:
                 spec.decode(
@@ -4170,12 +4212,12 @@ class Any(Obj):
         self.defined = None
 
     def _value_sanitize(self, value):
+        if isinstance(value, binary_type):
+            return value
         if isinstance(value, self.__class__):
             return value._value
         if isinstance(value, Obj):
             return value.encode()
-        if isinstance(value, binary_type):
-            return value
         raise InvalidValueType((self.__class__, Obj, binary_type))
 
     @property
@@ -4199,6 +4241,9 @@ class Any(Obj):
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
+        obj.expl_lenindef = self.expl_lenindef
+        obj.lenindef = self.lenindef
+        obj.ber_encoded = self.ber_encoded
         return obj
 
     def __eq__(self, their):
@@ -4512,7 +4557,7 @@ class Sequence(Obj):
 
     @property
     def ready(self):
-        for name, spec in self.specs.items():
+        for name, spec in iteritems(self.specs):
             value = self._value.get(name)
             if value is None:
                 if spec.optional:
@@ -4527,7 +4572,7 @@ class Sequence(Obj):
     def bered(self):
         if self.expl_lenindef or self.lenindef or self.ber_encoded:
             return True
-        return any(value.bered for value in self._value.values())
+        return any(value.bered for value in itervalues(self._value))
 
     def copy(self):
         obj = self.__class__(schema=self.specs)
@@ -4538,7 +4583,10 @@ class Sequence(Obj):
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
-        obj._value = {k: v.copy() for k, v in self._value.items()}
+        obj.expl_lenindef = self.expl_lenindef
+        obj.lenindef = self.lenindef
+        obj.ber_encoded = self.ber_encoded
+        obj._value = {k: v.copy() for k, v in iteritems(self._value)}
         return obj
 
     def __eq__(self, their):
@@ -4599,7 +4647,7 @@ class Sequence(Obj):
 
     def _encoded_values(self):
         raws = []
-        for name, spec in self.specs.items():
+        for name, spec in iteritems(self.specs):
             value = self._value.get(name)
             if value is None:
                 if spec.optional:
@@ -4665,7 +4713,7 @@ class Sequence(Obj):
         values = {}
         ber_encoded = False
         ctx_allow_default_values = ctx.get("allow_default_values", False)
-        for name, spec in self.specs.items():
+        for name, spec in iteritems(self.specs):
             if spec.optional and (
                     (lenindef and v[:EOC_LEN].tobytes() == EOC) or
                     len(v) == 0
@@ -4859,6 +4907,9 @@ class Set(Sequence):
         v = b"".join(raws)
         return b"".join((self.tag, len_encode(len(v)), v))
 
+    def _specs_items(self):
+        return iteritems(self.specs)
+
     def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         try:
             t, tlen, lv = tag_strip(tlv)
@@ -4913,11 +4964,11 @@ class Set(Sequence):
         ctx_allow_default_values = ctx.get("allow_default_values", False)
         ctx_allow_unordered_set = ctx.get("allow_unordered_set", False)
         value_prev = memoryview(v[:0])
-        specs_items = self.specs.items
+
         while len(v) > 0:
             if lenindef and v[:EOC_LEN].tobytes() == EOC:
                 break
-            for name, spec in specs_items():
+            for name, spec in self._specs_items():
                 sub_decode_path = decode_path + (name,)
                 try:
                     spec.decode(
@@ -5115,6 +5166,9 @@ class SequenceOf(Obj):
         obj.offset = self.offset
         obj.llen = self.llen
         obj.vlen = self.vlen
+        obj.expl_lenindef = self.expl_lenindef
+        obj.lenindef = self.lenindef
+        obj.ber_encoded = self.ber_encoded
         obj._value = [v.copy() for v in self._value]
         return obj
 
@@ -5388,7 +5442,7 @@ def generic_decoder():  # pragma: no cover
     choice = PrimitiveTypes()
     choice.specs["SequenceOf"] = SequenceOf(schema=choice)
     choice.specs["SetOf"] = SetOf(schema=choice)
-    for i in range(31):
+    for i in six_xrange(31):
         choice.specs["SequenceOf%d" % i] = SequenceOf(
             schema=choice,
             expl=tag_ctxc(i),