]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - pyderasn.py
allowable_chars property
[pyderasn.git] / pyderasn.py
index ed4071d8dffc5896bd0a92719baeee52220653b4..4a9684fc0b9a6a9e000764fbbca938e223640f4b 100755 (executable)
@@ -542,6 +542,7 @@ from collections import OrderedDict
 from datetime import datetime
 from math import ceil
 from os import environ
+from string import ascii_letters
 from string import digits
 
 from six import add_metaclass
@@ -554,12 +555,13 @@ from six import iterbytes
 from six import PY2
 from six import string_types
 from six import text_type
+from six import unichr as six_unichr
 from six.moves import xrange as six_xrange
 
 
 try:
     from termcolor import colored
-except ImportError:
+except ImportError:  # pragma: no cover
     def colored(what, *args):
         return what
 
@@ -641,7 +643,10 @@ LENINDEF_PP_CHAR = "I" if PY2 else "∞"
 # Errors
 ########################################################################
 
-class DecodeError(Exception):
+class ASN1Error(ValueError):
+    pass
+
+class DecodeError(ASN1Error):
     def __init__(self, msg="", klass=None, decode_path=(), offset=0):
         """
         :param str msg: reason of decode failing
@@ -696,7 +701,7 @@ class InvalidOID(DecodeError):
     pass
 
 
-class ObjUnknown(ValueError):
+class ObjUnknown(ASN1Error):
     def __init__(self, name):
         super(ObjUnknown, self).__init__()
         self.name = name
@@ -708,7 +713,7 @@ class ObjUnknown(ValueError):
         return "%s(%s)" % (self.__class__.__name__, self)
 
 
-class ObjNotReady(ValueError):
+class ObjNotReady(ASN1Error):
     def __init__(self, name):
         super(ObjNotReady, self).__init__()
         self.name = name
@@ -720,7 +725,7 @@ class ObjNotReady(ValueError):
         return "%s(%s)" % (self.__class__.__name__, self)
 
 
-class InvalidValueType(ValueError):
+class InvalidValueType(ASN1Error):
     def __init__(self, expected_types):
         super(InvalidValueType, self).__init__()
         self.expected_types = expected_types
@@ -734,7 +739,7 @@ class InvalidValueType(ValueError):
         return "%s(%s)" % (self.__class__.__name__, self)
 
 
-class BoundsError(ValueError):
+class BoundsError(ASN1Error):
     def __init__(self, bound_min, value, bound_max):
         super(BoundsError, self).__init__()
         self.bound_min = bound_min
@@ -958,16 +963,16 @@ class Obj(object):
         """
         raise NotImplementedError()
 
+    def _assert_ready(self):
+        if not self.ready:
+            raise ObjNotReady(self.__class__.__name__)
+
     @property
     def bered(self):
         """Is either object or any elements inside is BER encoded?
         """
         return self.expl_lenindef or self.lenindef or self.ber_encoded
 
-    def _assert_ready(self):
-        if not self.ready:
-            raise ObjNotReady(self.__class__.__name__)
-
     @property
     def decoded(self):
         """Is object decoded?
@@ -1084,7 +1089,7 @@ class Obj(object):
                     ctx=ctx,
                     tag_only=tag_only,
                 )
-                if tag_only:
+                if tag_only:  # pragma: no cover
                     return
                 obj, tail = result
                 eoc_expected, tail = tail[:EOC_LEN], tail[EOC_LEN:]
@@ -1119,7 +1124,7 @@ class Obj(object):
                     ctx=ctx,
                     tag_only=tag_only,
                 )
-                if tag_only:
+                if tag_only:  # pragma: no cover
                     return
                 obj, tail = result
                 if obj.tlvlen < l and not ctx.get("allow_expl_oob", False):
@@ -1170,7 +1175,10 @@ class Obj(object):
         return self.expl_tlvlen if self.expled else self.tlvlen
 
     def pps_lenindef(self, decode_path):
-        if self.lenindef:
+        if self.lenindef and not (
+            getattr(self, "defined", None) is not None and
+            self.defined[1].lenindef
+        ):
             yield _pp(
                 asn1_type_name="EOC",
                 obj_name="",
@@ -2314,7 +2322,7 @@ class BitString(Obj):
                 offset=offset,
             )
         if t == self.tag:
-            if tag_only:
+            if tag_only:  # pragma: no cover
                 return
             return self._decode_chunk(lv, offset, decode_path, ctx)
         if t == self.tag_constructed:
@@ -2325,7 +2333,7 @@ class BitString(Obj):
                     decode_path=decode_path,
                     offset=offset,
                 )
-            if tag_only:
+            if tag_only:  # pragma: no cover
                 return
             lenindef = False
             try:
@@ -2907,7 +2915,7 @@ class Null(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
-        if tag_only:
+        if tag_only:  # pragma: no cover
             return
         try:
             l, _, v = len_decode(lv)
@@ -3158,7 +3166,7 @@ class ObjectIdentifier(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
-        if tag_only:
+        if tag_only:  # pragma: no cover
             return
         try:
             l, llen, v = len_decode(lv)
@@ -3485,29 +3493,57 @@ class UTF8String(CommonString):
     asn1_type_name = "UTF8String"
 
 
-class NumericString(CommonString):
+class AllowableCharsMixin(object):
+    @property
+    def allowable_chars(self):
+        if PY2:
+            return self._allowable_chars
+        return set(six_unichr(c) for c in self._allowable_chars)
+
+
+class NumericString(AllowableCharsMixin, CommonString):
     """Numeric string
 
-    Its value is properly sanitized: only ASCII digits can be stored.
+    Its value is properly sanitized: only ASCII digits with spaces can
+    be stored.
+
+    >>> NumericString().allowable_chars
+    set(['3', '4', '7', '5', '1', '0', '8', '9', ' ', '6', '2'])
     """
     __slots__ = ()
     tag_default = tag_encode(18)
     encoding = "ascii"
     asn1_type_name = "NumericString"
-    allowable_chars = set(digits.encode("ascii"))
+    _allowable_chars = set(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 set(value) <= self._allowable_chars:
             raise DecodeError("non-numeric value")
         return value
 
 
-class PrintableString(CommonString):
+class PrintableString(AllowableCharsMixin, CommonString):
+    """Printable string
+
+    Its value is properly sanitized: see X.680 41.4 table 10.
+
+    >>> PrintableString().allowable_chars
+    >>> set([' ', "'", ..., 'z'])
+    """
     __slots__ = ()
     tag_default = tag_encode(19)
     encoding = "ascii"
     asn1_type_name = "PrintableString"
+    _allowable_chars = set(
+        (ascii_letters + digits + " '()+,-./:=?").encode("ascii")
+    )
+
+    def _value_sanitize(self, value):
+        value = super(PrintableString, self)._value_sanitize(value)
+        if not set(value) <= self._allowable_chars:
+            raise DecodeError("non-printable value")
+        return value
 
 
 class TeletexString(CommonString):
@@ -3981,7 +4017,7 @@ class Choice(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
-        if tag_only:
+        if tag_only:  # pragma: no cover
             return
         value, tail = spec.decode(
             tlv,
@@ -3998,8 +4034,6 @@ class Choice(Obj):
             _decoded=(offset, 0, value.fulllen),
         )
         obj._value = (choice, value)
-        obj.lenindef = value.lenindef
-        obj.ber_encoded = value.ber_encoded
         return obj, tail
 
     def __repr__(self):
@@ -4023,8 +4057,6 @@ class Choice(Obj):
             llen=self.llen,
             vlen=self.vlen,
             expl_lenindef=self.expl_lenindef,
-            lenindef=self.lenindef,
-            ber_encoded=self.ber_encoded,
             bered=self.bered,
         )
         if self.ready:
@@ -4559,7 +4591,7 @@ class Sequence(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
-        if tag_only:
+        if tag_only:  # pragma: no cover
             return
         lenindef = False
         ctx_bered = ctx.get("bered", False)