Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. // Copyright 2014 Mark Cavage, Inc. All rights reserved.
  2. // Copyright 2014 Patrick Mooney. All rights reserved.
  3. var assert = require('assert-plus');
  4. var helpers = require('./helpers.js');
  5. var AndFilter = require('./and_filter');
  6. var ApproximateFilter = require('./approx_filter');
  7. var EqualityFilter = require('./equality_filter');
  8. var ExtensibleFilter = require('./ext_filter');
  9. var GreaterThanEqualsFilter = require('./ge_filter');
  10. var LessThanEqualsFilter = require('./le_filter');
  11. var NotFilter = require('./not_filter');
  12. var OrFilter = require('./or_filter');
  13. var PresenceFilter = require('./presence_filter');
  14. var SubstringFilter = require('./substr_filter');
  15. ///--- Internal Parsers
  16. // expression parsing
  17. // returns the index of the closing parenthesis matching the open paren
  18. // specified by openParenIndex
  19. function matchParens(str, openParenIndex) {
  20. var stack = [];
  21. var esc = false;
  22. for (var i = openParenIndex || 0; i < str.length; i++) {
  23. var c = str[i];
  24. if (c === '\\') {
  25. if (!esc)
  26. esc = true;
  27. continue;
  28. } else if (c === '(' && !esc) {
  29. stack.push(1);
  30. } else if (c === ')' && !esc) {
  31. stack.pop();
  32. if (stack.length === 0)
  33. return i;
  34. }
  35. esc = false;
  36. }
  37. var ndx = str.length - 1;
  38. if (str.charAt(ndx) !== ')')
  39. throw new Error(str + ' has unbalanced parentheses');
  40. return ndx;
  41. }
  42. function parse_substr(tree) {
  43. // Effectively a hand-rolled .shift() to support \* sequences
  44. var clean = true;
  45. var esc = false;
  46. var obj = {};
  47. var split = [];
  48. var substrNdx = 0;
  49. split[substrNdx] = '';
  50. for (var i = 0; i < tree.value.length; i++) {
  51. var c = tree.value[i];
  52. if (esc) {
  53. split[substrNdx] += c;
  54. esc = false;
  55. } else if (c === '*') {
  56. split[++substrNdx] = '';
  57. } else if (c === '\\') {
  58. esc = true;
  59. } else {
  60. split[substrNdx] += c;
  61. }
  62. }
  63. if (split.length > 1) {
  64. obj.tag = 'substrings';
  65. clean = true;
  66. // if the value string doesn't start with a * then theres no initial
  67. // value else split will have an empty string in its first array
  68. // index...
  69. // we need to remove that empty string
  70. if (tree.value.indexOf('*') !== 0) {
  71. obj.initial = split.shift();
  72. } else {
  73. split.shift();
  74. }
  75. // if the value string doesn't end with a * then theres no final
  76. // value also same split stuff as the initial stuff above
  77. if (tree.value.lastIndexOf('*') !== tree.value.length - 1) {
  78. obj.final = split.pop();
  79. } else {
  80. split.pop();
  81. }
  82. obj.any = split;
  83. } else {
  84. obj.value = split[0]; // pick up the cleaned version
  85. }
  86. obj.clean = clean;
  87. obj.esc = esc;
  88. return obj;
  89. }
  90. // recursive function that builds a filter tree from a string expression
  91. // the filter tree is an intermediary step between the incoming expression and
  92. // the outgoing Filter Class structure.
  93. function _buildFilterTree(expr) {
  94. var c;
  95. var child;
  96. var clean = false;
  97. var endParen;
  98. var esc = false;
  99. var i = 0;
  100. var obj;
  101. var tree = {};
  102. var split;
  103. var val = '';
  104. if (expr.length === 0)
  105. return tree;
  106. // Chop the parens (the call to matchParens below gets rid of the trailer)
  107. if (expr.charAt(0) == '(')
  108. expr = expr.substring(1, expr.length - 1);
  109. //store prefix operator
  110. if (expr.charAt(0) === '&') {
  111. tree.op = 'and';
  112. expr = expr.substring(1);
  113. } else if (expr.charAt(0) === '|') {
  114. tree.op = 'or';
  115. expr = expr.substring(1);
  116. } else if (expr.charAt(0) === '!') {
  117. tree.op = 'not';
  118. expr = expr.substring(1);
  119. } else if (expr.charAt(0) === '(') {
  120. throw new Error('invalid nested parens');
  121. } else {
  122. tree.op = 'expr';
  123. }
  124. if (tree.op != 'expr') {
  125. tree.children = [];
  126. // logical operators are k-ary, so we go until our expression string runs
  127. // out (at least for this recursion level)
  128. while (expr.length !== 0) {
  129. endParen = matchParens(expr);
  130. if (endParen == expr.length - 1) {
  131. tree.children[i] = _buildFilterTree(expr);
  132. expr = '';
  133. } else {
  134. child = expr.slice(0, endParen + 1);
  135. expr = expr.substring(endParen + 1);
  136. tree.children[i] = _buildFilterTree(child);
  137. }
  138. i++;
  139. }
  140. } else {
  141. //else its some sort of non-logical expression, parse and return as such
  142. var operatorStr = '';
  143. tree.name = '';
  144. tree.value = '';
  145. // This parses and enforces filter syntax, which is an AttributeDescription
  146. // plus a filter operator, followed by (for ldapjs), anything. Note
  147. // that ldapjs additionally allows the '_' character in the AD, as many
  148. // users rely on it, even though it's non-standard
  149. //
  150. // From 4.1.5 of RFC251
  151. //
  152. // AttributeDescription ::= LDAPString
  153. //
  154. // A value of AttributeDescription is based on the following BNF:
  155. //
  156. // <AttributeDescription> ::= <AttributeType> [ ";" <options> ]
  157. //
  158. // <options> ::= <option> | <option> ";" <options>
  159. //
  160. // <option> ::= <opt-char> <opt-char>*
  161. //
  162. // <opt-char> ::= ASCII-equivalent letters, numbers and hyphen
  163. //
  164. // Examples of valid AttributeDescription:
  165. //
  166. // cn
  167. // userCertificate;binary
  168. /* JSSTYLED */
  169. if (!/[a-zA-Z0-9;_\-]+[~><:]?=.+/.test(expr))
  170. throw new Error(expr + ' is invalid');
  171. if (expr.indexOf('~=') !== -1) {
  172. operatorStr = '~=';
  173. tree.tag = 'approxMatch';
  174. } else if (expr.indexOf('>=') !== -1) {
  175. operatorStr = '>=';
  176. tree.tag = 'greaterOrEqual';
  177. } else if (expr.indexOf('<=') !== -1) {
  178. operatorStr = '<=';
  179. tree.tag = 'lessOrEqual';
  180. } else if (expr.indexOf(':=') !== -1) {
  181. operatorStr = ':=';
  182. tree.tag = 'extensibleMatch';
  183. } else if (expr.indexOf('=') !== -1) {
  184. operatorStr = '=';
  185. tree.tag = 'equalityMatch';
  186. } else {
  187. // tree.tag = 'present';
  188. throw new Error('invalid filter syntax');
  189. }
  190. if (operatorStr === '') {
  191. tree.name = expr;
  192. } else {
  193. // pull out lhs and rhs of equality operator
  194. var splitAry = expr.split(operatorStr);
  195. tree.name = splitAry.shift();
  196. tree.value = splitAry.join(operatorStr);
  197. // substrings fall into the equality bin in the
  198. // switch above so we need more processing here
  199. if (tree.tag === 'equalityMatch') {
  200. if (tree.value === '*') {
  201. tree.tag = 'present';
  202. } else {
  203. obj = parse_substr(tree);
  204. tree.initial = obj.initial;
  205. tree.any = obj.any;
  206. tree.final = obj.final;
  207. tree.tag = obj.tag || tree.tag;
  208. tree.value = obj.value;
  209. esc = obj.esc;
  210. clean = obj.clean;
  211. }
  212. } else if (tree.tag == 'extensibleMatch') {
  213. split = tree.name.split(':');
  214. tree.extensible = {
  215. matchType: split[0],
  216. value: tree.value
  217. };
  218. switch (split.length) {
  219. case 1:
  220. break;
  221. case 2:
  222. if (split[1].toLowerCase() === 'dn') {
  223. tree.extensible.dnAttributes = true;
  224. } else {
  225. tree.extensible.rule = split[1];
  226. }
  227. break;
  228. case 3:
  229. tree.extensible.dnAttributes = true;
  230. tree.extensible.rule = split[2];
  231. break;
  232. default:
  233. throw new Error('Invalid extensible filter');
  234. }
  235. switch (tree.extensible.rule) {
  236. case '2.5.13.4':
  237. case 'caseIgnoreSubstringsMatch':
  238. tree.extensible.attribute = tree.extensible.matchType;
  239. obj = parse_substr(tree);
  240. tree.extensible.initial = obj.initial;
  241. tree.extensible.any = obj.any;
  242. tree.extensible.final = obj.final;
  243. tree.value = obj.value;
  244. esc = obj.esc;
  245. clean = obj.clean;
  246. break;
  247. case '2.5.13.2':
  248. case 'caseIgnoreMatch':
  249. tree.extensible.attribute = tree.extensible.matchType;
  250. break;
  251. default:
  252. // noop
  253. break;
  254. }
  255. }
  256. }
  257. // Cleanup any escape sequences
  258. if (!clean) {
  259. for (i = 0; i < tree.value.length; i++) {
  260. c = tree.value[i];
  261. if (esc) {
  262. val += c;
  263. esc = false;
  264. } else if (c === '\\') {
  265. esc = true;
  266. } else {
  267. val += c;
  268. }
  269. }
  270. tree.value = val;
  271. }
  272. }
  273. return tree;
  274. }
  275. function serializeTree(tree, filter) {
  276. if (tree === undefined || tree.length === 0)
  277. return;
  278. // if the current tree object is not an expression then its a logical
  279. // operator (ie an internal node in the tree)
  280. var current = null;
  281. if (tree.op !== 'expr') {
  282. switch (tree.op) {
  283. case 'and':
  284. current = new AndFilter();
  285. break;
  286. case 'or':
  287. current = new OrFilter();
  288. break;
  289. case 'not':
  290. current = new NotFilter();
  291. break;
  292. default:
  293. break;
  294. }
  295. filter.addFilter(current || filter);
  296. if (current || tree.children.length) {
  297. tree.children.forEach(function (child) {
  298. serializeTree(child, current);
  299. });
  300. }
  301. } else {
  302. // else its a leaf node in the tree, and represents some type of
  303. // non-logical expression
  304. var tmp;
  305. // convert the tag name to a filter class type
  306. switch (tree.tag) {
  307. case 'approxMatch':
  308. tmp = new ApproximateFilter({
  309. attribute: tree.name,
  310. value: tree.value
  311. });
  312. break;
  313. case 'extensibleMatch':
  314. tmp = new ExtensibleFilter(tree.extensible);
  315. break;
  316. case 'greaterOrEqual':
  317. tmp = new GreaterThanEqualsFilter({
  318. attribute: tree.name,
  319. value: tree.value
  320. });
  321. break;
  322. case 'lessOrEqual':
  323. tmp = new LessThanEqualsFilter({
  324. attribute: tree.name,
  325. value: tree.value
  326. });
  327. break;
  328. case 'equalityMatch':
  329. tmp = new EqualityFilter({
  330. attribute: tree.name,
  331. value: tree.value
  332. });
  333. break;
  334. case 'substrings':
  335. tmp = new SubstringFilter({
  336. attribute: tree.name,
  337. initial: tree.initial,
  338. any: tree.any,
  339. final: tree.final
  340. });
  341. break;
  342. case 'present':
  343. tmp = new PresenceFilter({
  344. attribute: tree.name
  345. });
  346. break;
  347. default:
  348. break;
  349. }
  350. if (tmp)
  351. filter.addFilter(tmp);
  352. }
  353. }
  354. function _parseString(str) {
  355. assert.ok(str);
  356. // create a blank object to pass into treeToObjs
  357. // since its recursive we have to prime it ourselves.
  358. // this gets stripped off before the filter structure is returned
  359. // at the bottom of this function.
  360. var filterObj = new AndFilter({
  361. filters: []
  362. });
  363. serializeTree(_buildFilterTree(str), filterObj);
  364. return filterObj.filters[0];
  365. }
  366. ///--- Exports
  367. module.exports = {
  368. parse: function (filter) {
  369. assert.string(filter);
  370. return _parseString(filter);
  371. },
  372. // Helper utilties for writing custom matchers
  373. testValues: helpers.testValues,
  374. getAttrValue: helpers.getAttrValue,
  375. // Filter definitions
  376. AndFilter: AndFilter,
  377. ApproximateFilter: ApproximateFilter,
  378. EqualityFilter: EqualityFilter,
  379. ExtensibleFilter: ExtensibleFilter,
  380. GreaterThanEqualsFilter: GreaterThanEqualsFilter,
  381. LessThanEqualsFilter: LessThanEqualsFilter,
  382. NotFilter: NotFilter,
  383. OrFilter: OrFilter,
  384. PresenceFilter: PresenceFilter,
  385. SubstringFilter: SubstringFilter
  386. };