GH-27 Support for abandon operations client side, and noop it server side

This commit is contained in:
Mark Cavage 2011-11-07 14:13:53 -08:00
parent 060bb2c55b
commit 75aba691f1
8 changed files with 263 additions and 49 deletions

View File

@ -21,6 +21,7 @@ var url = require('./url');
///--- Globals ///--- Globals
var AbandonRequest = messages.AbandonRequest;
var AddRequest = messages.AddRequest; var AddRequest = messages.AddRequest;
var BindRequest = messages.BindRequest; var BindRequest = messages.BindRequest;
var CompareRequest = messages.CompareRequest; 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. * 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 // Now set up the callback in the messages table
message.messageID = conn.ldap.nextMessageID; message.messageID = conn.ldap.nextMessageID;
conn.ldap.messages[message.messageID] = function(res) { if (expect !== 'abandon') {
if (self.log.isDebugEnabled()) conn.ldap.messages[message.messageID] = function(res) {
self.log.debug('%s: response received: %j', conn.ldap.id, res.json); 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) { if (res instanceof LDAPResult) {
delete conn.ldap.messages[message.messageID]; delete conn.ldap.messages[message.messageID];
if (expect.indexOf(res.status) === -1) { if (expect.indexOf(res.status) === -1) {
err = errors.getError(res); 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') if (typeof(callback) === 'function')
return callback(err); 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 // Finally send some data
if (this.log.isDebugEnabled()) 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 // Note if this was an unbind, we just go ahead and end, since there
// will never be a response // will never be a response
return conn.write(message.toBer(), (expect === 'unbind' ? function() { var _writeCb = null;
conn.end(); if (expect === 'abandon') {
} : null)); _writeCb = function() {
return callback();
};
} else if (expect === 'unbind') {
_writeCb = function() {
conn.end();
};
} else {
// noop
}
return conn.write(message.toBer(), _writeCb);
}; };

View File

@ -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;
};

View File

@ -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;
};

View File

@ -4,6 +4,8 @@ var LDAPMessage = require('./message');
var LDAPResult = require('./result'); var LDAPResult = require('./result');
var Parser = require('./parser'); var Parser = require('./parser');
var AbandonRequest = require('./abandon_request');
var AbandonResponse = require('./abandon_response');
var AddRequest = require('./add_request'); var AddRequest = require('./add_request');
var AddResponse = require('./add_response'); var AddResponse = require('./add_response');
var BindRequest = require('./bind_request'); var BindRequest = require('./bind_request');
@ -35,6 +37,8 @@ module.exports = {
LDAPResult: LDAPResult, LDAPResult: LDAPResult,
Parser: Parser, Parser: Parser,
AbandonRequest: AbandonRequest,
AbandonResponse: AbandonResponse,
AddRequest: AddRequest, AddRequest: AddRequest,
AddResponse: AddResponse, AddResponse: AddResponse,
BindRequest: BindRequest, BindRequest: BindRequest,

View File

@ -6,6 +6,7 @@ var util = require('util');
var asn1 = require('asn1'); var asn1 = require('asn1');
var AbandonRequest = require('./abandon_request');
var AddRequest = require('./add_request'); var AddRequest = require('./add_request');
var AddResponse = require('./add_response'); var AddResponse = require('./add_response');
var BindRequest = require('./bind_request'); var BindRequest = require('./bind_request');
@ -148,6 +149,10 @@ Parser.prototype._newMessage = function(ber) {
var Message; var Message;
switch (type) { switch (type) {
case Protocol.LDAP_REQ_ABANDON:
Message = AbandonRequest;
break;
case Protocol.LDAP_REQ_ADD: case Protocol.LDAP_REQ_ADD:
Message = AddRequest; Message = AddRequest;
break; break;

View File

@ -16,6 +16,7 @@ var Protocol = require('./protocol');
var logStub = require('./log_stub'); var logStub = require('./log_stub');
var Parser = require('./messages').Parser; var Parser = require('./messages').Parser;
var AbandonResponse = require('./messages/abandon_response');
var AddResponse = require('./messages/add_response'); var AddResponse = require('./messages/add_response');
var BindResponse = require('./messages/bind_response'); var BindResponse = require('./messages/bind_response');
var CompareResponse = require('./messages/compare_response'); var CompareResponse = require('./messages/compare_response');
@ -79,7 +80,8 @@ function getResponse(req) {
Response = BindResponse; Response = BindResponse;
break; break;
case Protocol.LDAP_REQ_ABANDON: case Protocol.LDAP_REQ_ABANDON:
return; // Noop Response = AbandonResponse;
break;
case Protocol.LDAP_REQ_ADD: case Protocol.LDAP_REQ_ADD:
Response = AddResponse; Response = AddResponse;
break; 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) { function defaultUnbindHandler(req, res, next) {
assert.ok(req); assert.ok(req);
assert.ok(res); assert.ok(res);
@ -333,7 +345,6 @@ function Server(options) {
res.requestDN = req.dn; res.requestDN = req.dn;
var chain = self._getHandlerChain(req); var chain = self._getHandlerChain(req);
//.concat(this._postChain || []);
var i = 0; var i = 0;
return function(err) { return function(err) {
@ -650,6 +661,7 @@ Server.prototype.after = function() {
}); });
}; };
// All these just reexpose the requisite net.Server APIs // All these just reexpose the requisite net.Server APIs
Server.prototype.listen = function(port, host, callback) { Server.prototype.listen = function(port, host, callback) {
if (!port) if (!port)
@ -736,7 +748,7 @@ Server.prototype._getHandlerChain = function(req) {
for (var r in routes) { for (var r in routes) {
if (routes.hasOwnProperty(r)) { if (routes.hasOwnProperty(r)) {
var route = routes[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 (req.protocolOp === Protocol.LDAP_REQ_EXTENSION) {
if (r !== req.requestName) if (r !== req.requestName)
continue; continue;
@ -758,6 +770,11 @@ Server.prototype._getHandlerChain = function(req) {
backend: routes['unbind'] ? routes['unbind'].backend : self, backend: routes['unbind'] ? routes['unbind'].backend : self,
handlers: getUnbindChain() handlers: getUnbindChain()
}; };
} else if (req.protocolOp === Protocol.LDAP_REQ_ABANDON) {
return {
backend: self,
handlers: [defaultAbandonHandler]
};
} }
if (!route[op]) if (!route[op])

View File

@ -17,15 +17,15 @@
"node": ">=0.4" "node": ">=0.4"
}, },
"dependencies": { "dependencies": {
"asn1": "~0.1.7", "asn1": "0.1.8",
"buffertools": "~1.0.3", "buffertools": "1.0.5",
"dtrace-provider": "~0.0.3", "dtrace-provider": "0.0.3",
"nopt": "~1.0.10", "nopt": "1.0.10",
"sprintf": "~0.1.1" "sprintf": "0.1.1"
}, },
"devDependencies": { "devDependencies": {
"tap": "~0.0.9", "tap": "0.0.12",
"node-uuid": "~1.2.0" "node-uuid": "1.2.0"
}, },
"scripts": { "scripts": {
"test": "./node_modules/.bin/tap ./tst" "test": "./node_modules/.bin/tap ./tst"

View File

@ -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) { test('shutdown', function(t) {
client.unbind(function() { client.unbind(function() {
server.on('close', function() { server.on('close', function() {