]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - pyderasn.py
Add PEP-396 compatible module's __version__
[pyderasn.git] / pyderasn.py
index 1c5b53f1f617810c1e8c3cb906710a282cf23126..0b04f4eda76be9921b6de95d90e54d6c297e6e16 100755 (executable)
@@ -1,12 +1,11 @@
 #!/usr/bin/env python
 # coding: utf-8
 # PyDERASN -- Python ASN.1 DER/BER codec with abstract structures
-# Copyright (C) 2017-2019 Sergey Matveev <stargrave@stargrave.org>
+# Copyright (C) 2017-2020 Sergey Matveev <stargrave@stargrave.org>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
+# published by the Free Software Foundation, version 3 of the License.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -14,8 +13,7 @@
 # GNU Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
-# License along with this program.  If not, see
-# <http://www.gnu.org/licenses/>.
+# License along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """Python ASN.1 DER/BER codec with abstract structures
 
 This library allows you to marshal various structures in ASN.1 DER
@@ -348,6 +346,8 @@ DEFINED BY some previously met ObjectIdentifier. This library provides
 ability to specify mapping between some OID and field that must be
 decoded with specific specification.
 
+.. _defines:
+
 defines kwarg
 _____________
 
@@ -421,15 +421,15 @@ value must be sequence of following tuples::
 
 where ``decode_path`` is a tuple holding so-called decode path to the
 exact :py:class:`pyderasn.ObjectIdentifier` field you want to apply
-``defines``, holding exactly the same value as accepted in its keyword
-argument.
+``defines``, holding exactly the same value as accepted in its
+:ref:`keyword argument <defines>`.
 
 For example, again for CMS, you want to automatically decode
 ``SignedData`` and CMC's (:rfc:`5272`) ``PKIData`` and ``PKIResponse``
 structures it may hold. Also, automatically decode ``controlSequence``
 of ``PKIResponse``::
 
-    content_info, tail = ContentInfo().decode(data, defines_by_path=(
+    content_info, tail = ContentInfo().decode(data, ctx={"defines_by_path": (
         (
             ("contentType",),
             ((("content",), {id_signedData: SignedData()}),),
@@ -464,7 +464,7 @@ of ``PKIResponse``::
                 id_cmc_transactionId: TransactionId(),
             })),
         ),
-    ))
+    )})
 
 Pay attention for :py:class:`pyderasn.DecodePathDefBy` and ``any``.
 First function is useful for path construction when some automatic
@@ -670,9 +670,10 @@ from six.moves import xrange as six_xrange
 try:
     from termcolor import colored
 except ImportError:  # pragma: no cover
-    def colored(what, *args):
+    def colored(what, *args, **kwargs):
         return what
 
+__version__ = "5.5"
 
 __all__ = (
     "Any",
@@ -1435,7 +1436,7 @@ def colonize_hex(hexed):
 
 def pp_console_row(
         pp,
-        oids=None,
+        oid_maps=(),
         with_offsets=False,
         with_blob=True,
         with_colours=False,
@@ -1470,14 +1471,18 @@ def pp_console_row(
         if isinstance(ent, DecodePathDefBy):
             cols.append(_colourize("DEFINED BY", "red", with_colours, ("reverse",)))
             value = str(ent.defined_by)
+            oid_name = None
             if (
-                    oids is not None and
+                    len(oid_maps) > 0 and
                     ent.defined_by.asn1_type_name ==
-                    ObjectIdentifier.asn1_type_name and
-                    value in oids
+                    ObjectIdentifier.asn1_type_name
             ):
-                cols.append(_colourize("%s:" % oids[value], "green", with_colours))
-            else:
+                for oid_map in oid_maps:
+                    oid_name = oid_map.get(value)
+                    if oid_name is not None:
+                        cols.append(_colourize("%s:" % oid_name, "green", with_colours))
+                        break
+            if oid_name is None:
                 cols.append(_colourize("%s:" % value, "white", with_colours, ("reverse",)))
         else:
             cols.append(_colourize("%s:" % ent, "yellow", with_colours, ("reverse",)))
@@ -1498,11 +1503,14 @@ def pp_console_row(
         value = pp.value
         cols.append(_colourize(value, "white", with_colours, ("reverse",)))
         if (
-                oids is not None and
-                pp.asn1_type_name == ObjectIdentifier.asn1_type_name and
-                value in oids
+                len(oid_maps) > 0 and
+                pp.asn1_type_name == ObjectIdentifier.asn1_type_name
         ):
-            cols.append(_colourize("(%s)" % oids[value], "green", with_colours))
+            for oid_map in oid_maps:
+                oid_name = oid_map.get(value)
+                if oid_name is not None:
+                    cols.append(_colourize("(%s)" % oid_name, "green", with_colours))
+                    break
         if pp.asn1_type_name == Integer.asn1_type_name:
             hex_repr = hex(int(pp.obj._value))[2:].upper()
             if len(hex_repr) % 2 != 0:
@@ -1546,7 +1554,7 @@ def pp_console_blob(pp, decode_path_len_decrease=0):
 
 def pprint(
         obj,
-        oids=None,
+        oid_maps=(),
         big_blobs=False,
         with_colours=False,
         with_decode_path=False,
@@ -1555,8 +1563,9 @@ def pprint(
     """Pretty print object
 
     :param Obj obj: object you want to pretty print
-    :param oids: ``OID <-> humand readable string`` dictionary. When OID
-                 from it is met, then its humand readable form is printed
+    :param oid_maps: list of ``OID <-> humand readable string`` dictionary.
+                     When OID from it is met, then its humand readable form
+                     is printed
     :param big_blobs: if large binary objects are met (like OctetString
                       values), do we need to print them too, on separate
                       lines
@@ -1578,7 +1587,7 @@ def pprint(
                 if big_blobs:
                     yield pp_console_row(
                         pp,
-                        oids=oids,
+                        oid_maps=oid_maps,
                         with_offsets=True,
                         with_blob=False,
                         with_colours=with_colours,
@@ -1593,7 +1602,7 @@ def pprint(
                 else:
                     yield pp_console_row(
                         pp,
-                        oids=oids,
+                        oid_maps=oid_maps,
                         with_offsets=True,
                         with_blob=True,
                         with_colours=with_colours,
@@ -3832,7 +3841,7 @@ class UTCTime(CommonString):
             try:
                 value_decoded = value.decode("ascii")
             except (UnicodeEncodeError, UnicodeDecodeError) as err:
-                raise DecodeError("invalid UTCTime encoding")
+                raise DecodeError("invalid UTCTime encoding: %r" % err)
             try:
                 self._strptime(value_decoded)
             except (TypeError, ValueError) as err:
@@ -3977,7 +3986,7 @@ class GeneralizedTime(UTCTime):
             try:
                 value_decoded = value.decode("ascii")
             except (UnicodeEncodeError, UnicodeDecodeError) as err:
-                raise DecodeError("invalid GeneralizedTime encoding")
+                raise DecodeError("invalid GeneralizedTime encoding: %r" % err)
             try:
                 self._strptime(value_decoded)
             except (TypeError, ValueError) as err:
@@ -4877,8 +4886,8 @@ class Sequence(Obj):
                     ctx=ctx,
                     _ctx_immutable=False,
                 )
-            except TagMismatch:
-                if spec.optional:
+            except TagMismatch as err:
+                if (len(err.decode_path) == len(decode_path) + 1) and spec.optional:
                     continue
                 raise
 
@@ -5604,7 +5613,7 @@ def generic_decoder():  # pragma: no cover
 
     def pprint_any(
             obj,
-            oids=None,
+            oid_maps=(),
             with_colours=False,
             with_decode_path=False,
             decode_path_only=(),
@@ -5624,7 +5633,7 @@ def generic_decoder():  # pragma: no cover
                     pp = _pp(**pp_kwargs)
                     yield pp_console_row(
                         pp,
-                        oids=oids,
+                        oid_maps=oid_maps,
                         with_offsets=True,
                         with_blob=False,
                         with_colours=with_colours,
@@ -5654,7 +5663,7 @@ def main():  # pragma: no cover
     )
     parser.add_argument(
         "--oids",
-        help="Python path to dictionary with OIDs",
+        help="Python paths to dictionary with OIDs, comma separated",
     )
     parser.add_argument(
         "--schema",
@@ -5692,7 +5701,10 @@ def main():  # pragma: no cover
     args.DERFile.seek(args.skip)
     der = memoryview(args.DERFile.read())
     args.DERFile.close()
-    oids = obj_by_path(args.oids) if args.oids else {}
+    oid_maps = (
+        [obj_by_path(_path) for _path in (args.oids or "").split(",")]
+        if args.oids else ()
+    )
     if args.schema:
         schema = obj_by_path(args.schema)
         from functools import partial
@@ -5708,7 +5720,7 @@ def main():  # pragma: no cover
     obj, tail = schema().decode(der, ctx=ctx)
     print(pprinter(
         obj,
-        oids=oids,
+        oid_maps=oid_maps,
         with_colours=True if environ.get("NO_COLOR") is None else False,
         with_decode_path=args.print_decode_path,
         decode_path_only=(