// Copyright 2014 Mark Cavage, Inc. All rights reserved. // Copyright 2014 Patrick Mooney. All rights reserved. var assert = require('assert-plus'); var helpers = require('./helpers.js'); var AndFilter = require('./and_filter'); var ApproximateFilter = require('./approx_filter'); var EqualityFilter = require('./equality_filter'); var ExtensibleFilter = require('./ext_filter'); var GreaterThanEqualsFilter = require('./ge_filter'); var LessThanEqualsFilter = require('./le_filter'); var NotFilter = require('./not_filter'); var OrFilter = require('./or_filter'); var PresenceFilter = require('./presence_filter'); var SubstringFilter = require('./substr_filter'); ///--- 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 if (expr.charAt(0) === '(') { throw new Error('invalid nested parens'); } 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: // // ::= [ ";" ] // // ::=