diff --git a/lib/attribute.js b/lib/attribute.js index 5d9a453..d1ba081 100644 --- a/lib/attribute.js +++ b/lib/attribute.js @@ -2,6 +2,8 @@ var assert = require('assert'); +var asn1 = require('asn1'); + var Protocol = require('./protocol'); @@ -14,32 +16,69 @@ function Attribute(options) { 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.vals && !Array.isArray(options.vals)) - throw new TypeErrr('options.vals must be an array[string]'); - if (options.vals && options.vals.length) { - options.vals.forEach(function(v) { - if (typeof(v) !== 'string') - throw new TypeErrr('options.vals must be an array[string]'); - }); - } } else { options = {}; } - this.type = options.type || ''; - this.vals = options.vals ? options.vals.slice(0) : []; - var self = this; + + this.type = options.type || ''; + this._vals = []; + + this.__defineGetter__('vals', function() { + var _vals = []; + + self._vals.forEach(function(v) { + if (/;binary$/.test(self.type)) { + _vals.push(v.toString('base64')); + } else { + _vals.push(v.toString('utf8')); + } + }); + + return _vals; + }); + + this.__defineSetter__('vals', function(vals) { + if (Array.isArray(vals)) { + vals.forEach(function(v) { + self.addValue(v); + }); + } else { + self.addValue(vals); + } + }); + + this.__defineGetter__('buffers', function() { + return self._vals; + }); + this.__defineGetter__('json', function() { return { type: self.type, vals: self.vals }; }); + + if (options.vals) + this.vals = options.vals; + } module.exports = Attribute; +Attribute.prototype.addValue = function(val) { + if (Buffer.isBuffer(val)) { + this._vals.push(val); + } else { + var encoding = 'utf8'; + if (/;binary$/.test(this.type)) + encoding = 'base64'; + this._vals.push(new Buffer(val + '', encoding)); + } +}; + + Attribute.compare = function(a, b) { if (!(a instanceof Attribute) || !(b instanceof Attribute)) throw new TypeError('can only compare Attributes'); @@ -57,13 +96,6 @@ Attribute.compare = function(a, b) { return 0; }; -Attribute.prototype.addValue = function(val) { - if (typeof(val) !== 'string') - throw new TypeError('val (string) required'); - - this.vals.push(val); -}; - Attribute.prototype.parse = function(ber) { assert.ok(ber); @@ -75,7 +107,7 @@ Attribute.prototype.parse = function(ber) { if (ber.readSequence(Protocol.LBER_SET)) { var end = ber.offset + ber.length; while (ber.offset < end) - this.vals.push(ber.readString()); + this._vals.push(ber.readString(asn1.Ber.OctetString, true)); } } @@ -86,10 +118,15 @@ Attribute.prototype.parse = function(ber) { Attribute.prototype.toBer = function(ber) { assert.ok(ber); + var vals = []; + this._vals.forEach(function(v) { + vals.push(v.toString('utf8')); + }); + ber.startSequence(); ber.writeString(this.type); ber.startSequence(Protocol.LBER_SET); - ber.writeStringArray(this.vals && this.vals.length ? this.vals : []); + ber.writeStringArray(vals); ber.endSequence(); ber.endSequence(); @@ -108,7 +145,8 @@ Attribute.isAttribute = function(attr) { if (!attr.type || typeof(attr.type) !== 'string') return false; if (!attr.vals || !Array.isArray(attr.vals)) return false; for (var i = 0; i < attr.vals.length; i++) - if (typeof(attr.vals[i]) !== 'string') return false; + if (typeof(attr.vals[i]) !== 'string' && !Buffer.isBuffer(attr.vals[i])) + return false; return true; }; diff --git a/lib/messages/search_entry.js b/lib/messages/search_entry.js index 3934767..96f7dce 100644 --- a/lib/messages/search_entry.js +++ b/lib/messages/search_entry.js @@ -87,20 +87,7 @@ SearchEntry.prototype.fromObject = function(obj) { this.attributes = []; Object.keys(obj).forEach(function(k) { - var attr = new Attribute({type: k, vals: []}); - - if (Array.isArray(obj[k])) { - obj[k].forEach(function(v) { - if (typeof(v) !== 'string') - v = '' + v; - - attr.vals.push(v); - }); - } else { - attr.vals.push(obj[k] + ''); - } - - self.attributes.push(attr); + self.attributes.push(new Attribute({type: k, vals: obj[k]})); }); return true; diff --git a/package.json b/package.json index 44723f8..0ad2e95 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "node": ">=0.4" }, "dependencies": { - "asn1": "~0.1.5", + "asn1": "~0.1.6", "buffertools": "~1.0.3", "dtrace-provider": "~0.0.3", "sprintf": "~0.1.1" diff --git a/tst/client.test.js b/tst/client.test.js index 6d744d8..a208dc6 100644 --- a/tst/client.test.js +++ b/tst/client.test.js @@ -83,7 +83,8 @@ test('setup', function(t) { res.send(res.createSearchEntry({ objectName: req.dn, attributes: { - 'foo;binary': '\u00bd + \u00bc = \u00be' + 'foo;binary': 'wr0gKyDCvCA9IMK+', + 'objectclass': 'binary' } })); } else { @@ -431,6 +432,39 @@ test('search empty attribute', function(t) { }); +test('GH-21 binary attributes', function(t) { + client.search('cn=bin, ' + SUFFIX, '(objectclass=*)', function(err, res) { + t.ifError(err); + t.ok(res); + var gotEntry = 0; + var expect = new Buffer('\u00bd + \u00bc = \u00be', 'utf8'); + res.on('searchEntry', function(entry) { + t.ok(entry); + t.ok(entry instanceof ldap.SearchEntry); + t.equal(entry.dn.toString(), 'cn=bin, ' + SUFFIX); + t.ok(entry.attributes); + t.ok(entry.attributes.length); + t.equal(entry.attributes[0].type, 'foo;binary'); + t.equal(entry.attributes[0].vals[0], expect.toString('base64')); + t.equal(entry.attributes[0].buffers[0].toString('base64'), + expect.toString('base64')); + t.ok(entry.object); + gotEntry++; + }); + res.on('error', function(err) { + t.fail(err); + }); + res.on('end', function(res) { + t.ok(res); + t.ok(res instanceof ldap.SearchResponse); + t.equal(res.status, 0); + t.equal(gotEntry, 1); + t.end(); + }); + }); +}); + + test('GH-23 case insensitive attribute filtering', function(t) { var opts = { filter: '(objectclass=*)',