lint cleanup, and extensible matching

This commit is contained in:
Mark Cavage 2012-01-19 14:00:14 -08:00
parent 60870497ef
commit fb63ba2220
3 changed files with 268 additions and 245 deletions

View File

@ -28,249 +28,271 @@ var BerReader = asn1.BerReader;
///--- Internal Parsers
//expression parsing
//returns the index of the closing parenthesis matching the open paren specified by openParenIndex
function matchParens(str, openParenIndex){
var stack = [];
for(var i=openParenIndex || 0;i<str.length;i++){
if(str.charAt(i)=='('){
stack.push(1);
}else if(str.charAt(i) == ')'){
stack.pop();
if(stack.length === 0){
//console.log('[_findMatchingParenthesis]: returning ', i);
return i;
}
}
}
//console.log('[_findMatchingParenthesis]: returning ', str.length-1);
return str.length-1;
};
// expression parsing
// returns the index of the closing parenthesis matching the open paren
// specified by openParenIndex
function matchParens(str, openParenIndex) {
var stack = [];
for (var i = openParenIndex || 0; i < str.length; i++) {
if (str.charAt(i) === '(') {
stack.push(1);
} else if (str.charAt(i) === ')') {
stack.pop();
if (stack.length === 0)
return i;
}
}
return str.length - 1;
}
//recursive function that builds a filter tree from a string expression
//the filter tree is an intermediary step between the incoming expression and the outgoing Filter Class structure.
function _buildFilterTree(expr){
//console.log('[buildFilterTree]: expression: ',expr);
var tree = {};
if(expr.length === 0){
return tree;
}
//console.log(expr);
//remove leading and trailing parenthesis if they are there
if (expr.charAt(0) == '('){
expr = expr.substring(1,expr.length-1);
console.log('substring: '+expr);
}
//store prefix operator
if(expr.charAt(0) == '&'){
tree.op = 'and';
expr = expr.substring(1);
}else if (expr.charAt(0) == '|'){
tree.op = 'or';
expr = expr.substring(1);
}else if(expr.charAt(0) == '!'){
tree.op = 'not';
expr = expr.substring(1);
}else{
tree.op = 'expr';
}
//if its a logical operator
if(tree.op != 'expr'){
var child,i=0;
tree.children = [];
//logical operators are k-ary, so we go until our expression string runs out
//(at least for this recursion level)
var endParen;
while (expr.length !== 0){
endParen = matchParens(expr);
if (endParen == expr.length-1){
tree.children[i] = _buildFilterTree(expr);
expr = "";
}else{
child = expr.slice(0,endParen+1);
expr = expr.substring(endParen+1);
tree.children[i] = _buildFilterTree(child);
}
i++;
}
}else{
//else its some sort of non-logical expression, parse and return as such
//tag represents the name of the operator, initially this library was used to
//encode a filter string into DSML where the tag name would be <DSML:equalityMatch ...>
//so thats why its named tag, if you were wondering...
var operatorStr = "";
var valueOffset=0;
tree.name = "";
tree.value = '';
if(expr.indexOf('~=') !=-1 ){
operatorStr = '~=';
tree.tag = 'approxMatch';
valueOffset = 2;
}else if(expr.indexOf('>=') != -1){
operatorStr = '>=';
tree.tag = 'greaterOrEqual';
valueOffset = 2;
}else if(expr.indexOf('<=') != -1){
operatorStr = '<=';
tree.tag = 'lessOrEqual';
valueOffset = 2;
}else if(expr.indexOf("=") != -1){
operatorStr = '=';
tree.tag = 'equalityMatch';
valueOffset = 1;
}else {
tree.tag = 'present';
}
console.log('operatorStr: ' + operatorStr + '; valueOffset: ' + valueOffset);
if (operatorStr == ""){
tree.name = expr;
}else{
//pull out lhs and rhs of equality operator
//var index = expr.indexOf('=');
var splitAry = expr.split(operatorStr,2);
tree.name =splitAry[0];
tree.value = splitAry[1];
//substrings fall into the equality bin in the switch above
//so we need another check here to look for *
if (tree.value.indexOf('*') != -1 && tree.tag == 'equalityMatch'){
tree.tag = 'substrings';
var split = tree.value.split("*");
//if the value string doesn't start with a * then theres no initial value
//else split will have an empty string in its first array index...
//we need to remove that empty string
if(tree.value.indexOf("*") != 0){
tree.initial = split.shift();
}else{
split.shift();
}
//if the value string doesn't end with a * then theres no final value
//also same split stuff as the initial stuff above
if(tree.value.lastIndexOf("*") != tree.value.length-1){
tree['final'] = split.pop();
}else{
split.pop();
}
tree.any = split;
}
if(tree.value.length == 0){
tree.tag = 'present';
}
}
}
return tree;
};
// recursive function that builds a filter tree from a string expression
// the filter tree is an intermediary step between the incoming expression and
// the outgoing Filter Class structure.
function _buildFilterTree(expr) {
var tree = {};
var split;
var treeToObjs = function(tree,filterObj){
//console.log("in treeToObjs");
if(tree === undefined){
return filterObj;
}
if(tree.length === 0){
return filterObj;
}
//if the current tree object is not an expression then its a logical operator (ie an internal node in the tree)
var currentFilter = filterObj;
if(tree.op != "expr"){
console.log("adding "+tree.op+" to filters");
switch (tree.op){
case "and":
filterObj.addFilter(currentFilter = new AndFilter({filters:[]}));
break;
case "or":
filterObj.addFilter(currentFilter = new OrFilter({filters:[]}));
break;
case "not":
filterObj.addFilter(currentFilter = new NotFilter({filters:[]}));
break;
}
for(var i = 0,tempFilter,child;i<tree.children.length;i++){
child = tree.children[i];
treeToObjs(child,currentFilter);
}
}else{
//else its a leaf node in the tree, and represents some type of non-logical expression
var tempFilter;
//convert the tag name to a filter class type
switch(tree.tag){
case "approxMatch":
tempFilter = new ApproximateFilter({
attribute: tree.name,
value: tree.value
});
//console.log("adding "+tree.tag+"; attr: "+tree.name+"; value: "+tree.value);
break;
case "greaterOrEqual":
tempFilter = new GreaterThanEqualsFilter({
attribute: tree.name,
value: tree.value
});
//console.log("adding "+tree.tag+"; attr: "+tree.name+"; value: "+tree.value);
break;
case "lessOrEqual":
tempFilter = new LessThanEqualsFilter({
attribute: tree.name,
value: tree.value
});
//console.log("adding "+tree.tag+"; attr: "+tree.name+"; value: "+tree.value);
break;
case "equalityMatch":
tempFilter = new EqualityFilter({
attribute: tree.name,
value: tree.value
});
//console.log("adding "+tree.tag+"; attr: "+tree.name+"; value: "+tree.value);
break;
case "substrings":
tempFilter = new SubstringFilter({
attribute: tree.name,
initial: tree.initial,
any: tree.any,
"final": tree["final"]
});
//console.log("adding "+tree.tag+"; attr: "+tree.name+"; initial: "+tree.initial+"; any: "+JSON.stringify(tree.any) + "; final: "+tree['final']);
break;
case "present":
tempFilter = new PresenceFilter({
attribute: tree.name
});
//console.log("adding "+tree.tag+"; attr: "+tree.name);
break;
}
filterObj.addFilter(tempFilter);
}
};
if (expr.length === 0)
return tree;
if (expr.charAt(0) == '(')
expr = expr.substring(1, expr.length - 1);
//store prefix operator
if (expr.charAt(0) === '&') {
tree.op = 'and';
expr = expr.substring(1);
} else if (expr.charAt(0) === '|') {
tree.op = 'or';
expr = expr.substring(1);
} else if (expr.charAt(0) === '!') {
tree.op = 'not';
expr = expr.substring(1);
} else {
tree.op = 'expr';
}
if (tree.op != 'expr') {
var child;
var i = 0;
tree.children = [];
// logical operators are k-ary, so we go until our expression string runs
// out (at least for this recursion level)
var endParen;
while (expr.length !== 0) {
endParen = matchParens(expr);
if (endParen == expr.length - 1) {
tree.children[i] = _buildFilterTree(expr);
expr = '';
} else {
child = expr.slice(0, endParen + 1);
expr = expr.substring(endParen + 1);
tree.children[i] = _buildFilterTree(child);
}
i++;
}
} else {
//else its some sort of non-logical expression, parse and return as such
// tag represents the name of the operator, initially this library was used
// to encode a filter string into DSML where the tag name would be
// <DSML:equalityMatch ...>
// so thats why its named tag, if you were wondering...
var operatorStr = '';
var valueOffset = 0;
tree.name = '';
tree.value = '';
if (expr.indexOf('~=') !== -1) {
operatorStr = '~=';
tree.tag = 'approxMatch';
valueOffset = 2;
} else if (expr.indexOf('>=') !== -1) {
operatorStr = '>=';
tree.tag = 'greaterOrEqual';
valueOffset = 2;
} else if (expr.indexOf('<=') !== -1) {
operatorStr = '<=';
tree.tag = 'lessOrEqual';
valueOffset = 2;
} else if (expr.indexOf(':=') !== -1) {
operatorStr = ':=';
tree.tag = 'extensibleMatch';
valueOffset = 2;
} else if (expr.indexOf('=') !== -1) {
operatorStr = '=';
tree.tag = 'equalityMatch';
valueOffset = 1;
} else {
tree.tag = 'present';
}
if (operatorStr === '') {
tree.name = expr;
} else {
// pull out lhs and rhs of equality operator
var splitAry = expr.split(operatorStr, 2);
tree.name = splitAry[0];
tree.value = splitAry[1];
// substrings and extensible matching fall into the equality bin in the
// switch above so we need more processing here
if (tree.tag === 'equalityMatch') {
if (tree.value.indexOf('*') !== -1) {
tree.tag = 'substrings';
split = tree.value.split('*');
// if the value string doesn't start with a * then theres no initial
// value else split will have an empty string in its first array
// index...
// we need to remove that empty string
if (tree.value.indexOf('*') !== 0) {
tree.initial = split.shift();
} else {
split.shift();
}
//if the value string doesn't end with a * then theres no final value
//also same split stuff as the initial stuff above
if (tree.value.lastIndexOf('*') !== tree.value.length - 1) {
tree['final'] = split.pop();
} else {
split.pop();
}
tree.any = split;
} else if (tree.value.length === 0) {
tree.tag = 'present';
}
} else if (tree.tag == 'extensibleMatch') {
split = tree.name.split(':');
console.log(split)
tree.extensible = {
matchType: split[0],
value: tree.value
};
switch (split.length) {
case 1:
break;
case 2:
if (split[1].toLowerCase() === 'dn') {
tree.extensible.dnAttributes = true;
} else {
tree.extensible.rule = split[1];
}
break;
case 3:
tree.extensible.dnAttributes = true;
tree.extensible.rule = split[2];
break;
default:
throw new Error('Invalid extensible filter');
}
}
}
}
return tree;
}
function _parseString(str){
assert.ok(str);
//create a blank object to pass into treeToObjs
//since its recursive we have to prime it ourselves.
//this gets stripped off before the filter structure is returned
//at the bottom of this function.
var filterObj = new AndFilter({
filters:[]
});
var tree = _buildFilterTree(str);
//console.log("tree built: ",JSON.stringify(tree));
treeToObjs(tree,filterObj);
return filterObj.filters[0];
};
function serializeTree(tree, filter) {
if (tree === undefined || tree.length === 0)
return filter;
// if the current tree object is not an expression then its a logical
// operator (ie an internal node in the tree)
var current = null;
if (tree.op !== 'expr') {
switch (tree.op) {
case 'and':
current = new AndFilter();
break;
case 'or':
current = new OrFilter();
break;
case 'not':
current = new NotFilter();
break;
}
filter.addFilter(current || filter);
if (current || tree.children.length) {
tree.children.forEach(function(child) {
serializeTree(child, current);
});
}
} else {
// else its a leaf node in the tree, and represents some type of
// non-logical expression
var tmp;
// convert the tag name to a filter class type
switch (tree.tag) {
case 'approxMatch':
tmp = new ApproximateFilter({
attribute: tree.name,
value: tree.value
});
break;
case 'extensibleMatch':
tmp = new ExtensibleFilter(tree.extensible);
break;
case 'greaterOrEqual':
tmp = new GreaterThanEqualsFilter({
attribute: tree.name,
value: tree.value
});
break;
case 'lessOrEqual':
tmp = new LessThanEqualsFilter({
attribute: tree.name,
value: tree.value
});
break;
case 'equalityMatch':
tmp = new EqualityFilter({
attribute: tree.name,
value: tree.value
});
break;
case 'substrings':
tmp = new SubstringFilter({
attribute: tree.name,
initial: tree.initial,
any: tree.any,
'final': tree['final']
});
break;
case 'present':
tmp = new PresenceFilter({
attribute: tree.name
});
break;
}
filter.addFilter(tmp);
}
}
function _parseString(str) {
assert.ok(str);
// create a blank object to pass into treeToObjs
// since its recursive we have to prime it ourselves.
// this gets stripped off before the filter structure is returned
// at the bottom of this function.
var filterObj = new AndFilter({
filters: []
});
serializeTree(_buildFilterTree(str), filterObj);
return filterObj.filters[0];
}
/*
* A filter looks like this coming in:

View File

@ -425,14 +425,14 @@ function Server(options) {
log.trace('data on %s: %s', c.ldap.id, util.inspect(data));
try {
c.parser.write(data);
c.parser.write(data);
} catch (e) {
log.warn('Unable to parse message [c.on(\'data\')]: %s', e.stack);
c.end(new LDAPResult({
status: 0x02,
errorMessage: e.toString(),
connection: c
}).toBer());
log.warn('Unable to parse message [c.on(\'data\')]: %s', e.stack);
c.end(new LDAPResult({
status: 0x02,
errorMessage: e.toString(),
connection: c
}).toBer());
}
});

View File

@ -108,3 +108,4 @@ test('parse RFC example 5', function(t) {
t.ok(f.dnAttributes);
t.end();
});