GH-26 rewrite network parser

This commit is contained in:
Mark Cavage 2011-11-11 10:08:48 -08:00
parent c9b10ce420
commit 868c46bde4
10 changed files with 88 additions and 105 deletions

View File

@ -180,10 +180,10 @@ Client.prototype.connect = function(callback) {
if (err) if (err)
c.end(); c.end();
self.emit('connect') self.emit('connect');
}); });
self.emit('connect') self.emit('connect');
}); });
}; };
@ -216,9 +216,9 @@ Client.prototype.bind = function(name, credentials, controls, callback, conn) {
var self = this; var self = this;
var req = new BindRequest({ var req = new BindRequest({
name: name, name: name || '',
authentication: 'Simple', authentication: 'Simple',
credentials: credentials, credentials: credentials || '',
controls: controls controls: controls
}); });
@ -830,8 +830,9 @@ Client.prototype._newConnection = function() {
c.on('connect', function() { c.on('connect', function() {
if (log.isTraceEnabled()) if (log.isTraceEnabled())
log.trace('%s connect event', c.ldap.id); c.ldap.connected = true; log.trace('%s connect event', c.ldap.id);
c.ldap.connected = true;
c.ldap.id += ':' + c.fd; c.ldap.id += ':' + c.fd;
self.emit('connect', c.ldap.id); self.emit('connect', c.ldap.id);
}); });
@ -853,7 +854,7 @@ Client.prototype._newConnection = function() {
err = new ConnectionError(c.ldap.id + ' closed'); err = new ConnectionError(c.ldap.id + ' closed');
} else { } else {
err = new UnbindResponse({ err = new UnbindResponse({
messageID: msgid, messageID: msgid
}); });
err.status = 'unbind'; err.status = 'unbind';
} }
@ -910,5 +911,15 @@ Client.prototype._newConnection = function() {
return callback(message); return callback(message);
}); });
c.parser.on('error', function(err) {
if (log.isTraceEnabled())
log.trace('%s error event=%s', c.ldap.id, err ? err.stack : '?');
if (self.listeners('error').length)
self.emit('error', err);
c.end();
});
return c; return c;
}; };

View File

@ -68,9 +68,9 @@ BindRequest.prototype._toBer = function(ber) {
assert.ok(ber); assert.ok(ber);
ber.writeInt(this.version); ber.writeInt(this.version);
ber.writeString(this.name.toString()); ber.writeString((this.name || '').toString());
// TODO add support for SASL et al // TODO add support for SASL et al
ber.writeString(this.credentials, Ber.Context); ber.writeString((this.credentials || ''), Ber.Context);
return ber; return ber;
}; };

View File

@ -62,17 +62,14 @@ LDAPMessage.prototype.toString = function() {
}; };
LDAPMessage.prototype.parse = function(data, length) { LDAPMessage.prototype.parse = function(ber) {
if (!data || !Buffer.isBuffer(data)) assert.ok(ber);
throw new TypeError('data (buffer) required');
if (this.log.isTraceEnabled()) if (this.log.isTraceEnabled())
this.log.trace('parse: data=%s, len=%d', util.inspect(data), length); this.log.trace('parse: data=%s', util.inspect(ber.buffer));
var ber = new BerReader(data);
// Delegate off to the specific type to parse // Delegate off to the specific type to parse
this._parse(ber, length); this._parse(ber, ber.remain);
// Look for controls // Look for controls
if (ber.peek() === 0xa0) { if (ber.peek() === 0xa0) {

View File

@ -55,9 +55,7 @@ function Parser(options) {
EventEmitter.call(this); EventEmitter.call(this);
this._reset(); this.buffer = null;
var self = this;
this.log4js = options.log4js; this.log4js = options.log4js;
this.log = this.log4js.getLogger('Parser'); this.log = this.log4js.getLogger('Parser');
} }
@ -69,83 +67,61 @@ Parser.prototype.write = function(data) {
if (!data || !Buffer.isBuffer(data)) if (!data || !Buffer.isBuffer(data))
throw new TypeError('data (buffer) required'); throw new TypeError('data (buffer) required');
var log = this.log;
var nextMessage = null;
var self = this; var self = this;
if (this._buffer) function end() {
data = this._buffer.concat(data); if (nextMessage)
return self.write(nextMessage);
if (this.log.isTraceEnabled()) return true;
this.log.trace('Processing buffer (concat\'d): ' + util.inspect(data)); }
// If there's more than one message in this buffer self.buffer = (self.buffer ? self.buffer.concat(data) : data);
var extra;
var ber = new BerReader(self.buffer);
if (!ber.readSequence())
return false;
if (ber.remain < ber.length) { // ENOTENOUGH
return false;
} else if (ber.remain > ber.length) { // ETOOMUCH
// This is sort of ugly, but allows us to make miminal copies
nextMessage = self.buffer.slice(ber.offset + ber.length);
ber._size = ber.offset + ber.length;
assert.equal(ber.remain, ber.length);
}
// If we're here, ber holds the message, and nextMessage is temporarily
// pointing at the next sequence of data (if it exists)
self.buffer = null;
var message = this.getMessage(ber);
if (!message)
return end();
try { try {
if (this._message === null) { message.parse(ber);
var ber = new BerReader(data);
if (!this._newMessage(ber))
return false;
data = data.slice(ber.offset);
}
if (data.length > this._messageLength) {
extra = data.slice(ber.length);
data = data.slice(0, ber.length);
}
assert.ok(this._message.parse(data, ber.length));
var message = this._message;
this._reset();
this.emit('message', message); this.emit('message', message);
} catch (e) { } catch (e) {
self.emit('error', e, self._message); this.emit('error', e, message);
return false; return false;
} }
// Another message is already there return end();
if (extra) {
if (this.log.isTraceEnabled())
this.log.trace('parsing extra bytes: ' + util.inspect(extra));
return this.write(extra);
}
return true;
}; };
Parser.prototype._newMessage = function(ber) { Parser.prototype.getMessage = function(ber) {
assert.ok(ber); assert.ok(ber);
if (this._messageLength === null) { var log = this.log;
if (ber.readSequence() === null) { // not enough data for the length? var self = this;
this._buffer = ber.buffer;
if (this.log.isTraceEnabled())
this.log.trace('Not enough data for the message header');
return false;
}
this._messageLength = ber.length;
}
if (ber.remain < this._messageLength) {
if (this.log.isTraceEnabled())
this.log.trace('Not enough data for the message');
this._buffer = ber.buffer;
return false;
}
var messageID = ber.readInt(); var messageID = ber.readInt();
var type = ber.readSequence(); var type = ber.readSequence();
if (this.log.isTraceEnabled())
this.log.trace('message id=%d, type=0x%s', messageID, type.toString(16));
var Message; var Message;
switch (type) { switch (type) {
@ -230,28 +206,23 @@ Parser.prototype._newMessage = function(ber) {
break; break;
default: default:
var e = new Error('protocolOp 0x' + type.toString(16) + ' not supported'); this.emit('error',
this.emit('error', e, new LDAPResult({ new Error('protocolOp 0x' +
messageID: messageID, (type ? type.toString(16) : '??') +
protocolOp: type ' not supported'
})); ),
this._reset(); new LDAPResult({
messageID: messageID,
protocolOp: type || Protocol.LDAP_REP_EXTENSION
}));
return false; return false;
} }
assert.ok(Message);
var self = this;
this._message = new Message({ return new Message({
messageID: messageID, messageID: messageID,
log4js: self.log4js log4js: self.log4js
}); });
return true;
}; };
Parser.prototype._reset = function() {
this._message = null;
this._messageLength = null;
this._buffer = null;
};

View File

@ -140,8 +140,8 @@ SearchEntry.prototype._parse = function(ber) {
this.objectName = ber.readString(); this.objectName = ber.readString();
assert.ok(ber.readSequence()); assert.ok(ber.readSequence());
var end = ber.offset + ber.length;
var end = ber.offset + ber.length;
while (ber.offset < end) { while (ber.offset < end) {
var a = new Attribute(); var a = new Attribute();
a.parse(ber); a.parse(ber);

View File

@ -395,14 +395,17 @@ function Server(options) {
log.error('Exception happened parsing for %s: %s', log.error('Exception happened parsing for %s: %s',
c.ldap.id, err.stack); c.ldap.id, err.stack);
if (!message)
return c.destroy();
var res = getResponse(message); var res = getResponse(message);
if (res) { if (!res)
res.status = 0x02; // protocol error return c.destroy();
res.errorMessage = err.toString();
c.end(res.toBer()); res.status = 0x02; // protocol error
} else { res.errorMessage = err.toString();
c.destroy(); c.end(res.toBer());
}
}); });
c.on('data', function(data) { c.on('data', function(data) {
@ -804,7 +807,7 @@ Server.prototype._getHandlerChain = function(req) {
handlers: [(req.protocolOp !== Protocol.LDAP_REQ_EXTENSION ? handlers: [(req.protocolOp !== Protocol.LDAP_REQ_EXTENSION ?
noSuffixHandler : noExOpHandler)] noSuffixHandler : noExOpHandler)]
}; };
} };
Server.prototype._mount = function(op, name, argv, notDN) { Server.prototype._mount = function(op, name, argv, notDN) {

View File

@ -3,7 +3,7 @@
"name": "ldapjs", "name": "ldapjs",
"homepage": "http://ldapjs.org", "homepage": "http://ldapjs.org",
"description": "LDAP client and server APIs", "description": "LDAP client and server APIs",
"version": "0.2.8", "version": "0.3.0",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git://github.com/mcavage/node-ldapjs.git" "url": "git://github.com/mcavage/node-ldapjs.git"

View File

@ -128,7 +128,8 @@ test('setup', function(t) {
reconnect: false // turn this off for unit testing reconnect: false // turn this off for unit testing
}); });
t.ok(client); t.ok(client);
client.log4js.setLevel('Trace'); // client.log4js.setLevel('Trace');
// server.log4js.setLevel('Trace');
t.end(); t.end();
}); });

View File

@ -34,7 +34,7 @@ test('Construct no args', function(t) {
test('Construct args', function(t) { test('Construct args', function(t) {
var f = new ExtensibleFilter({ var f = new ExtensibleFilter({
matchType: 'foo', matchType: 'foo',
value: 'bar', value: 'bar'
}); });
t.ok(f); t.ok(f);
t.equal(f.matchType, 'foo'); t.equal(f.matchType, 'foo');

View File

@ -46,7 +46,7 @@ test('parse', function(t) {
var req = new DeleteRequest(); var req = new DeleteRequest();
var reader = new BerReader(ber.buffer); var reader = new BerReader(ber.buffer);
reader.readSequence(0x4a); reader.readSequence(0x4a);
t.ok(req.parse(reader.buffer, reader.length)); t.ok(req.parse(reader, reader.length));
t.equal(req.dn.toString(), 'cn=test'); t.equal(req.dn.toString(), 'cn=test');
t.end(); t.end();
}); });