From aa633dfd2027c9db0f76cdfd68f49e0ca320227d Mon Sep 17 00:00:00 2001 From: Mark Cavage Date: Mon, 28 Oct 2013 21:12:12 +0000 Subject: [PATCH] support extensible matching of caseIgnore and caseIgnoreSubstrings --- lib/client/client.js | 6 +- lib/filters/ext_filter.js | 27 +++++++- lib/filters/index.js | 135 +++++++++++++++++++++++++------------- lib/url.js | 1 + test/filters/ext.test.js | 23 +++++++ 5 files changed, 142 insertions(+), 50 deletions(-) diff --git a/lib/client/client.js b/lib/client/client.js index 9dda196..a90ed19 100644 --- a/lib/client/client.js +++ b/lib/client/client.js @@ -692,7 +692,7 @@ Client.prototype.unbind = function unbind(callback) { var req = new UnbindRequest(); if (this.socket.listeners('error').length === 0) { - this.socket.once('error', function(){}); + this.socket.once('error', function () {}); } return this._send(req, 'unbind', null, callback); }; @@ -725,7 +725,9 @@ Client.prototype._connect = function _connect() { self.emit('connect', socket); } - socket = proto.connect((this.port || this.socketPath), this.host, this.secure ? this.tlsOptions : null); + socket = proto.connect((this.port || this.socketPath), + this.host, + this.secure ? this.tlsOptions : null); socket.once('connect', onConnect); socket.once('secureConnect', onConnect); diff --git a/lib/filters/ext_filter.js b/lib/filters/ext_filter.js index 339fbda..54446b1 100644 --- a/lib/filters/ext_filter.js +++ b/lib/filters/ext_filter.js @@ -6,6 +6,7 @@ var util = require('util'); var Filter = require('./filter'); var Protocol = require('../protocol'); +var SubstringFilter = require('./substr_filter'); @@ -23,6 +24,10 @@ function ExtensibleFilter(options) { 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 || ''; @@ -90,9 +95,29 @@ ExtensibleFilter.prototype.matches = function (target) { var self = this; if (this.matchType && target.hasOwnProperty(this.matchType)) { + + 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') + 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; }, target[this.matchType]); diff --git a/lib/filters/index.js b/lib/filters/index.js index 2951c4d..336ae38 100644 --- a/lib/filters/index.js +++ b/lib/filters/index.js @@ -60,6 +60,62 @@ function matchParens(str, openParenIndex) { } +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. @@ -70,9 +126,9 @@ function _buildFilterTree(expr) { var endParen; var esc = false; var i = 0; + var obj; var tree = {}; var split; - var substrNdx = 0; var val = ''; if (expr.length === 0) @@ -183,59 +239,22 @@ function _buildFilterTree(expr) { if (tree.value === '*') { tree.tag = 'present'; } else { - - // Effectively a hand-rolled .shift() to support \* sequences - clean = true; - split = []; - substrNdx = 0; - split[substrNdx] = ''; - for (i = 0; i < tree.value.length; i++) { - 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) { - tree.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) { - 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 { - tree.value = split[0]; // pick up the cleaned version - } + 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; @@ -253,6 +272,28 @@ function _buildFilterTree(expr) { 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; + } } } diff --git a/lib/url.js b/lib/url.js index d9d69a7..29fa14e 100644 --- a/lib/url.js +++ b/lib/url.js @@ -52,6 +52,7 @@ module.exports = { if (tmp[3]) { u.extensions = querystring.unescape(tmp[3]); } + if (!u.scope) u.scope = 'base'; if (!u.filter) diff --git a/test/filters/ext.test.js b/test/filters/ext.test.js index a84f1fd..09f3f96 100644 --- a/test/filters/ext.test.js +++ b/test/filters/ext.test.js @@ -108,3 +108,26 @@ 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(); +});