GH-14: Client needs to handle search references

This commit is contained in:
Mark Cavage 2011-09-27 11:49:33 -07:00
parent 50a0c59d1f
commit f4b49a20ae
6 changed files with 197 additions and 15 deletions

View File

@ -33,6 +33,7 @@ var UnbindRequest = messages.UnbindRequest;
var LDAPResult = messages.LDAPResult;
var SearchEntry = messages.SearchEntry;
var SearchReference = messages.SearchReference;
var SearchResponse = messages.SearchResponse;
var Parser = messages.Parser;
@ -587,6 +588,7 @@ Client.prototype._send = function(message, expect, callback, connection) {
self.log.debug('%s: response received: %j', conn.ldap.id, res.json);
var err = null;
if (res instanceof LDAPResult) {
delete conn.ldap.messages[message.messageID];
@ -602,11 +604,18 @@ Client.prototype._send = function(message, expect, callback, connection) {
return callback(null, res);
callback.emit('end', res);
} else if (res instanceof SearchEntry) {
assert.ok(callback instanceof EventEmitter);
callback.emit('searchEntry', res);
} else if (res instanceof SearchReference) {
assert.ok(callback instanceof EventEmitter);
callback.emit('searchReference', res);
} else if (res instanceof Error) {
return callback(res);
} else {
delete conn.ldap.messages[message.messageID];

View File

@ -20,6 +20,7 @@ var ModifyDNRequest = require('./moddn_request');
var ModifyDNResponse = require('./moddn_response');
var SearchRequest = require('./search_request');
var SearchEntry = require('./search_entry');
var SearchReference = require('./search_reference');
var SearchResponse = require('./search_response');
var UnbindRequest = require('./unbind_request');
var UnbindResponse = require('./unbind_response');
@ -50,6 +51,7 @@ module.exports = {
ModifyDNResponse: ModifyDNResponse,
SearchRequest: SearchRequest,
SearchEntry: SearchEntry,
SearchReference: SearchReference,
SearchResponse: SearchResponse,
UnbindRequest: UnbindRequest,
UnbindResponse: UnbindResponse

View File

@ -22,6 +22,7 @@ var ModifyDNRequest = require('./moddn_request');
var ModifyDNResponse = require('./moddn_response');
var SearchRequest = require('./search_request');
var SearchEntry = require('./search_entry');
var SearchReference = require('./search_reference');
var SearchResponse = require('./search_response');
var UnbindRequest = require('./unbind_request');
var UnbindResponse = require('./unbind_response');
@ -211,6 +212,10 @@ Parser.prototype._newMessage = function(ber) {
Message = SearchEntry;
break;
case Protocol.LDAP_REP_SEARCH_REF:
Message = SearchReference;
break;
case Protocol.LDAP_REP_SEARCH:
Message = SearchResponse;
break;

View File

@ -0,0 +1,106 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var asn1 = require('asn1');
var LDAPMessage = require('./message');
var Protocol = require('../protocol');
var dn = require('../dn');
var url = require('../url');
///--- Globals
var BerWriter = asn1.BerWriter;
var parseURL = url.parse;
///--- API
function SearchReference(options) {
if (options) {
if (typeof(options) !== 'object')
throw new TypeError('options must be an object');
if (options.objectName && !(options.objectName instanceof dn.DN))
throw new TypeError('options.objectName must be a DN');
} else {
options = {};
}
options.protocolOp = Protocol.LDAP_REP_SEARCH_REF;
LDAPMessage.call(this, options);
this.uris = options.uris || [];
var self = this;
this.__defineGetter__('type', function() { return 'SearchReference'; });
this.__defineGetter__('object', function() {
return {
dn: self.dn.toString(),
uris: self.uris.slice()
};
});
this.__defineGetter__('_dn', function() {
return new dn.DN('');
});
this.__defineGetter__('urls', function() {
return self.uris;
});
this.__defineSetter__('urls', function(u) {
self.uris = u.slice();
});
}
util.inherits(SearchReference, LDAPMessage);
module.exports = SearchReference;
SearchReference.prototype.toObject = function() {
return this.object;
};
SearchReference.prototype.fromObject = function(obj) {
if (typeof(obj) !== 'object')
throw new TypeError('object required');
this.uris = obj.uris ? obj.uris.slice() : [];
return true;
};
SearchReference.prototype._json = function(j) {
assert.ok(j);
j.uris = this.uris.slice();
return j;
};
SearchReference.prototype._parse = function(ber, length) {
assert.ok(ber);
while (ber.offset < length) {
var _url = ber.readString();
parseURL(_url);
this.uris.push(_url);
}
return true;
};
SearchReference.prototype._toBer = function(ber) {
assert.ok(ber);
this.uris.forEach(function(u) {
ber.writeString(u.href || u);
});
return ber;
};

View File

@ -5,8 +5,10 @@ var util = require('util');
var LDAPResult = require('./result');
var SearchEntry = require('./search_entry');
var SearchReference = require('./search_reference');
var parseDN = require('../dn').parse;
var parseURL = require('../url').parse;
var Protocol = require('../protocol');
@ -45,7 +47,12 @@ SearchResponse.prototype.send = function(entry, nofiltering) {
var self = this;
if (!(entry instanceof SearchEntry)) {
if (entry instanceof SearchEntry || entry instanceof SearchReference) {
if (!entry.messageID)
entry.messageID = this.messageID;
if (entry.messageID !== this.messageID)
throw new Error('SearchEntry messageID mismatch');
} else {
if (!entry.dn)
throw new Error('entry.dn required');
if (!entry.attributes)
@ -72,11 +79,6 @@ SearchResponse.prototype.send = function(entry, nofiltering) {
log4js: self.log4js
});
entry.fromObject(save);
} else {
if (!entry.messageID)
entry.messageID = this.messageID;
if (entry.messageID !== this.messageID)
throw new Error('SearchEntry messageID mismatch');
}
try {
@ -108,3 +110,24 @@ SearchResponse.prototype.createSearchEntry = function(object) {
return entry;
};
SearchResponse.prototype.createSearchReference = function(uris) {
if (!uris)
throw new TypeError('uris ([string]) required');
if (!Array.isArray(uris))
uris = [uris];
for (var i = 0; i < uris.length; i++) {
if (typeof(uris[i]) == 'string')
uris[i] = parseURL(uris[i]);
}
var self = this;
return new SearchReference({
messageID: self.messageID,
log4js: self.log4js,
uris: uris
});
};

View File

@ -76,15 +76,21 @@ test('setup', function(t) {
});
server.search(SUFFIX, function(req, res, next) {
var e = res.createSearchEntry({
objectName: req.dn,
attributes: {
cn: ['unit', 'test'],
sn: 'testy'
}
});
res.send(e);
res.send(e);
if (!req.dn.equals('cn=ref,' + SUFFIX)) {
var e = res.createSearchEntry({
objectName: req.dn,
attributes: {
cn: ['unit', 'test'],
sn: 'testy'
}
});
res.send(e);
res.send(e);
} else {
res.send(res.createSearchReference('ldap://localhost'));
}
res.end();
return next();
});
@ -344,6 +350,37 @@ test('search basic', function(t) {
});
test('search referral', function(t) {
client.search('cn=ref, ' + SUFFIX, '(objectclass=*)', function(err, res) {
t.ifError(err);
t.ok(res);
var gotEntry = 0;
var gotReferral = false;
res.on('searchEntry', function(entry) {
gotEntry++;
});
res.on('searchReference', function(referral) {
gotReferral = true;
t.ok(referral);
t.ok(referral instanceof ldap.SearchReference);
t.ok(referral.uris);
t.ok(referral.uris.length);
});
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, 0);
t.ok(gotReferral);
t.end();
});
});
});
test('shutdown', function(t) {
client.unbind(function() {
server.on('close', function() {