GH-27 Support for abandon operations client side, and noop it server side
This commit is contained in:
parent
060bb2c55b
commit
75aba691f1
123
lib/client.js
123
lib/client.js
|
@ -21,6 +21,7 @@ var url = require('./url');
|
|||
|
||||
///--- Globals
|
||||
|
||||
var AbandonRequest = messages.AbandonRequest;
|
||||
var AddRequest = messages.AddRequest;
|
||||
var BindRequest = messages.BindRequest;
|
||||
var CompareRequest = messages.CompareRequest;
|
||||
|
@ -189,6 +190,38 @@ Client.prototype.bind = function(name, credentials, controls, callback, conn) {
|
|||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends an abandon request to the LDAP server.
|
||||
*
|
||||
* The callback will be invoked as soon as the data is flushed out to the
|
||||
* network, as there is never a response from abandon.
|
||||
*
|
||||
* @param {Number} messageID the messageID to abandon.
|
||||
* @param {Control} controls (optional) either a Control or [Control].
|
||||
* @param {Function} callback of the form f(err).
|
||||
* @throws {TypeError} on invalid input.
|
||||
*/
|
||||
Client.prototype.abandon = function(messageID, controls, callback) {
|
||||
if (typeof(messageID) !== 'number')
|
||||
throw new TypeError('messageID (number) required');
|
||||
if (typeof(controls) === 'function') {
|
||||
callback = controls;
|
||||
controls = [];
|
||||
} else {
|
||||
control = validateControls(controls);
|
||||
}
|
||||
if (typeof(callback) !== 'function')
|
||||
throw new TypeError('callback (function) required');
|
||||
|
||||
var req = new AbandonRequest({
|
||||
abandonID: messageID,
|
||||
controls: controls
|
||||
});
|
||||
|
||||
return this._send(req, 'abandon', callback);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds an entry to the LDAP server.
|
||||
*
|
||||
|
@ -583,49 +616,51 @@ Client.prototype._send = function(message, expect, callback, connection) {
|
|||
|
||||
// Now set up the callback in the messages table
|
||||
message.messageID = conn.ldap.nextMessageID;
|
||||
conn.ldap.messages[message.messageID] = function(res) {
|
||||
if (self.log.isDebugEnabled())
|
||||
self.log.debug('%s: response received: %j', conn.ldap.id, res.json);
|
||||
if (expect !== 'abandon') {
|
||||
conn.ldap.messages[message.messageID] = function(res) {
|
||||
if (self.log.isDebugEnabled())
|
||||
self.log.debug('%s: response received: %j', conn.ldap.id, res.json);
|
||||
|
||||
var err = null;
|
||||
var err = null;
|
||||
|
||||
if (res instanceof LDAPResult) {
|
||||
delete conn.ldap.messages[message.messageID];
|
||||
if (res instanceof LDAPResult) {
|
||||
delete conn.ldap.messages[message.messageID];
|
||||
|
||||
if (expect.indexOf(res.status) === -1) {
|
||||
err = errors.getError(res);
|
||||
if (expect.indexOf(res.status) === -1) {
|
||||
err = errors.getError(res);
|
||||
if (typeof(callback) === 'function')
|
||||
return callback(err);
|
||||
|
||||
return callback.emit('error', err);
|
||||
}
|
||||
|
||||
if (typeof(callback) === 'function')
|
||||
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];
|
||||
|
||||
err = new errors.ProtocolError(res.type);
|
||||
if (typeof(callback) === 'function')
|
||||
return callback(err);
|
||||
|
||||
return callback.emit('error', err);
|
||||
callback.emit('error', err);
|
||||
}
|
||||
|
||||
if (typeof(callback) === 'function')
|
||||
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];
|
||||
|
||||
err = new errors.ProtocolError(res.type);
|
||||
if (typeof(callback) === 'function')
|
||||
return callback(err);
|
||||
|
||||
callback.emit('error', err);
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// Finally send some data
|
||||
if (this.log.isDebugEnabled())
|
||||
|
@ -633,9 +668,19 @@ Client.prototype._send = function(message, expect, callback, connection) {
|
|||
|
||||
// Note if this was an unbind, we just go ahead and end, since there
|
||||
// will never be a response
|
||||
return conn.write(message.toBer(), (expect === 'unbind' ? function() {
|
||||
conn.end();
|
||||
} : null));
|
||||
var _writeCb = null;
|
||||
if (expect === 'abandon') {
|
||||
_writeCb = function() {
|
||||
return callback();
|
||||
};
|
||||
} else if (expect === 'unbind') {
|
||||
_writeCb = function() {
|
||||
conn.end();
|
||||
};
|
||||
} else {
|
||||
// noop
|
||||
}
|
||||
return conn.write(message.toBer(), _writeCb);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
// 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 LDAPResult = require('./result');
|
||||
|
||||
var dn = require('../dn');
|
||||
var Attribute = require('../attribute');
|
||||
var Protocol = require('../protocol');
|
||||
|
||||
///--- Globals
|
||||
|
||||
var Ber = asn1.Ber;
|
||||
|
||||
|
||||
|
||||
///--- API
|
||||
|
||||
function AbandonRequest(options) {
|
||||
if (options) {
|
||||
if (typeof(options) !== 'object')
|
||||
throw new TypeError('options must be an object');
|
||||
if (options.abandonID && typeof(options.abandonID) !== 'number')
|
||||
throw new TypeError('abandonID must be a number');
|
||||
} else {
|
||||
options = {};
|
||||
}
|
||||
|
||||
options.protocolOp = Protocol.LDAP_REQ_ABANDON;
|
||||
LDAPMessage.call(this, options);
|
||||
|
||||
this.abandonID = options.abandonID || 0;
|
||||
|
||||
var self = this;
|
||||
this.__defineGetter__('type', function() { return 'AbandonRequest'; });
|
||||
}
|
||||
util.inherits(AbandonRequest, LDAPMessage);
|
||||
module.exports = AbandonRequest;
|
||||
|
||||
|
||||
AbandonRequest.prototype._parse = function(ber, length) {
|
||||
assert.ok(ber);
|
||||
assert.ok(length);
|
||||
|
||||
// What a PITA - have to replicate ASN.1 integer logic to work around the
|
||||
// way abandon is encoded and the way ldapjs framework handles "normal"
|
||||
// messages
|
||||
|
||||
var buf = ber.buffer;
|
||||
var offset = 0;
|
||||
var value = 0;
|
||||
|
||||
var fb = buf[offset++];
|
||||
value = fb & 0x7F;
|
||||
for (var i = 1; i < length; i++) {
|
||||
value <<= 8;
|
||||
value |= (buf[offset++] & 0xff);
|
||||
}
|
||||
if ((fb & 0x80) == 0x80)
|
||||
value = -value;
|
||||
|
||||
ber._offset += length;
|
||||
|
||||
this.abandonID = value;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
AbandonRequest.prototype._toBer = function(ber) {
|
||||
assert.ok(ber);
|
||||
|
||||
var i = this.abandonID;
|
||||
var sz = 4;
|
||||
|
||||
while ((((i & 0xff800000) == 0) || ((i & 0xff800000) == 0xff800000)) &&
|
||||
(sz > 1)) {
|
||||
sz--;
|
||||
i <<= 8;
|
||||
}
|
||||
assert.ok(sz <= 4);
|
||||
|
||||
while (sz-- > 0) {
|
||||
ber.writeByte((i & 0xff000000) >> 24);
|
||||
i <<= 8;
|
||||
}
|
||||
|
||||
return ber;
|
||||
};
|
||||
|
||||
|
||||
AbandonRequest.prototype._json = function(j) {
|
||||
assert.ok(j);
|
||||
|
||||
j.abandonID = this.abandonID;
|
||||
|
||||
return j;
|
||||
};
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
|
||||
|
||||
var assert = require('assert');
|
||||
var util = require('util');
|
||||
|
||||
var LDAPMessage = require('./result');
|
||||
var Protocol = require('../protocol');
|
||||
|
||||
|
||||
///--- API
|
||||
// Stub this out
|
||||
|
||||
function AbandonResponse(options) {
|
||||
if (!options)
|
||||
options = {};
|
||||
if (typeof(options) !== 'object')
|
||||
throw new TypeError('options must be an object');
|
||||
|
||||
options.protocolOp = 0;
|
||||
LDAPMessage.call(this, options);
|
||||
this.__defineGetter__('type', function() { return 'AbandonResponse'; });
|
||||
}
|
||||
util.inherits(AbandonResponse, LDAPMessage);
|
||||
module.exports = AbandonResponse;
|
||||
|
||||
|
||||
AbandonResponse.prototype.end = function(status) {};
|
||||
|
||||
|
||||
AbandonResponse.prototype._json = function(j) {
|
||||
return j;
|
||||
};
|
|
@ -4,6 +4,8 @@ var LDAPMessage = require('./message');
|
|||
var LDAPResult = require('./result');
|
||||
var Parser = require('./parser');
|
||||
|
||||
var AbandonRequest = require('./abandon_request');
|
||||
var AbandonResponse = require('./abandon_response');
|
||||
var AddRequest = require('./add_request');
|
||||
var AddResponse = require('./add_response');
|
||||
var BindRequest = require('./bind_request');
|
||||
|
@ -35,6 +37,8 @@ module.exports = {
|
|||
LDAPResult: LDAPResult,
|
||||
Parser: Parser,
|
||||
|
||||
AbandonRequest: AbandonRequest,
|
||||
AbandonResponse: AbandonResponse,
|
||||
AddRequest: AddRequest,
|
||||
AddResponse: AddResponse,
|
||||
BindRequest: BindRequest,
|
||||
|
|
|
@ -6,6 +6,7 @@ var util = require('util');
|
|||
|
||||
var asn1 = require('asn1');
|
||||
|
||||
var AbandonRequest = require('./abandon_request');
|
||||
var AddRequest = require('./add_request');
|
||||
var AddResponse = require('./add_response');
|
||||
var BindRequest = require('./bind_request');
|
||||
|
@ -148,6 +149,10 @@ Parser.prototype._newMessage = function(ber) {
|
|||
var Message;
|
||||
switch (type) {
|
||||
|
||||
case Protocol.LDAP_REQ_ABANDON:
|
||||
Message = AbandonRequest;
|
||||
break;
|
||||
|
||||
case Protocol.LDAP_REQ_ADD:
|
||||
Message = AddRequest;
|
||||
break;
|
||||
|
|
|
@ -16,6 +16,7 @@ var Protocol = require('./protocol');
|
|||
var logStub = require('./log_stub');
|
||||
|
||||
var Parser = require('./messages').Parser;
|
||||
var AbandonResponse = require('./messages/abandon_response');
|
||||
var AddResponse = require('./messages/add_response');
|
||||
var BindResponse = require('./messages/bind_response');
|
||||
var CompareResponse = require('./messages/compare_response');
|
||||
|
@ -79,7 +80,8 @@ function getResponse(req) {
|
|||
Response = BindResponse;
|
||||
break;
|
||||
case Protocol.LDAP_REQ_ABANDON:
|
||||
return; // Noop
|
||||
Response = AbandonResponse;
|
||||
break;
|
||||
case Protocol.LDAP_REQ_ADD:
|
||||
Response = AddResponse;
|
||||
break;
|
||||
|
@ -133,6 +135,16 @@ function defaultHandler(req, res, next) {
|
|||
}
|
||||
|
||||
|
||||
function defaultAbandonHandler(req, res, next) {
|
||||
assert.ok(req);
|
||||
assert.ok(res);
|
||||
assert.ok(next);
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
}
|
||||
|
||||
|
||||
function defaultUnbindHandler(req, res, next) {
|
||||
assert.ok(req);
|
||||
assert.ok(res);
|
||||
|
@ -333,7 +345,6 @@ function Server(options) {
|
|||
res.requestDN = req.dn;
|
||||
|
||||
var chain = self._getHandlerChain(req);
|
||||
//.concat(this._postChain || []);
|
||||
|
||||
var i = 0;
|
||||
return function(err) {
|
||||
|
@ -650,6 +661,7 @@ Server.prototype.after = function() {
|
|||
});
|
||||
};
|
||||
|
||||
|
||||
// All these just reexpose the requisite net.Server APIs
|
||||
Server.prototype.listen = function(port, host, callback) {
|
||||
if (!port)
|
||||
|
@ -736,7 +748,7 @@ Server.prototype._getHandlerChain = function(req) {
|
|||
for (var r in routes) {
|
||||
if (routes.hasOwnProperty(r)) {
|
||||
var route = routes[r];
|
||||
// Special cases are exops and unbinds, handle those first.
|
||||
// Special cases are abandons, exops and unbinds, handle those first.
|
||||
if (req.protocolOp === Protocol.LDAP_REQ_EXTENSION) {
|
||||
if (r !== req.requestName)
|
||||
continue;
|
||||
|
@ -758,6 +770,11 @@ Server.prototype._getHandlerChain = function(req) {
|
|||
backend: routes['unbind'] ? routes['unbind'].backend : self,
|
||||
handlers: getUnbindChain()
|
||||
};
|
||||
} else if (req.protocolOp === Protocol.LDAP_REQ_ABANDON) {
|
||||
return {
|
||||
backend: self,
|
||||
handlers: [defaultAbandonHandler]
|
||||
};
|
||||
}
|
||||
|
||||
if (!route[op])
|
||||
|
|
14
package.json
14
package.json
|
@ -17,15 +17,15 @@
|
|||
"node": ">=0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"asn1": "~0.1.7",
|
||||
"buffertools": "~1.0.3",
|
||||
"dtrace-provider": "~0.0.3",
|
||||
"nopt": "~1.0.10",
|
||||
"sprintf": "~0.1.1"
|
||||
"asn1": "0.1.8",
|
||||
"buffertools": "1.0.5",
|
||||
"dtrace-provider": "0.0.3",
|
||||
"nopt": "1.0.10",
|
||||
"sprintf": "0.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tap": "~0.0.9",
|
||||
"node-uuid": "~1.2.0"
|
||||
"tap": "0.0.12",
|
||||
"node-uuid": "1.2.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "./node_modules/.bin/tap ./tst"
|
||||
|
|
|
@ -541,6 +541,14 @@ test('GH-24 attribute selection of *', function(t) {
|
|||
});
|
||||
|
||||
|
||||
test('abandon (GH-27)', function(t) {
|
||||
client.abandon(401876543, function(err) {
|
||||
t.ifError(err);
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
test('shutdown', function(t) {
|
||||
client.unbind(function() {
|
||||
server.on('close', function() {
|
||||
|
|
Loading…
Reference in New Issue