[Pkg-javascript-commits] [node-asn1.js] 24/202: api: partial decode

Bastien Roucariès rouca at moszumanska.debian.org
Thu Apr 20 19:18:50 UTC 2017


This is an automated email from the git hooks/post-receive script.

rouca pushed a commit to branch master
in repository node-asn1.js.

commit 726e22ca44cd43133dcc00b72606fd6fb4bf3b23
Author: Fedor Indutny <fedor.indutny at gmail.com>
Date:   Sat Dec 7 20:53:12 2013 +0400

    api: partial decode
---
 lib/asn1/api.js           |   4 +-
 lib/asn1/base/buffer.js   |   8 +-
 lib/asn1/base/node.js     |  31 ++++++--
 lib/asn1/base/reporter.js |  30 +++++--
 lib/asn1/decoders/der.js  |  37 +++++----
 test/encode-error-test.js |  82 -------------------
 test/error-test.js        | 199 ++++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 273 insertions(+), 118 deletions(-)

diff --git a/lib/asn1/api.js b/lib/asn1/api.js
index 5812982..7468edc 100644
--- a/lib/asn1/api.js
+++ b/lib/asn1/api.js
@@ -28,11 +28,11 @@ Entity.prototype._createNamed = function createNamed(base) {
   return new named(this);
 };
 
-Entity.prototype.decode = function decode(data, enc) {
+Entity.prototype.decode = function decode(data, enc, options) {
   // Lazily create decoder
   if (!this.decoders.hasOwnProperty(enc))
     this.decoders[enc] = this._createNamed(asn1.decoders[enc]);
-  return this.decoders[enc].decode(data);
+  return this.decoders[enc].decode(data, options);
 };
 
 Entity.prototype.encode = function encode(data, enc, /* internal */ reporter) {
diff --git a/lib/asn1/base/buffer.js b/lib/asn1/base/buffer.js
index 578dbbb..524b545 100644
--- a/lib/asn1/base/buffer.js
+++ b/lib/asn1/base/buffer.js
@@ -36,16 +36,16 @@ DecoderBuffer.prototype.isEmpty = function isEmpty() {
   return this.offset === this.length;
 };
 
-DecoderBuffer.prototype.readUInt8 = function readUInt8() {
+DecoderBuffer.prototype.readUInt8 = function readUInt8(fail) {
   if (this.offset + 1 <= this.length)
     return this.base.readUInt8(this.offset++, true);
   else
-    return this.error('DecoderBuffer overrun');
+    return this.error(fail || 'DecoderBuffer overrun');
 }
 
-DecoderBuffer.prototype.skip = function skip(bytes) {
+DecoderBuffer.prototype.skip = function skip(bytes, fail) {
   if (!(this.offset + bytes <= this.length))
-    return this.error('DecoderBuffer overrun');
+    return this.error(fail || 'DecoderBuffer overrun');
 
   var res = new DecoderBuffer(this.base);
 
diff --git a/lib/asn1/base/node.js b/lib/asn1/base/node.js
index d600d58..cb18394 100644
--- a/lib/asn1/base/node.js
+++ b/lib/asn1/base/node.js
@@ -242,7 +242,7 @@ Node.prototype._decode = function decode(input) {
 
   // Decode root node
   if (state.parent === null)
-    return state.children[0]._decode(input);
+    return input.wrapResult(state.children[0]._decode(input));
 
   var result = state['default'];
   var present = true;
@@ -259,6 +259,8 @@ Node.prototype._decode = function decode(input) {
           state.implicit !== null ? state.implicit :
               state.tag || 0
     );
+    if (input.isError(present))
+      return present;
   }
 
   // Push object on stack
@@ -268,16 +270,23 @@ Node.prototype._decode = function decode(input) {
 
   if (present) {
     // Unwrap explicit values
-    if (state.explicit !== null)
-      input = this._decodeTag(input, state.explicit);
+    if (state.explicit !== null) {
+      var explicit = this._decodeTag(input, state.explicit);
+      if (input.isError(explicit))
+        return explicit;
+      input = explicit;
+    }
 
     // Unwrap implicit and normal values
     if (state.use === null && !state.any && state.choice === null) {
-      input = this._decodeTag(
+      var body = this._decodeTag(
         input,
         state.implicit !== null ? state.implicit : state.tag,
         state.any
       );
+      if (input.isError(body))
+        return body;
+      input = body;
     }
 
     // Select proper method for tag
@@ -287,12 +296,18 @@ Node.prototype._decode = function decode(input) {
       result = this._decodeGeneric(state.tag, input);
     else
       result = this._decodeChoice(input);
+    if (input.isError(result))
+      return result;
 
     // Decode children
     if (!state.any && state.choice === null && state.children !== null) {
-      state.children.forEach(function decodeChildren(child) {
+      var fail = state.children.some(function decodeChildren(child) {
+        // NOTE: We are ignoring errors here, to let parser continue with other
+        // parts of encoded data
         child._decode(input);
       });
+      if (fail)
+        return err;
     }
   }
 
@@ -345,7 +360,11 @@ Node.prototype._decodeChoice = function decodeChoice(input) {
     var save = input.save();
     var node = state.choice[key];
     try {
-      result = { type: key, value: node._decode(input) };
+      var value = node._decode(input);
+      if (input.isError(value))
+        return false;
+
+      result = { type: key, value: value };
       match = true;
     } catch (e) {
       input.restore(save);
diff --git a/lib/asn1/base/reporter.js b/lib/asn1/base/reporter.js
index 746ccaa..605ae4e 100644
--- a/lib/asn1/base/reporter.js
+++ b/lib/asn1/base/reporter.js
@@ -1,7 +1,5 @@
 var util = require('util');
 
-var errRef = {};
-
 function Reporter(options) {
   this._reporterState = {
     obj: null,
@@ -13,7 +11,7 @@ function Reporter(options) {
 exports.Reporter = Reporter;
 
 Reporter.prototype.isError = function isError(obj) {
-  return obj === errRef;
+  return obj instanceof ReporterError;
 };
 
 Reporter.prototype.enterKey = function enterKey(key) {
@@ -63,13 +61,29 @@ Reporter.prototype.error = function error(msg) {
   if (!inherited)
     state.errors.push(err);
 
-  return errRef;
+  return err;
 };
 
-function ReporterError(path, msg, stack) {
-  var message = 'Error at object path: ' + path + '\n' + msg;
-  Error.call(this, message);
+Reporter.prototype.wrapResult = function wrapResult(result) {
+  var state = this._reporterState;
+  if (!state.options.partial)
+    return result;
 
-  this.stack = stack || new Error(message).stack;
+  return {
+    result: this.isError(result) ? null : result,
+    errors: state.errors
+  };
+};
+
+function ReporterError(path, msg) {
+  this.path = path;
+  this.rethrow(msg);
 };
 util.inherits(ReporterError, Error);
+
+ReporterError.prototype.rethrow = function rethrow(msg) {
+  this.message = msg + ' at: ' + (this.path || '(shallow)');
+  Error.captureStackTrace(this, ReporterError);
+
+  return this;
+};
diff --git a/lib/asn1/decoders/der.js b/lib/asn1/decoders/der.js
index 593eafe..5699c6d 100644
--- a/lib/asn1/decoders/der.js
+++ b/lib/asn1/decoders/der.js
@@ -36,7 +36,7 @@ DERNode.prototype._peekTag = function peekTag(buffer, tag) {
     return false;
 
   var state = buffer.save();
-  var decodedTag = derDecodeTag(buffer);
+  var decodedTag = derDecodeTag(buffer, 'Failed to peek tag: "' + tag + '"');
   if (buffer.isError(decodedTag))
     return decodedTag;
 
@@ -46,11 +46,14 @@ DERNode.prototype._peekTag = function peekTag(buffer, tag) {
 };
 
 DERNode.prototype._decodeTag = function decodeTag(buffer, tag, any) {
-  var decodedTag = derDecodeTag(buffer);
+  var decodedTag = derDecodeTag(buffer,
+                                'Failed to decode tag of "' + tag + '"');
   if (buffer.isError(decodedTag))
     return decodedTag;
 
-  var len = derDecodeLen(buffer, decodedTag.primitive);
+  var len = derDecodeLen(buffer,
+                         decodedTag.primitive,
+                         'Failed to get length of "' + tag + '"');
 
   // Failure
   if (buffer.isError(len))
@@ -60,27 +63,29 @@ DERNode.prototype._decodeTag = function decodeTag(buffer, tag, any) {
       decodedTag.tag !== tag &&
       decodedTag.tagStr !== tag &&
       decodedTag.tagStr + 'of' !== tag) {
-    return buffer.error('Failed to match tag: ' + tag);
+    return buffer.error('Failed to match tag: "' + tag + '"');
   }
 
   if (decodedTag.primitive || len !== null)
-    return buffer.skip(len);
+    return buffer.skip(len, 'Failed to match body of: "' + tag + '"');
 
   // Indefinite length... find END tag
   var state = buffer.save();
-  var res = this._skipUntilEnd(buffer);
+  var res = this._skipUntilEnd(
+      buffer,
+      'Failed to skip indefinite length body: "' + this.tag + '"');
   if (buffer.isError(res))
     return res;
 
   return buffer.restore(state);
 };
 
-DERNode.prototype._skipUntilEnd = function skipUntilEnd(buffer) {
+DERNode.prototype._skipUntilEnd = function skipUntilEnd(buffer, fail) {
   while (true) {
-    var tag = derDecodeTag(buffer);
+    var tag = derDecodeTag(buffer, fail);
     if (buffer.isError(tag))
       return tag;
-    var len = derDecodeLen(buffer, tag.primitive);
+    var len = derDecodeLen(buffer, tag.primitive, fail);
     if (buffer.isError(len))
       return len;
 
@@ -88,7 +93,7 @@ DERNode.prototype._skipUntilEnd = function skipUntilEnd(buffer) {
     if (tag.primitive || len !== null)
       res = buffer.skip(len)
     else
-      res = this._skipUntilEnd(buffer);
+      res = this._skipUntilEnd(buffer, fail);
 
     // Failure
     if (buffer.isError(res))
@@ -212,8 +217,8 @@ DERNode.prototype._use = function use(buffer, decoder) {
 
 // Utility methods
 
-function derDecodeTag(buf) {
-  var tag = buf.readUInt8();
+function derDecodeTag(buf, fail) {
+  var tag = buf.readUInt8(fail);
   if (buf.isError(tag))
     return tag;
 
@@ -225,7 +230,7 @@ function derDecodeTag(buf) {
     var oct = tag;
     tag = 0;
     while ((oct & 0x80) === 0x80) {
-      oct = buf.readUInt8();
+      oct = buf.readUInt8(fail);
       if (buf.isError(oct))
         return oct;
 
@@ -245,8 +250,8 @@ function derDecodeTag(buf) {
   };
 }
 
-function derDecodeLen(buf, primitive) {
-  var len = buf.readUInt8();
+function derDecodeLen(buf, primitive, fail) {
+  var len = buf.readUInt8(fail);
   if (buf.isError(len))
     return len;
 
@@ -268,7 +273,7 @@ function derDecodeLen(buf, primitive) {
   len = 0;
   for (var i = 0; i < num; i++) {
     len <<= 8;
-    var j = buf.readUInt8();
+    var j = buf.readUInt8(fail);
     if (buf.isError(j))
       return j;
     len |= j;
diff --git a/test/encode-error-test.js b/test/encode-error-test.js
deleted file mode 100644
index 7d1c5f8..0000000
--- a/test/encode-error-test.js
+++ /dev/null
@@ -1,82 +0,0 @@
-var assert = require('assert');
-var asn1 = require('..');
-
-var Buffer = require('buffer').Buffer;
-
-describe('asn1.js encode error', function() {
-  function test(name, model, input, expected) {
-    it('should support ' + name, function() {
-      var M = asn1.define('TestModel', model);
-
-      var error;
-      assert.throws(function() {
-        try {
-          var encoded = M.encode(input, 'der');
-        } catch (e) {
-          error = e;
-          throw e;
-        }
-      });
-
-      assert(expected.test(error.stack),
-             'Failed to match, expected: ' + expected + ' got: ' +
-                 JSON.stringify(error.stack));
-    });
-  }
-
-  describe('primitives', function() {
-    test('int', function() {
-      this.int();
-    }, 'hello', /no values map/i);
-
-    test('enum', function() {
-      this.enum({ 0: 'hello', 1: 'world' });
-    }, 'gosh', /contain: "gosh"/);
-
-    test('objid', function() {
-      this.objid();
-    }, 1, /objid\(\) should be either array or string, got: 1/);
-  });
-
-  describe('composite', function() {
-    test('shallow', function() {
-      this.seq().obj(
-        this.key('key').int()
-      );
-    }, { key: 'hello' } , /object path: \["key"\]/i);
-
-    test('deep and empty', function() {
-      this.seq().obj(
-        this.key('a').seq().obj(
-          this.key('b').seq().obj(
-            this.key('c').int()
-          )
-        )
-      );
-    }, { } , /object path: \["a"\]\["b"\]/i);
-
-    test('deep', function() {
-      this.seq().obj(
-        this.key('a').seq().obj(
-          this.key('b').seq().obj(
-            this.key('c').int()
-          )
-        )
-      );
-    }, { a: { b: { c: 'hello' } } } , /object path: \["a"\]\["b"\]\["c"\]/i);
-
-    test('use', function() {
-      var S = asn1.define('S', function() {
-        this.seq().obj(
-          this.key('x').int()
-        );
-      });
-
-      this.seq().obj(
-        this.key('a').seq().obj(
-          this.key('b').use(S)
-        )
-      );
-    }, { a: { b: { x: 'hello' } } } , /object path: \["a"\]\["b"\]\["x"\]/i);
-  });
-});
diff --git a/test/error-test.js b/test/error-test.js
new file mode 100644
index 0000000..3535482
--- /dev/null
+++ b/test/error-test.js
@@ -0,0 +1,199 @@
+var assert = require('assert');
+var asn1 = require('..');
+
+var Buffer = require('buffer').Buffer;
+
+describe('asn1.js error', function() {
+  describe('encoder', function() {
+    function test(name, model, input, expected) {
+      it('should support ' + name, function() {
+        var M = asn1.define('TestModel', model);
+
+        var error;
+        assert.throws(function() {
+          try {
+            var encoded = M.encode(input, 'der');
+          } catch (e) {
+            error = e;
+            throw e;
+          }
+        });
+
+        assert(expected.test(error.stack),
+               'Failed to match, expected: ' + expected + ' got: ' +
+                   JSON.stringify(error.stack));
+      });
+    }
+
+    describe('primitives', function() {
+      test('int', function() {
+        this.int();
+      }, 'hello', /no values map/i);
+
+      test('enum', function() {
+        this.enum({ 0: 'hello', 1: 'world' });
+      }, 'gosh', /contain: "gosh"/);
+
+      test('objid', function() {
+        this.objid();
+      }, 1, /objid\(\) should be either array or string, got: 1/);
+    });
+
+    describe('composite', function() {
+      test('shallow', function() {
+        this.seq().obj(
+          this.key('key').int()
+        );
+      }, { key: 'hello' } , /map at: \["key"\]/i);
+
+      test('deep and empty', function() {
+        this.seq().obj(
+          this.key('a').seq().obj(
+            this.key('b').seq().obj(
+              this.key('c').int()
+            )
+          )
+        );
+      }, { } , /input is not object at: \["a"\]\["b"\]/i);
+
+      test('deep', function() {
+        this.seq().obj(
+          this.key('a').seq().obj(
+            this.key('b').seq().obj(
+              this.key('c').int()
+            )
+          )
+        );
+      }, { a: { b: { c: 'hello' } } } , /map at: \["a"\]\["b"\]\["c"\]/i);
+
+      test('use', function() {
+        var S = asn1.define('S', function() {
+          this.seq().obj(
+            this.key('x').int()
+          );
+        });
+
+        this.seq().obj(
+          this.key('a').seq().obj(
+            this.key('b').use(S)
+          )
+        );
+      }, { a: { b: { x: 'hello' } } } , /map at: \["a"\]\["b"\]\["x"\]/i);
+    });
+  });
+
+  describe('decoder', function() {
+    function test(name, model, input, expected) {
+      it('should support ' + name, function() {
+        var M = asn1.define('TestModel', model);
+
+        var error;
+        assert.throws(function() {
+          try {
+            var decoded = M.decode(new Buffer(input, 'hex'), 'der');
+          } catch (e) {
+            error = e;
+            throw e;
+          }
+        });
+        var partial = M.decode(new Buffer(input, 'hex'), 'der', {
+          partial: true
+        });
+
+        assert(expected.test(error.stack),
+               'Failed to match, expected: ' + expected + ' got: ' +
+                   JSON.stringify(error.stack));
+
+        assert.equal(partial.errors.length, 1);
+        assert(expected.test(partial.errors[0].stack),
+               'Failed to match, expected: ' + expected + ' got: ' +
+                   JSON.stringify(partial.errors[0].stack));
+      });
+    }
+
+    describe('primitive', function() {
+      test('int', function() {
+        this.int();
+      }, '2201', /body of: "int"/);
+
+      test('int', function() {
+        this.int();
+      }, '', /tag of "int"/);
+    });
+
+    describe('composite', function() {
+      test('shallow', function() {
+        this.seq().obj(
+          this.key('a').seq().obj()
+        );
+      }, '30', /length of "seq"/);
+
+      test('deep and empty', function() {
+        this.seq().obj(
+          this.key('a').seq().obj(
+            this.key('b').seq().obj(
+              this.key('c').int()
+            )
+          )
+        );
+      }, '300430023000', /tag of "int" at: \["a"\]\["b"\]\["c"\]/);
+
+      test('deep and incomplete', function() {
+        this.seq().obj(
+          this.key('a').seq().obj(
+            this.key('b').seq().obj(
+              this.key('c').int()
+            )
+          )
+        );
+      }, '30053003300122', /length of "int" at: \["a"\]\["b"\]\["c"\]/);
+    });
+  });
+
+  describe('partial decoder', function() {
+    function test(name, model, input, expectedObj, expectedErrs) {
+      it('should support ' + name, function() {
+        var M = asn1.define('TestModel', model);
+
+        var decoded = M.decode(new Buffer(input, 'hex'), 'der', {
+          partial: true
+        });
+
+        assert.deepEqual(decoded.result, expectedObj);
+
+        assert.equal(decoded.errors.length, expectedErrs.length);
+        expectedErrs.forEach(function(expected, i) {
+          assert(expected.test(decoded.errors[i].stack),
+                 'Failed to match, expected: ' + expected + ' got: ' +
+                     JSON.stringify(decoded.errors[i].stack));
+        });
+      });
+    }
+
+    test('last key not present', function() {
+      this.seq().obj(
+        this.key('a').seq().obj(
+          this.key('b').seq().obj(
+            this.key('c').int()
+          ),
+          this.key('d').int()
+        )
+      );
+    }, '30073005300022012e', { a: { b: {}, d: 46 } }, [
+      /"int" at: \["a"\]\["b"\]\["c"\]/
+    ]);
+
+    test('first key not present', function() {
+      this.seq().obj(
+        this.key('a').seq().obj(
+          this.key('b').seq().obj(
+            this.key('c').int()
+          ),
+          this.key('d').int()
+        )
+      );
+    }, '30073005300322012e', { a: { b: { c: 46 } } }, [
+      /"int" at: \["a"\]\["d"\]/
+    ]);
+  });
+});

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-asn1.js.git



More information about the Pkg-javascript-commits mailing list