Move filter parsing into separate module

This commit is contained in:
Patrick Mooney 2014-10-16 10:36:52 -05:00
parent 9b8244e568
commit 22b04f3a94
16 changed files with 130 additions and 1215 deletions

View File

@ -3,75 +3,22 @@
var assert = require('assert');
var util = require('util');
var Filter = require('./filter');
var parents = require('ldap-filter');
var Protocol = require('../protocol');
var Filter = require('./filter');
///--- API
function AndFilter(options) {
if (typeof (options) === 'object') {
if (!options.filters || !Array.isArray(options.filters))
throw new TypeError('options.filters ([Filter]) required');
this.filters = options.filters.slice();
} else {
options = {};
}
options.type = Protocol.FILTER_AND;
Filter.call(this, options);
if (!this.filters)
this.filters = [];
var self = this;
this.__defineGetter__('json', function () {
return {
type: 'And',
filters: self.filters || []
};
});
parents.AndFilter.call(this, options);
}
util.inherits(AndFilter, Filter);
util.inherits(AndFilter, parents.AndFilter);
Filter.mixin(AndFilter);
module.exports = AndFilter;
AndFilter.prototype.toString = function () {
var str = '(&';
this.filters.forEach(function (f) {
str += f.toString();
});
str += ')';
return str;
};
AndFilter.prototype.matches = function (target) {
if (typeof (target) !== 'object')
throw new TypeError('target (object) required');
var matches = this.filters.length ? true : false;
for (var i = 0; i < this.filters.length; i++) {
if (!this.filters[i].matches(target))
return false;
}
return matches;
};
AndFilter.prototype.addFilter = function (filter) {
if (!filter || typeof (filter) !== 'object')
throw new TypeError('filter (object) required');
this.filters.push(filter);
};
AndFilter.prototype._toBer = function (ber) {
assert.ok(ber);

View File

@ -3,67 +3,22 @@
var assert = require('assert');
var util = require('util');
var escape = require('./escape').escape;
var parents = require('ldap-filter');
var Filter = require('./filter');
var Protocol = require('../protocol');
///--- API
function ApproximateFilter(options) {
if (typeof (options) === 'object') {
if (!options.attribute || typeof (options.attribute) !== 'string')
throw new TypeError('options.attribute (string) required');
if (!options.value || typeof (options.value) !== 'string')
throw new TypeError('options.value (string) required');
this.attribute = options.attribute;
this.value = options.value;
} else {
options = {};
}
options.type = Protocol.FILTER_APPROX;
Filter.call(this, options);
var self = this;
this.__defineGetter__('json', function () {
return {
type: 'ApproximateMatch',
attribute: self.attribute || undefined,
value: self.value || undefined
};
});
parents.ApproximateFilter.call(this, options);
}
util.inherits(ApproximateFilter, Filter);
util.inherits(ApproximateFilter, parents.ApproximateFilter);
Filter.mixin(ApproximateFilter);
module.exports = ApproximateFilter;
ApproximateFilter.prototype.toString = function () {
return '(' + escape(this.attribute) + '~=' + escape(this.value) + ')';
};
ApproximateFilter.prototype.matches = function (target) {
if (typeof (target) !== 'object')
throw new TypeError('target (object) required');
var matches = false;
var tv = Filter.get_attr_caseless(target, this.attribute);
if (tv !== null) {
if (Array.isArray(tv)) {
matches = (tv.indexOf(this.value) != -1);
} else {
matches = (this.value === tv);
}
}
return matches;
};
ApproximateFilter.prototype.parse = function (ber) {
assert.ok(ber);

View File

@ -4,87 +4,22 @@ var assert = require('assert');
var util = require('util');
var ASN1 = require('asn1').Ber;
var escape = require('./escape').escape;
var parents = require('ldap-filter');
var Filter = require('./filter');
var Protocol = require('../protocol');
///--- API
function EqualityFilter(options) {
if (typeof (options) === 'object') {
if (!options.attribute || typeof (options.attribute) !== 'string')
throw new TypeError('options.attribute (string) required');
if (!options.value)
throw new TypeError('options.value required');
} else {
this.raw = new Buffer(0);
options = {};
}
options.type = Protocol.FILTER_EQUALITY;
Filter.call(this, options);
var self = this;
this.__defineGetter__('value', function () {
return self.raw.toString();
});
this.__defineSetter__('value', function (data) {
if (typeof (data) === 'string') {
self.raw = new Buffer(data);
} else if (Buffer.isBuffer(data)) {
self.raw = new Buffer(data.length);
data.copy(self.raw);
} else {
throw new TypeError('value (string|buffer) required');
}
});
this.__defineGetter__('json', function () {
return {
type: 'EqualityMatch',
attribute: self.attribute || undefined,
value: self.value || undefined
};
});
if (options.attribute !== undefined && options.value !== undefined) {
this.attribute = options.attribute;
this.value = options.value;
}
parents.EqualityFilter.call(this, options);
}
util.inherits(EqualityFilter, Filter);
util.inherits(EqualityFilter, parents.EqualityFilter);
Filter.mixin(EqualityFilter);
module.exports = EqualityFilter;
EqualityFilter.prototype.toString = function () {
return '(' + escape(this.attribute) + '=' + escape(this.value) + ')';
};
EqualityFilter.prototype.matches = function (target) {
if (typeof (target) !== 'object')
throw new TypeError('target (object) required');
var self = this;
var tv = Filter.get_attr_caseless(target, this.attribute);
if (tv !== null) {
var value = this.value;
return Filter.multi_test(
function (v) {
if (self.attribute === 'objectclass')
v = v.toLowerCase();
return value === v;
},
tv);
}
return false;
};
EqualityFilter.prototype.parse = function (ber) {
assert.ok(ber);

View File

@ -3,132 +3,27 @@
var assert = require('assert');
var util = require('util');
var parents = require('ldap-filter');
var Filter = require('./filter');
var Protocol = require('../protocol');
var SubstringFilter = require('./substr_filter');
// THIS IS A STUB!
//
// ldapjs does not support server side extensible matching.
// This class exists only for the client to send them.
///--- API
function ExtensibleFilter(options) {
if (typeof (options) === 'object') {
if (options.rule && typeof (options.rule) !== 'string')
throw new TypeError('options.rule must be a string');
if (options.matchType && typeof (options.matchType) !== 'string')
throw new TypeError('options.type must be a string');
if (options.value && typeof (options.value) !== 'string')
throw new TypeError('options.value (string) required');
} else {
options = {};
}
this.any = options.any || null;
this.attribute = options.matchType || null;
this['final'] = options['final'] || null;
this.initial = options.initial || null;
this.matchType = options.matchType || null;
this.rule = options.rule || null;
this.value = options.value || '';
this.dnAttributes = options.dnAttributes || false;
options.type = Protocol.FILTER_EXT;
Filter.call(this, options);
var self = this;
this.__defineGetter__('json', function () {
return {
type: 'ExtensibleMatch',
matchRule: self.rule,
matchType: self.matchType,
matchValue: self.value,
dnAttributes: self.dnAttributes
};
});
this.__defineGetter__('matchingRule', function () {
return self.rule;
});
this.__defineGetter__('matchValue', function () {
return self.value;
});
parents.ExtensibleFilter.call(this, options);
}
util.inherits(ExtensibleFilter, Filter);
util.inherits(ExtensibleFilter, parents.ExtensibleFilter);
Filter.mixin(ExtensibleFilter);
module.exports = ExtensibleFilter;
ExtensibleFilter.prototype.toString = function () {
var str = '(';
if (this.matchType)
str += this.matchType;
str += ':';
if (this.dnAttributes)
str += 'dn:';
if (this.rule)
str += this.rule + ':';
return (str + '=' + this.value + ')');
};
/**
* THIS IS A STUB!
*
* ldapjs does not support server side extensible matching. This class exists
* only for the client to send them.
*
* @param {Object} target the target object.
* @return {Boolean} false always.
*/
ExtensibleFilter.prototype.matches = function (target) {
if (typeof (target) !== 'object')
throw new TypeError('target (object) required');
if (this.dnAttribute)
throw new Error('ExtensibleMatch dnAttributes not supported');
if (!this.value)
return false;
var self = this;
var tv = Filter.get_attr_caseless(target, this.matchType);
if (this.matchType && tv !== null) {
if (self.rule === '2.5.13.4' || self.rule === 'caseIgnoreSubstringsMatch') {
var f = {
attribute: self.matchType,
initial: self.initial ? self.initial.toLowerCase() : undefined,
any: self.any ? self.any.map(function (a) {
return a.toLowerCase();
}) : undefined,
'final': self['final'] ? self['final'].toLowerCase() : undefined
};
}
return Filter.multi_test(function (v) {
if (self.rule === '2.5.13.2' || self.rule === 'caseIgnoreMatch') {
return self.value.toLowerCase() === v.toLowerCase();
} else if (self.rule === '2.5.13.4' ||
self.rule === 'caseIgnoreSubstringsMatch') {
var t = {};
t[self.matchType] = v.toLowerCase();
return SubstringFilter.prototype.matches.call(f, t);
}
return self.value === v;
}, tv);
}
return false;
};
ExtensibleFilter.prototype.parse = function (ber) {
var end = ber.offset + ber.length;
while (ber.offset < end) {

View File

@ -4,106 +4,55 @@ var assert = require('assert');
var asn1 = require('asn1');
var Protocol = require('../protocol');
///--- Globals
var BerWriter = asn1.BerWriter;
var TYPES = {
'and': Protocol.FILTER_AND,
'or': Protocol.FILTER_OR,
'not': Protocol.FILTER_NOT,
'equal': Protocol.FILTER_EQUALITY,
'substring': Protocol.FILTER_SUBSTRINGS,
'ge': Protocol.FILTER_GE,
'le': Protocol.FILTER_LE,
'present': Protocol.FILTER_PRESENT,
'approx': Protocol.FILTER_APPROX,
'ext': Protocol.FILTER_EXT
};
///--- API
function Filter(options) {
if (!options || typeof (options) !== 'object')
throw new TypeError('options (object) required');
if (typeof (options.type) !== 'number')
throw new TypeError('options.type (number) required');
this._type = options.type;
var self = this;
this.__defineGetter__('type', function () {
switch (self._type) {
case Protocol.FILTER_AND: return 'and';
case Protocol.FILTER_OR: return 'or';
case Protocol.FILTER_NOT: return 'not';
case Protocol.FILTER_EQUALITY: return 'equal';
case Protocol.FILTER_SUBSTRINGS: return 'substring';
case Protocol.FILTER_GE: return 'ge';
case Protocol.FILTER_LE: return 'le';
case Protocol.FILTER_PRESENT: return 'present';
case Protocol.FILTER_APPROX: return 'approx';
case Protocol.FILTER_EXT: return 'ext';
default:
throw new Error('0x' + self._type.toString(16) +
' is an invalid search filter');
}
});
}
Filter.isFilter = function isFilter(filter) {
function isFilter(filter) {
if (!filter || typeof (filter) !== 'object') {
return false;
}
// Do our best to duck-type it
if ((filter instanceof Filter) || (
typeof (filter.toBer) === 'functin' &&
if (typeof (filter.toBer) === 'function' &&
typeof (filter.matches) === 'function' &&
typeof (filter._type) === 'number')) {
TYPES[filter.type] !== undefined) {
return true;
}
return false;
};
module.exports = Filter;
Filter.prototype.toBer = function (ber) {
if (!ber || !(ber instanceof BerWriter))
throw new TypeError('ber (BerWriter) required');
ber.startSequence(this._type);
ber = this._toBer(ber);
ber.endSequence();
return ber;
};
// Test a rule against one or more values.
Filter.multi_test = function (rule, value) {
if (Array.isArray(value)) {
var response = false;
for (var i = 0; i < value.length; i++) {
if (rule(value[i])) {
response = true;
break;
}
}
return response;
} else {
return rule(value);
}
};
// Search object for attribute, insensitive to case
Filter.get_attr_caseless = function (target, attr) {
// Check for exact case match first
if (target.hasOwnProperty(attr)) {
return target[attr];
}
// Perform case-insensitive enumeration after that
var lower = attr.toLowerCase();
var result = null;
Object.getOwnPropertyNames(target).some(function (item) {
if (item.toLowerCase() == lower) {
result = target[item];
return true;
}
return false;
});
return result;
}
function mixin(target) {
target.prototype.toBer = function toBer(ber) {
if (!ber || !(ber instanceof BerWriter))
throw new TypeError('ber (BerWriter) required');
ber.startSequence(TYPES[this.type]);
ber = this._toBer(ber);
ber.endSequence();
return ber;
};
}
module.exports = {
isFilter: isFilter,
mixin: mixin
};

View File

@ -3,66 +3,21 @@
var assert = require('assert');
var util = require('util');
var escape = require('./escape').escape;
var parents = require('ldap-filter');
var Filter = require('./filter');
var Protocol = require('../protocol');
///--- API
function GreaterThanEqualsFilter(options) {
if (typeof (options) === 'object') {
if (!options.attribute || typeof (options.attribute) !== 'string')
throw new TypeError('options.attribute (string) required');
if (!options.value || typeof (options.value) !== 'string')
throw new TypeError('options.value (string) required');
this.attribute = options.attribute;
this.value = options.value;
} else {
options = {};
}
options.type = Protocol.FILTER_GE;
Filter.call(this, options);
var self = this;
this.__defineGetter__('json', function () {
return {
type: 'GreaterThanEqualsMatch',
attribute: self.attribute || undefined,
value: self.value || undefined
};
});
parents.GreaterThanEqualsFilter.call(this, options);
}
util.inherits(GreaterThanEqualsFilter, Filter);
util.inherits(GreaterThanEqualsFilter, parents.GreaterThanEqualsFilter);
Filter.mixin(GreaterThanEqualsFilter);
module.exports = GreaterThanEqualsFilter;
GreaterThanEqualsFilter.prototype.toString = function () {
return '(' + escape(this.attribute) + '>=' + escape(this.value) + ')';
};
GreaterThanEqualsFilter.prototype.matches = function (target) {
if (typeof (target) !== 'object')
throw new TypeError('target (object) required');
var tv = Filter.get_attr_caseless(target, this.attribute);
if (tv !== null) {
var value = this.value;
return Filter.multi_test(
function (v) { return value <= v; },
tv);
}
return false;
};
GreaterThanEqualsFilter.prototype.parse = function (ber) {
assert.ok(ber);

View File

@ -4,6 +4,8 @@ var assert = require('assert');
var asn1 = require('asn1');
var parents = require('ldap-filter');
var Protocol = require('../protocol');
var Filter = require('./filter');
@ -25,400 +27,8 @@ var SubstringFilter = require('./substr_filter');
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 = [];
var esc = false;
for (var i = openParenIndex || 0; i < str.length; i++) {
var c = str[i];
if (c === '\\') {
if (!esc)
esc = true;
continue;
} else if (c === '(' && !esc) {
stack.push(1);
} else if (c === ')' && !esc) {
stack.pop();
if (stack.length === 0)
return i;
}
esc = false;
}
var ndx = str.length - 1;
if (str.charAt(ndx) !== ')')
throw new Error(str + ' has unbalanced parentheses');
return ndx;
}
function parse_substr(tree) {
// Effectively a hand-rolled .shift() to support \* sequences
var clean = true;
var esc = false;
var obj = {};
var split = [];
var substrNdx = 0;
split[substrNdx] = '';
for (var i = 0; i < tree.value.length; i++) {
var c = tree.value[i];
if (esc) {
split[substrNdx] += c;
esc = false;
} else if (c === '*') {
split[++substrNdx] = '';
} else if (c === '\\') {
esc = true;
} else {
split[substrNdx] += c;
}
}
if (split.length > 1) {
obj.tag = 'substrings';
clean = true;
// 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) {
obj.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) {
obj['final'] = split.pop();
} else {
split.pop();
}
obj.any = split;
} else {
obj.value = split[0]; // pick up the cleaned version
}
obj.clean = clean;
obj.esc = esc;
return obj;
}
// 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 c;
var child;
var clean = false;
var endParen;
var esc = false;
var i = 0;
var obj;
var tree = {};
var split;
var val = '';
if (expr.length === 0)
return tree;
// Chop the parens (the call to matchParens below gets rid of the trailer)
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') {
tree.children = [];
// logical operators are k-ary, so we go until our expression string runs
// out (at least for this recursion level)
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
var operatorStr = '';
tree.name = '';
tree.value = '';
// This parses and enforces filter syntax, which is an AttributeDescription
// plus a filter operator, followed by (for ldapjs), anything. Note
// that ldapjs additionally allows the '_' character in the AD, as many
// users rely on it, even though it's non-standard
//
// From 4.1.5 of RFC251
//
// AttributeDescription ::= LDAPString
//
// A value of AttributeDescription is based on the following BNF:
//
// <AttributeDescription> ::= <AttributeType> [ ";" <options> ]
//
// <options> ::= <option> | <option> ";" <options>
//
// <option> ::= <opt-char> <opt-char>*
//
// <opt-char> ::= ASCII-equivalent letters, numbers and hyphen
//
// Examples of valid AttributeDescription:
//
// cn
// userCertificate;binary
/* JSSTYLED */
if (!/[a-zA-Z0-9;_\-]+[~><:]?=.+/.test(expr))
throw new Error(expr + ' is invalid');
if (expr.indexOf('~=') !== -1) {
operatorStr = '~=';
tree.tag = 'approxMatch';
} else if (expr.indexOf('>=') !== -1) {
operatorStr = '>=';
tree.tag = 'greaterOrEqual';
} else if (expr.indexOf('<=') !== -1) {
operatorStr = '<=';
tree.tag = 'lessOrEqual';
} else if (expr.indexOf(':=') !== -1) {
operatorStr = ':=';
tree.tag = 'extensibleMatch';
} else if (expr.indexOf('=') !== -1) {
operatorStr = '=';
tree.tag = 'equalityMatch';
} else {
// tree.tag = 'present';
throw new Error('invalid filter syntax');
}
if (operatorStr === '') {
tree.name = expr;
} else {
// pull out lhs and rhs of equality operator
var splitAry = expr.split(operatorStr);
tree.name = splitAry.shift();
tree.value = splitAry.join(operatorStr);
// substrings fall into the equality bin in the
// switch above so we need more processing here
if (tree.tag === 'equalityMatch') {
if (tree.value === '*') {
tree.tag = 'present';
} else {
obj = parse_substr(tree);
tree.initial = obj.initial;
tree.any = obj.any;
tree['final'] = obj['final'];
tree.tag = obj.tag || tree.tag;
tree.value = obj.value;
esc = obj.esc;
clean = obj.clean;
}
} else if (tree.tag == 'extensibleMatch') {
split = tree.name.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');
}
switch (tree.extensible.rule) {
case '2.5.13.4':
case 'caseIgnoreSubstringsMatch':
tree.extensible.attribute = tree.extensible.matchType;
obj = parse_substr(tree);
tree.extensible.initial = obj.initial;
tree.extensible.any = obj.any;
tree.extensible['final'] = obj['final'];
tree.value = obj.value;
esc = obj.esc;
clean = obj.clean;
break;
case '2.5.13.2':
case 'caseIgnoreMatch':
tree.extensible.attribute = tree.extensible.matchType;
break;
default:
// noop
break;
}
}
}
// Cleanup any escape sequences
if (!clean) {
for (i = 0; i < tree.value.length; i++) {
c = tree.value[i];
if (esc) {
val += c;
esc = false;
} else if (c === '\\') {
esc = true;
} else {
val += c;
}
}
tree.value = val;
}
}
return tree;
}
function serializeTree(tree, filter) {
if (tree === undefined || tree.length === 0)
return;
// 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;
default:
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;
default:
break;
}
if (tmp)
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:
* Filter ::= CHOICE {
@ -528,11 +138,51 @@ function _parse(ber) {
}
function cloneFilter(input) {
var child;
if (input.type === 'and' || input.type === 'or') {
child = input.filters.map(cloneFilter);
} else if (input.type === 'not') {
child = cloneFilter(input.filter);
}
switch (input.type) {
case 'and':
return new AndFilter({filters: child});
case 'or':
return new OrFilter({filters: child});
case 'not':
return new NotFilter({filter: child});
case 'equal':
return new EqualityFilter(input);
case 'substring':
return new SubstringFilter(input);
case 'ge':
return new GreaterThanEqualsFilter(input);
case 'le':
return new LessThanEqualsFilter(input);
case 'present':
return new PresenceFilter(input);
case 'approx':
return new ApproximateFilter(input);
case 'ext':
return new ExtensibleFilter(input);
default:
throw new Error('invalid filter type:' + input.type);
}
}
function parseString(str) {
var generic = parents.parse(str);
// The filter object(s) return from ldap-filter.parse lack the toBer/parse
// decoration that native ldapjs filter possess. cloneFilter adds that back.
return cloneFilter(generic);
}
///--- API
module.exports = {
parse: function (ber) {
if (!ber || !(ber instanceof BerReader))
throw new TypeError('ber (BerReader) required');
@ -540,12 +190,7 @@ module.exports = {
return _parse(ber);
},
parseString: function (filter) {
if (!filter || typeof (filter) !== 'string')
throw new TypeError('filter (string) required');
return _parseString(filter);
},
parseString: parseString,
isFilter: Filter.isFilter,
@ -558,6 +203,5 @@ module.exports = {
NotFilter: NotFilter,
OrFilter: OrFilter,
PresenceFilter: PresenceFilter,
SubstringFilter: SubstringFilter,
Filter: Filter
SubstringFilter: SubstringFilter
};

View File

@ -3,66 +3,21 @@
var assert = require('assert');
var util = require('util');
var escape = require('./escape').escape;
var parents = require('ldap-filter');
var Filter = require('./filter');
var Protocol = require('../protocol');
///--- API
function LessThanEqualsFilter(options) {
if (typeof (options) === 'object') {
if (!options.attribute || typeof (options.attribute) !== 'string')
throw new TypeError('options.attribute (string) required');
if (!options.value || typeof (options.value) !== 'string')
throw new TypeError('options.value (string) required');
this.attribute = options.attribute;
this.value = options.value;
} else {
options = {};
}
options.type = Protocol.FILTER_LE;
Filter.call(this, options);
var self = this;
this.__defineGetter__('json', function () {
return {
type: 'LessThanEqualsMatch',
attribute: self.attribute || undefined,
value: self.value || undefined
};
});
parents.LessThanEqualsFilter.call(this, options);
}
util.inherits(LessThanEqualsFilter, Filter);
util.inherits(LessThanEqualsFilter, parents.LessThanEqualsFilter);
Filter.mixin(LessThanEqualsFilter);
module.exports = LessThanEqualsFilter;
LessThanEqualsFilter.prototype.toString = function () {
return '(' + escape(this.attribute) + '<=' + escape(this.value) + ')';
};
LessThanEqualsFilter.prototype.matches = function (target) {
if (typeof (target) !== 'object')
throw new TypeError('target (object) required');
var tv = Filter.get_attr_caseless(target, this.attribute);
if (tv !== null) {
var value = this.value;
return Filter.multi_test(
function (v) { return value >= v; },
tv);
}
return false;
};
LessThanEqualsFilter.prototype.parse = function (ber) {
assert.ok(ber);

View File

@ -3,55 +3,21 @@
var assert = require('assert');
var util = require('util');
var parents = require('ldap-filter');
var Filter = require('./filter');
var Protocol = require('../protocol');
///--- API
function NotFilter(options) {
if (typeof (options) === 'object') {
if (!options.filter || !Filter.isFilter(options.filter))
throw new TypeError('options.filter (Filter) required');
} else {
options = {};
}
this.filter = options.filter || {};
options.type = Protocol.FILTER_NOT;
Filter.call(this, options);
var self = this;
this.__defineGetter__('json', function () {
return {
type: 'Not',
filter: self.filter
};
});
parents.NotFilter.call(this, options);
}
util.inherits(NotFilter, Filter);
util.inherits(NotFilter, parents.NotFilter);
Filter.mixin(NotFilter);
module.exports = NotFilter;
NotFilter.prototype.addFilter = function (f) {
if (!Filter.isFilter(f))
throw new TypeError('filter (Filter) required');
this.filter = f;
};
NotFilter.prototype.toString = function () {
return '(!' + this.filter.toString() + ')';
};
NotFilter.prototype.matches = function (target) {
return !this.filter.matches(target);
};
NotFilter.prototype._toBer = function (ber) {
assert.ok(ber);

View File

@ -3,73 +3,21 @@
var assert = require('assert');
var util = require('util');
var parents = require('ldap-filter');
var Filter = require('./filter');
var Protocol = require('../protocol');
///--- API
function OrFilter(options) {
if (typeof (options) === 'object') {
if (!options.filters || !Array.isArray(options.filters))
throw new TypeError('options.filters ([Filter]) required');
this.filters = options.filters.slice();
} else {
options = {};
}
options.type = Protocol.FILTER_OR;
Filter.call(this, options);
if (!this.filters)
this.filters = [];
var self = this;
this.__defineGetter__('json', function () {
return {
type: 'Or',
filters: self.filters || []
};
});
parents.OrFilter.call(this, options);
}
util.inherits(OrFilter, Filter);
util.inherits(OrFilter, parents.OrFilter);
Filter.mixin(OrFilter);
module.exports = OrFilter;
OrFilter.prototype.toString = function () {
var str = '(|';
this.filters.forEach(function (f) {
str += f.toString();
});
str += ')';
return str;
};
OrFilter.prototype.matches = function (target) {
if (typeof (target) !== 'object')
throw new TypeError('target (object) required');
for (var i = 0; i < this.filters.length; i++) {
if (this.filters[i].matches(target))
return true;
}
return false;
};
OrFilter.prototype.addFilter = function (filter) {
if (!filter || typeof (filter) !== 'object')
throw new TypeError('filter (object) required');
this.filters.push(filter);
};
OrFilter.prototype._toBer = function (ber) {
assert.ok(ber);

View File

@ -3,51 +3,21 @@
var assert = require('assert');
var util = require('util');
var escape = require('./escape').escape;
var parents = require('ldap-filter');
var Filter = require('./filter');
var Protocol = require('../protocol');
///--- API
function PresenceFilter(options) {
if (typeof (options) === 'object') {
if (!options.attribute || typeof (options.attribute) !== 'string')
throw new TypeError('options.attribute (string) required');
this.attribute = options.attribute;
} else {
options = {};
}
options.type = Protocol.FILTER_PRESENT;
Filter.call(this, options);
var self = this;
this.__defineGetter__('json', function () {
return {
type: 'PresenceMatch',
attribute: self.attribute || undefined
};
});
parents.PresenceFilter.call(this, options);
}
util.inherits(PresenceFilter, Filter);
util.inherits(PresenceFilter, parents.PresenceFilter);
Filter.mixin(PresenceFilter);
module.exports = PresenceFilter;
PresenceFilter.prototype.toString = function () {
return '(' + escape(this.attribute) + '=*)';
};
PresenceFilter.prototype.matches = function (target) {
if (typeof (target) !== 'object')
throw new TypeError('target (object) required');
return (Filter.get_attr_caseless(target, this.attribute) !== null);
};
PresenceFilter.prototype.parse = function (ber) {
assert.ok(ber);

View File

@ -3,107 +3,21 @@
var assert = require('assert');
var util = require('util');
var escape = require('./escape').escape;
var parents = require('ldap-filter');
var Filter = require('./filter');
var Protocol = require('../protocol');
///--- API
function SubstringFilter(options) {
if (typeof (options) === 'object') {
if (!options.attribute || typeof (options.attribute) !== 'string')
throw new TypeError('options.attribute (string) required');
this.attribute = options.attribute;
this.initial = options.initial ? options.initial : null;
this.any = options.any ? options.any.slice(0) : [];
this['final'] = options['final'] || null;
} else {
options = {};
}
if (!this.any)
this.any = [];
options.type = Protocol.FILTER_SUBSTRINGS;
Filter.call(this, options);
var self = this;
this.__defineGetter__('json', function () {
return {
type: 'SubstringMatch',
initial: self.initial || undefined,
any: self.any || undefined,
'final': self['final'] || undefined
};
});
parents.SubstringFilter.call(this, options);
}
util.inherits(SubstringFilter, Filter);
util.inherits(SubstringFilter, parents.SubstringFilter);
Filter.mixin(SubstringFilter);
module.exports = SubstringFilter;
SubstringFilter.prototype.toString = function () {
var str = '(' + escape(this.attribute) + '=';
if (this.initial)
str += escape(this.initial);
str += '*';
this.any.forEach(function (s) {
str += escape(s) + '*';
});
if (this['final'])
str += escape(this['final']);
str += ')';
return str;
};
function escapeRegExp(str) {
/* JSSTYLED */
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
}
SubstringFilter.prototype.matches = function (target) {
if (typeof (target) !== 'object')
throw new TypeError('target (object) required');
var tv = Filter.get_attr_caseless(target, this.attribute);
if (tv !== null) {
var re = '';
if (this.initial)
re += '^' + escapeRegExp(this.initial) + '.*';
this.any.forEach(function (s) {
re += escapeRegExp(s) + '.*';
});
if (this['final'])
re += escapeRegExp(this['final']) + '$';
var matcher = new RegExp(re);
var self = this;
return Filter.multi_test(
function (v) {
if (self.attribute === 'objectclass')
v = v.toLowerCase();
return matcher.test(v);
},
tv);
}
return false;
};
SubstringFilter.prototype.parse = function (ber) {
assert.ok(ber);
@ -126,9 +40,9 @@ SubstringFilter.prototype.parse = function (ber) {
this.any.push(anyVal);
break;
case 0x82: // Final
this['final'] = ber.readString(tag);
this.final = ber.readString(tag);
if (this.attribute === 'objectclass')
this['final'] = this['final'].toLowerCase();
this.final = this.final.toLowerCase();
break;
default:
throw new Error('Invalid substrings filter type: 0x' + tag.toString(16));
@ -153,8 +67,8 @@ SubstringFilter.prototype._toBer = function (ber) {
ber.writeString(s, 0x81);
});
if (this['final'])
ber.writeString(this['final'], 0x82);
if (this.final)
ber.writeString(this.final, 0x82);
ber.endSequence();

View File

@ -32,6 +32,7 @@
"bunyan": "0.23.1",
"dashdash": "1.7.0",
"backoff": "2.4.0",
"ldap-filter": "0.1.1",
"once": "1.3.0",
"vasync": "1.5.0",
"verror": "~1.4.0"

View File

@ -59,39 +59,6 @@ test('GH-109 = escape value only in toString()', function (t) {
});
test('match true', function (t) {
var f = new ApproximateFilter({
attribute: 'foo',
value: 'bar'
});
t.ok(f);
t.ok(f.matches({ foo: 'bar' }));
t.end();
});
test('match multiple', function (t) {
var f = new ApproximateFilter({
attribute: 'foo',
value: 'bar'
});
t.ok(f);
t.ok(f.matches({ foo: ['steak', 'bar']}));
t.ok(!f.matches({ foo: ['nihhh', 'rabbit']}));
t.end();
});
test('match false', function (t) {
var f = new ApproximateFilter({
attribute: 'foo',
value: 'bar'
});
t.ok(f);
t.ok(!f.matches({ foo: 'baz' }));
t.end();
});
test('parse ok', function (t) {
var writer = new BerWriter();
writer.writeString('foo');
@ -100,7 +67,6 @@ test('parse ok', function (t) {
var f = new ApproximateFilter();
t.ok(f);
t.ok(f.parse(new BerReader(writer.buffer)));
t.ok(f.matches({ foo: 'bar' }));
t.end();
});

View File

@ -108,26 +108,3 @@ test('parse RFC example 5', function (t) {
t.ok(f.dnAttributes);
t.end();
});
test('parse caseIgnore', function (t) {
var f = filters.parseString('(cn:caseIgnoreMatch:=Dino)');
t.ok(f);
t.ok(f.matchType);
t.equal(f.matchingRule, 'caseIgnoreMatch');
t.equal(f.matchValue, 'Dino');
t.ok(f.matches({cn: 'dino'}));
t.end();
});
test('parse case substrings', function (t) {
var f = filters.parseString('(cn:caseIgnoreSubstringsMatch:=*i*o)');
t.ok(f);
t.ok(f.matchType);
t.equal(f.matchingRule, 'caseIgnoreSubstringsMatch');
t.ok(f.any);
t.ok(f['final']);
t.ok(f.matches({cn: 'dino'}));
t.end();
});

View File

@ -1,62 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tape').test;
///--- Globals
var Filter;
///--- Tests
test('load library', function (t) {
var filters = require('../../lib/index').filters;
t.ok(filters);
Filter = filters.Filter;
t.ok(Filter);
t.end();
});
test('multi_test array', function (t) {
var rule = function (item) {
return (item == 3);
};
t.ok(Filter.multi_test(rule, [1, 2, 3]));
t.ok(!Filter.multi_test(rule, [1, 2]));
t.end();
});
test('multi_test value', function (t) {
var rule = function (item) {
return (item == 3);
};
t.ok(Filter.multi_test(rule, 3));
t.ok(!Filter.multi_test(rule, 1));
t.end();
});
test('get_attr_caseless exact match', function (t) {
var f = Filter.get_attr_caseless;
t.equal(f({attr: 'testval'}, 'attr'), 'testval');
t.equal(f({attr: 'testval'}, 'missing'), null);
t.end();
});
test('get_attr_caseless insensitive match', function (t) {
var f = Filter.get_attr_caseless;
var data = {
lower: 'lower',
UPPER: 'upper',
MiXeD: 'mixed'
};
t.equal(f(data, 'lower'), 'lower');
t.equal(f(data, 'upper'), 'upper');
t.equal(f(data, 'mixed'), 'mixed');
t.equal(f(data, 'missing'), null);
t.end();
});