From ee88fc70196fc1e99ef8bbd2e185302947bd0d9e Mon Sep 17 00:00:00 2001 From: Yunong Xiao Date: Wed, 7 Dec 2011 17:02:17 -0800 Subject: [PATCH] "added ability to deserialize ldap persistent search controls" --- lib/control.js | 32 +------ lib/index.js | 35 +++++++- lib/messages/message.js | 9 +- lib/persistent_search_control.js | 114 ++++++++++++++++++++++++ package.json | 1 + tst/control.test.js | 7 +- tst/persistent_search_control.test.js | 123 ++++++++++++++++++++++++++ 7 files changed, 286 insertions(+), 35 deletions(-) create mode 100644 lib/persistent_search_control.js create mode 100644 tst/persistent_search_control.test.js diff --git a/lib/control.js b/lib/control.js index 778b847..295e997 100644 --- a/lib/control.js +++ b/lib/control.js @@ -5,15 +5,16 @@ var util = require('util'); var asn1 = require('asn1'); +var PersistentSearchControl = require('./persistent_search_control'); var Protocol = require('./protocol'); - +var log4js = require('log4js'); ///--- Globals - +var LOG = log4js.getLogger('control.js'); var Ber = asn1.Ber; - +var OID_PERSISTENT_SEARCH_CONTROL = '2.16.840.1.113730.3.4.3'; ///--- API @@ -47,35 +48,10 @@ function Control(options) { } module.exports = Control; - Control.prototype.toString = function() { return this.json; }; - -Control.prototype.parse = function(ber) { - assert.ok(ber); - - if (ber.readSequence() === null) - return false; - - var end = ber.offset + ber.length; - - if (ber.length) { - this.type = ber.readString(); - if (ber.offset < end) { - if (ber.peek() === 0x01) // Boolean, optional - this.criticality = ber.readBoolean(); - } - - if (ber.offset < end) - this.value = ber.readString(); - } - - return true; -}; - - Control.prototype.toBer = function(ber) { assert.ok(ber); diff --git a/lib/index.js b/lib/index.js index a3cb425..9052146 100644 --- a/lib/index.js +++ b/lib/index.js @@ -4,18 +4,22 @@ var Client = require('./client'); var Attribute = require('./attribute'); var Change = require('./change'); var Control = require('./control'); +var PersistentSearchControl = require('./persistent_search_control'); var Protocol = require('./protocol'); var Server = require('./server'); +var assert = require('assert'); var dn = require('./dn'); var errors = require('./errors'); var filters = require('./filters'); +var log4js = require('log4js'); var logStub = require('./log_stub'); var messages = require('./messages'); var schema = require('./schema'); var url = require('./url'); - +var LOG = log4js.getLogger(this); +var OID_PERSISTENT_SEARCH_CONTROL = '2.16.840.1.113730.3.4.3'; /// Hack a few things we need (i.e., "monkey patch" the prototype) @@ -57,6 +61,35 @@ module.exports = { Change: Change, Control: Control, + getControl: function(ber) { + assert.ok(ber); + + if (ber.readSequence() === null) + return; + + var end = ber.offset + ber.length; + var options = {}; + if (ber.length) { + options.type = ber.readString(); + if (ber.offset < end) { + if (ber.peek() === 0x01) // Boolean, optional + options.criticality = ber.readBoolean(); + } + if (ber.offset < end) { + if (options.type == OID_PERSISTENT_SEARCH_CONTROL) { + // send the buffer directly to the PSC + options.value = ber.readString(0x04, true); + return new PersistentSearchControl(options); + } else { + options.value = ber.readString(); + return new Control(options); + } + } + } + }, + + PersistentSearchControl: PersistentSearchControl, + DN: dn.DN, RDN: dn.RDN, parseDN: dn.parse, diff --git a/lib/messages/message.js b/lib/messages/message.js index 26ab987..e0d62dc 100644 --- a/lib/messages/message.js +++ b/lib/messages/message.js @@ -8,6 +8,7 @@ var asn1 = require('asn1'); var Control = require('../control'); var Protocol = require('../protocol'); +var log4js = require('log4js'); var logStub = require('../log_stub'); ///--- Globals @@ -16,7 +17,7 @@ var Ber = asn1.Ber; var BerReader = asn1.BerReader; var BerWriter = asn1.BerWriter; - +var LOG = log4js.getLogger('Message'); ///--- API @@ -76,9 +77,11 @@ LDAPMessage.prototype.parse = function(ber) { ber.readSequence(); var end = ber.offset + ber.length; while (ber.offset < end) { - var c = new Control(); - if (c.parse(ber)) + var getControl = require('../index').getControl; + var c = getControl(ber); + if (c) { this.controls.push(c); + } } } diff --git a/lib/persistent_search_control.js b/lib/persistent_search_control.js new file mode 100644 index 0000000..c12c14b --- /dev/null +++ b/lib/persistent_search_control.js @@ -0,0 +1,114 @@ +var assert = require('assert'); +var asn1 = require('asn1'); +var buffer = require('buffer'); +var log4js = require('log4js'); +var Control = require('./control'); +var util = require('util'); +var Ber = asn1.Ber; + +var LOG = log4js.getLogger('PersistentSearchControl'); + +function PersistentSearchControl(options) { + if (options) { + if (typeof(options) !== 'object') + throw new TypeError('options must be an object'); + if (options.type && typeof(options.type) !== 'string') + throw new TypeError('options.type must be a string'); + if (options.criticality !== undefined && + typeof(options.criticality) !== 'boolean') + throw new TypeError('options.criticality must be a boolean'); + if (options.value && typeof(options.value) !== 'object') + throw new TypeError('options.value must be a buffer'); + } else { + options = {}; + } + + this.type = options.type || ''; + this.criticality = options.criticality || false; + this.value = options.value || undefined; + + if (this.value) { + // parse out this.value into the PSC object + var ber = new Ber.Reader(this.value); + ber.readSequence(); + this.value = { + changeTypes: ber.readInt(), + changesOnly: ber.readBoolean(), + returnECs: ber.readBoolean() + }; + } + + var self = this; + this.__defineGetter__('json', function() { + return { + controlType: self.type, + criticality: self.criticality, + controlValue: self.value + }; + }); + +} +module.exports = PersistentSearchControl; + +// returns a psc given a fully populated psc object +PersistentSearchControl.prototype.get = function(options) { + if (options) { + if (typeof(options) !== 'object') + throw new TypeError('options must be an object'); + if (options.type && typeof(options.type) !== 'string') + throw new TypeError('options.type must be a string'); + if (options.criticality !== undefined && + typeof(options.criticality) !== 'boolean') + throw new TypeError('options.criticality must be a boolean'); + if (options.value && typeof(options.value) !== 'object') { + throw new TypeError('options.value must be an object'); + } else { + if (options.value.changeTypes && + typeof(options.value.changeTypes) !== 'number') + throw new TypeError('options.value.changeTypes must be a number'); + if (options.value.changesOnly !== undefined && + typeof(options.value.changesOnly) !== 'boolean') + throw new TypeError('options.value.changesOnly must be a boolean'); + if (options.value.returnECs !== undefined && + typeof(options.value.returnECs) !== 'boolean') + throw new TypeError('options.value.returnECs must be a boolean'); + } + } else { + options = {}; + } + + this.type = options.type || ''; + this.criticality = options.criticality || false; + this.value = options.value || undefined; + + var self = this; + this.__defineGetter__('json', function() { + return { + controlType: self.type, + criticality: self.criticality, + controlValue: self.value + }; + }); +}; + +PersistentSearchControl.prototype.toBer = function(ber) { + assert.ok(ber); + + ber.startSequence(); + ber.writeString(this.type || ''); + ber.writeBoolean(this.criticality); + + var pscWriter = new Ber.Writer(); + + // write the value subsequence + pscWriter.startSequence(); + pscWriter.writeInt(this.value.changeTypes); + pscWriter.writeBoolean(this.value.changesOnly); + pscWriter.writeBoolean(this.value.returnECs); + pscWriter.endSequence(); + + // write the pscValue as a octetstring to the ber + ber.writeBuffer(pscWriter.buffer, 0x04); + + ber.endSequence(); +}; diff --git a/package.json b/package.json index 2930b6c..1168c9a 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "asn1": "0.1.8", "buffertools": "1.0.5", "dtrace-provider": "0.0.3", + "log4js": "0.4.1", "nopt": "1.0.10", "sprintf": "0.1.1" }, diff --git a/tst/control.test.js b/tst/control.test.js index 5b82124..e9276a4 100644 --- a/tst/control.test.js +++ b/tst/control.test.js @@ -10,6 +10,7 @@ var asn1 = require('asn1'); var BerReader = asn1.BerReader; var BerWriter = asn1.BerWriter; var Control; +var getControl; ///--- Tests @@ -17,6 +18,8 @@ var Control; test('load library', function(t) { Control = require('../lib/index').Control; t.ok(Control); + getControl = require('../lib/index').getControl; + t.ok(getControl); t.end(); }); @@ -47,9 +50,7 @@ test('parse', function(t) { ber.writeString('foo'); ber.endSequence(); - var c = new Control(); - t.ok(c); - t.ok(c.parse(new BerReader(ber.buffer))); + var c = getControl(new BerReader(ber.buffer)); t.ok(c); t.equal(c.type, '2.16.840.1.113730.3.4.2'); diff --git a/tst/persistent_search_control.test.js b/tst/persistent_search_control.test.js new file mode 100644 index 0000000..d8d1132 --- /dev/null +++ b/tst/persistent_search_control.test.js @@ -0,0 +1,123 @@ +// Copyright 2011 Mark Cavage, Inc. All rights reserved. + +var test = require('tap').test; + +var asn1 = require('asn1'); +var log4js = require('log4js'); +var sys = require('sys'); + +var BerReader = asn1.BerReader; +var BerWriter = asn1.BerWriter; +var getControl; +var PersistentSearchControl; + +///--- Globals +var LOG = log4js.getLogger('persistent_search_control.test'); + +///--- Tests + +test('load library', function(t) { + PersistentSearchControl = require('../lib/index').PersistentSearchControl; + t.ok(PersistentSearchControl); + getControl = require('../lib/index').getControl; + t.ok(getControl); + t.end(); +}); + + +test('new no args', function(t) { + t.ok(new PersistentSearchControl()); + t.end(); +}); + + +test('new with args', function(t) { + var options = { + type: '2.16.840.1.113730.3.4.3', + criticality: true, + value: { + changeTypes: 15, + changesOnly: false, + returnECs: false + } + }; + + var c = new PersistentSearchControl(); + c.get(options); + t.ok(c); + t.equal(c.type, '2.16.840.1.113730.3.4.3'); + t.ok(c.criticality); + + t.equal(c.value.changeTypes, 15); + t.equal(c.value.changesOnly, false); + t.equal(c.value.returnECs, false); + + + var writer = new BerWriter(); + c.toBer(writer); + var reader = new BerReader(writer.buffer); + var psc = getControl(reader); + t.ok(psc); + t.equal(psc.type, '2.16.840.1.113730.3.4.3'); + t.ok(psc.criticality); + t.equal(psc.value.changeTypes, 15); + t.equal(psc.value.changesOnly, false); + t.equal(psc.value.returnECs, false); + + t.end(); +}); + +test('getControl with args', function(t) { + var buf = new Buffer([ + 0x30, 0x26, 0x04, 0x17, 0x32, 0x2e, 0x31, 0x36, 0x2e, 0x38, 0x34, 0x30, + 0x2e, 0x31, 0x2e, 0x31, 0x31, 0x33, 0x37, 0x33, 0x30, 0x2e, 0x33, 0x2e, + 0x34, 0x2e, 0x33, 0x04, 0x0b, 0x30, 0x09, 0x02, 0x01, 0x0f, 0x01, 0x01, + 0xff, 0x01, 0x01, 0xff]); + + var options = { + type: '2.16.840.1.113730.3.4.3', + criticality: false, + value: { + changeTypes: 15, + changesOnly: true, + returnECs: true + } + }; + + var ber = new BerReader(buf); + var psc = getControl(ber); + LOG.info(psc.value); + t.ok(psc); + t.equal(psc.type, '2.16.840.1.113730.3.4.3'); + t.equal(psc.criticality, false); + t.equal(psc.value.changeTypes, 15); + t.equal(psc.value.changesOnly, true); + t.equal(psc.value.returnECs, true); + t.end(); +}); + +test('tober', function(t) { + var ber = new BerWriter(); + var options = { + type: '2.16.840.1.113730.3.4.3', + criticality: true, + value: { + changeTypes: 15, + changesOnly: false, + returnECs: false + } + }; + + var psc = new PersistentSearchControl(); + psc.get(options); + psc.toBer(ber); + + var c = getControl(new BerReader(ber.buffer)); + t.ok(c); + t.equal(c.type, '2.16.840.1.113730.3.4.3'); + t.ok(c.criticality); + t.equal(c.value.changeTypes, 15); + t.equal(c.value.changesOnly, false); + t.equal(c.value.returnECs, false); + t.end(); +});