diff --git a/lib/client.js b/lib/client.js index a239190..1706675 100644 --- a/lib/client.js +++ b/lib/client.js @@ -8,7 +8,7 @@ var util = require('util'); var Attribute = require('./attribute'); var Change = require('./change'); -var Control = require('./control'); +var Control = require('./controls/index').Control; var Protocol = require('./protocol'); var dn = require('./dn'); var errors = require('./errors'); diff --git a/lib/control.js b/lib/controls/control.js similarity index 75% rename from lib/control.js rename to lib/controls/control.js index 778b847..808c967 100644 --- a/lib/control.js +++ b/lib/controls/control.js @@ -5,16 +5,11 @@ var util = require('util'); var asn1 = require('asn1'); -var Protocol = require('./protocol'); - - +var Protocol = require('../protocol'); ///--- Globals - var Ber = asn1.Ber; - - ///--- API function Control(options) { @@ -47,35 +42,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/controls/index.js b/lib/controls/index.js new file mode 100644 index 0000000..9c7d42c --- /dev/null +++ b/lib/controls/index.js @@ -0,0 +1,40 @@ +var assert = require('assert'); + +var Control = require('./control'); +var PersistentSearchControl = require('./persistent_search_control'); + +var OID_PERSISTENT_SEARCH_CONTROL = '2.16.840.1.113730.3.4.3'; + +///--- API + +module.exports = { + 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); + } + } + } + }, + + Control: Control, + PersistentSearchControl: PersistentSearchControl +}; diff --git a/lib/controls/persistent_search_control.js b/lib/controls/persistent_search_control.js new file mode 100644 index 0000000..a2cf43e --- /dev/null +++ b/lib/controls/persistent_search_control.js @@ -0,0 +1,109 @@ +var assert = require('assert'); +var asn1 = require('asn1'); +var buffer = require('buffer'); +var Control = require('./control'); +var util = require('util'); + +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 && !Buffer.isBuffer(options.value)) + throw new TypeError('options.value must be a buffer'); + } else { + options = {}; + } + + this.type = options.type || '2.16.840.1.113730.3.4.3'; + this.criticality = options.criticality || false; + + if (options.value) { + // parse out this.value into the PSC object + var ber = new asn1.BerReader(options.value); + if (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 asn1.BerWriter(); + + // 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/lib/index.js b/lib/index.js index a3cb425..71585a7 100644 --- a/lib/index.js +++ b/lib/index.js @@ -3,10 +3,10 @@ var Client = require('./client'); var Attribute = require('./attribute'); var Change = require('./change'); -var Control = require('./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'); @@ -15,8 +15,6 @@ var messages = require('./messages'); var schema = require('./schema'); var url = require('./url'); - - /// Hack a few things we need (i.e., "monkey patch" the prototype) if (!String.prototype.startsWith) { @@ -55,7 +53,6 @@ module.exports = { Attribute: Attribute, Change: Change, - Control: Control, DN: dn.DN, RDN: dn.RDN, diff --git a/lib/messages/message.js b/lib/messages/message.js index 26ab987..031904b 100644 --- a/lib/messages/message.js +++ b/lib/messages/message.js @@ -5,7 +5,7 @@ var util = require('util'); var asn1 = require('asn1'); -var Control = require('../control'); +var Control = require('../controls/index').Control; var Protocol = require('../protocol'); var logStub = require('../log_stub'); @@ -15,11 +15,11 @@ var logStub = require('../log_stub'); var Ber = asn1.Ber; var BerReader = asn1.BerReader; var BerWriter = asn1.BerWriter; - - +var getControl = require('../controls/index').getControl; ///--- API + /** * LDAPMessage structure. * @@ -76,8 +76,9 @@ 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 c = getControl(ber); + if (c) this.controls.push(c); } } diff --git a/tst/control.test.js b/tst/controls/control.test.js similarity index 81% rename from tst/control.test.js rename to tst/controls/control.test.js index 5b82124..3edbf4f 100644 --- a/tst/control.test.js +++ b/tst/controls/control.test.js @@ -10,13 +10,16 @@ var asn1 = require('asn1'); var BerReader = asn1.BerReader; var BerWriter = asn1.BerWriter; var Control; +var getControl; ///--- Tests test('load library', function(t) { - Control = require('../lib/index').Control; + Control = require('../../lib/controls/index').Control; t.ok(Control); + getControl = require('../../lib/controls/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/controls/persistent_search_control.test.js b/tst/controls/persistent_search_control.test.js new file mode 100644 index 0000000..6341e54 --- /dev/null +++ b/tst/controls/persistent_search_control.test.js @@ -0,0 +1,124 @@ +// 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/controls/index').PersistentSearchControl; + t.ok(PersistentSearchControl); + getControl = require('../../lib/controls/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(); +});