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.

dn.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. // Copyright 2011 Mark Cavage, Inc. All rights reserved.
  2. var assert = require('assert-plus');
  3. ///--- Helpers
  4. function invalidDN(name) {
  5. var e = new Error();
  6. e.name = 'InvalidDistinguishedNameError';
  7. e.message = name;
  8. return e;
  9. }
  10. function isAlphaNumeric(c) {
  11. var re = /[A-Za-z0-9]/;
  12. return re.test(c);
  13. }
  14. function isWhitespace(c) {
  15. var re = /\s/;
  16. return re.test(c);
  17. }
  18. function repeatChar(c, n) {
  19. var out = '';
  20. var max = n ? n : 0;
  21. for (var i = 0; i < max; i++)
  22. out += c;
  23. return out;
  24. }
  25. ///--- API
  26. function RDN(obj) {
  27. var self = this;
  28. this.attrs = {};
  29. if (obj) {
  30. Object.keys(obj).forEach(function (k) {
  31. self.set(k, obj[k]);
  32. });
  33. }
  34. }
  35. RDN.prototype.set = function rdnSet(name, value, opts) {
  36. assert.string(name, 'name (string) required');
  37. assert.string(value, 'value (string) required');
  38. var self = this;
  39. var lname = name.toLowerCase();
  40. this.attrs[lname] = {
  41. value: value,
  42. name: name
  43. };
  44. if (opts && typeof (opts) === 'object') {
  45. Object.keys(opts).forEach(function (k) {
  46. if (k !== 'value')
  47. self.attrs[lname][k] = opts[k];
  48. });
  49. }
  50. };
  51. RDN.prototype.equals = function rdnEquals(rdn) {
  52. if (typeof (rdn) !== 'object')
  53. return false;
  54. var ourKeys = Object.keys(this.attrs);
  55. var theirKeys = Object.keys(rdn.attrs);
  56. if (ourKeys.length !== theirKeys.length)
  57. return false;
  58. ourKeys.sort();
  59. theirKeys.sort();
  60. for (var i = 0; i < ourKeys.length; i++) {
  61. if (ourKeys[i] !== theirKeys[i])
  62. return false;
  63. if (this.attrs[ourKeys[i]].value !== rdn.attrs[ourKeys[i]].value)
  64. return false;
  65. }
  66. return true;
  67. };
  68. /**
  69. * Convert RDN to string according to specified formatting options.
  70. * (see: DN.format for option details)
  71. */
  72. RDN.prototype.format = function rdnFormat(options) {
  73. assert.optionalObject(options, 'options must be an object');
  74. options = options || {};
  75. var self = this;
  76. var str = '';
  77. function escapeValue(val, forceQuote) {
  78. var out = '';
  79. var cur = 0;
  80. var len = val.length;
  81. var quoted = false;
  82. /* BEGIN JSSTYLED */
  83. var escaped = /[\\\"]/;
  84. var special = /[,=+<>#;]/;
  85. /* END JSSTYLED */
  86. if (len > 0) {
  87. // Wrap strings with trailing or leading spaces in quotes
  88. quoted = forceQuote || (val[0] == ' ' || val[len-1] == ' ');
  89. }
  90. while (cur < len) {
  91. if (escaped.test(val[cur]) || (!quoted && special.test(val[cur]))) {
  92. out += '\\';
  93. }
  94. out += val[cur++];
  95. }
  96. if (quoted)
  97. out = '"' + out + '"';
  98. return out;
  99. }
  100. function sortParsed(a, b) {
  101. return self.attrs[a].order - self.attrs[b].order;
  102. }
  103. function sortStandard(a, b) {
  104. var nameCompare = a.localeCompare(b);
  105. if (nameCompare === 0) {
  106. // TODO: Handle binary values
  107. return self.attrs[a].value.localeCompare(self.attrs[b].value);
  108. } else {
  109. return nameCompare;
  110. }
  111. }
  112. var keys = Object.keys(this.attrs);
  113. if (options.keepOrder) {
  114. keys.sort(sortParsed);
  115. } else {
  116. keys.sort(sortStandard);
  117. }
  118. keys.forEach(function (key) {
  119. var attr = self.attrs[key];
  120. if (str.length)
  121. str += '+';
  122. if (options.keepCase) {
  123. str += attr.name;
  124. } else {
  125. if (options.upperName)
  126. str += key.toUpperCase();
  127. else
  128. str += key;
  129. }
  130. str += '=' + escapeValue(attr.value, (options.keepQuote && attr.quoted));
  131. });
  132. return str;
  133. };
  134. RDN.prototype.toString = function rdnToString() {
  135. return this.format();
  136. };
  137. // Thank you OpenJDK!
  138. function parse(name) {
  139. if (typeof (name) !== 'string')
  140. throw new TypeError('name (string) required');
  141. var cur = 0;
  142. var len = name.length;
  143. function parseRdn() {
  144. var rdn = new RDN();
  145. var order = 0;
  146. rdn.spLead = trim();
  147. while (cur < len) {
  148. var opts = {
  149. order: order
  150. };
  151. var attr = parseAttrType();
  152. trim();
  153. if (cur >= len || name[cur++] !== '=')
  154. throw invalidDN(name);
  155. trim();
  156. // Parameters about RDN value are set in 'opts' by parseAttrValue
  157. var value = parseAttrValue(opts);
  158. rdn.set(attr, value, opts);
  159. rdn.spTrail = trim();
  160. if (cur >= len || name[cur] !== '+')
  161. break;
  162. ++cur;
  163. ++order;
  164. }
  165. return rdn;
  166. }
  167. function trim() {
  168. var count = 0;
  169. while ((cur < len) && isWhitespace(name[cur])) {
  170. ++cur;
  171. count++;
  172. }
  173. return count;
  174. }
  175. function parseAttrType() {
  176. var beg = cur;
  177. while (cur < len) {
  178. var c = name[cur];
  179. if (isAlphaNumeric(c) ||
  180. c == '.' ||
  181. c == '-' ||
  182. c == ' ') {
  183. ++cur;
  184. } else {
  185. break;
  186. }
  187. }
  188. // Back out any trailing spaces.
  189. while ((cur > beg) && (name[cur - 1] == ' '))
  190. --cur;
  191. if (beg == cur)
  192. throw invalidDN(name);
  193. return name.slice(beg, cur);
  194. }
  195. function parseAttrValue(opts) {
  196. if (cur < len && name[cur] == '#') {
  197. opts.binary = true;
  198. return parseBinaryAttrValue();
  199. } else if (cur < len && name[cur] == '"') {
  200. opts.quoted = true;
  201. return parseQuotedAttrValue();
  202. } else {
  203. return parseStringAttrValue();
  204. }
  205. }
  206. function parseBinaryAttrValue() {
  207. var beg = cur++;
  208. while (cur < len && isAlphaNumeric(name[cur]))
  209. ++cur;
  210. return name.slice(beg, cur);
  211. }
  212. function parseQuotedAttrValue() {
  213. var str = '';
  214. ++cur; // Consume the first quote
  215. while ((cur < len) && name[cur] != '"') {
  216. if (name[cur] === '\\')
  217. cur++;
  218. str += name[cur++];
  219. }
  220. if (cur++ >= len) // no closing quote
  221. throw invalidDN(name);
  222. return str;
  223. }
  224. function parseStringAttrValue() {
  225. var beg = cur;
  226. var str = '';
  227. var esc = -1;
  228. while ((cur < len) && !atTerminator()) {
  229. if (name[cur] === '\\') {
  230. // Consume the backslash and mark its place just in case it's escaping
  231. // whitespace which needs to be preserved.
  232. esc = cur++;
  233. }
  234. if (cur === len) // backslash followed by nothing
  235. throw invalidDN(name);
  236. str += name[cur++];
  237. }
  238. // Trim off (unescaped) trailing whitespace and rewind cursor to the end of
  239. // the AttrValue to record whitespace length.
  240. for (; cur > beg; cur--) {
  241. if (!isWhitespace(name[cur - 1]) || (esc === (cur - 1)))
  242. break;
  243. }
  244. return str.slice(0, cur - beg);
  245. }
  246. function atTerminator() {
  247. return (cur < len &&
  248. (name[cur] === ',' ||
  249. name[cur] === ';' ||
  250. name[cur] === '+'));
  251. }
  252. var rdns = [];
  253. // Short-circuit for empty DNs
  254. if (len === 0)
  255. return new DN(rdns);
  256. rdns.push(parseRdn());
  257. while (cur < len) {
  258. if (name[cur] === ',' || name[cur] === ';') {
  259. ++cur;
  260. rdns.push(parseRdn());
  261. } else {
  262. throw invalidDN(name);
  263. }
  264. }
  265. return new DN(rdns);
  266. }
  267. function DN(rdns) {
  268. assert.optionalArrayOfObject(rdns, '[object] required');
  269. this.rdns = rdns ? rdns.slice() : [];
  270. this._format = {};
  271. }
  272. Object.defineProperties(DN.prototype, {
  273. length: {
  274. get: function getLength() { return this.rdns.length; },
  275. configurable: false
  276. }
  277. });
  278. /**
  279. * Convert DN to string according to specified formatting options.
  280. *
  281. * Parameters:
  282. * - options: formatting parameters (optional, details below)
  283. *
  284. * Options are divided into two types:
  285. * - Preservation options: Using data recorded during parsing, details of the
  286. * original DN are preserved when converting back into a string.
  287. * - Modification options: Alter string formatting defaults.
  288. *
  289. * Preservation options _always_ take precedence over modification options.
  290. *
  291. * Preservation Options:
  292. * - keepOrder: Order of multi-value RDNs.
  293. * - keepQuote: RDN values which were quoted will remain so.
  294. * - keepSpace: Leading/trailing spaces will be output.
  295. * - keepCase: Parsed attr name will be output instead of lowercased version.
  296. *
  297. * Modification Options:
  298. * - upperName: RDN names will be uppercased instead of lowercased.
  299. * - skipSpace: Disable trailing space after RDN separators
  300. */
  301. DN.prototype.format = function dnFormat(options) {
  302. assert.optionalObject(options, 'options must be an object');
  303. options = options || this._format;
  304. var str = '';
  305. this.rdns.forEach(function (rdn) {
  306. var rdnString = rdn.format(options);
  307. if (str.length !== 0) {
  308. str += ',';
  309. }
  310. if (options.keepSpace) {
  311. str += (repeatChar(' ', rdn.spLead) +
  312. rdnString + repeatChar(' ', rdn.spTrail));
  313. } else if (options.skipSpace === true || str.length === 0) {
  314. str += rdnString;
  315. } else {
  316. str += ' ' + rdnString;
  317. }
  318. });
  319. return str;
  320. };
  321. /**
  322. * Set default string formatting options.
  323. */
  324. DN.prototype.setFormat = function setFormat(options) {
  325. assert.object(options, 'options must be an object');
  326. this._format = options;
  327. };
  328. DN.prototype.toString = function dnToString() {
  329. return this.format();
  330. };
  331. DN.prototype.parentOf = function parentOf(dn) {
  332. if (typeof (dn) !== 'object')
  333. dn = parse(dn);
  334. if (this.rdns.length >= dn.rdns.length)
  335. return false;
  336. var diff = dn.rdns.length - this.rdns.length;
  337. for (var i = this.rdns.length - 1; i >= 0; i--) {
  338. var myRDN = this.rdns[i];
  339. var theirRDN = dn.rdns[i + diff];
  340. if (!myRDN.equals(theirRDN))
  341. return false;
  342. }
  343. return true;
  344. };
  345. DN.prototype.childOf = function childOf(dn) {
  346. if (typeof (dn) !== 'object')
  347. dn = parse(dn);
  348. return dn.parentOf(this);
  349. };
  350. DN.prototype.isEmpty = function isEmpty() {
  351. return (this.rdns.length === 0);
  352. };
  353. DN.prototype.equals = function dnEquals(dn) {
  354. if (typeof (dn) !== 'object')
  355. dn = parse(dn);
  356. if (this.rdns.length !== dn.rdns.length)
  357. return false;
  358. for (var i = 0; i < this.rdns.length; i++) {
  359. if (!this.rdns[i].equals(dn.rdns[i]))
  360. return false;
  361. }
  362. return true;
  363. };
  364. DN.prototype.parent = function dnParent() {
  365. if (this.rdns.length !== 0) {
  366. var save = this.rdns.shift();
  367. var dn = new DN(this.rdns);
  368. this.rdns.unshift(save);
  369. return dn;
  370. }
  371. return null;
  372. };
  373. DN.prototype.clone = function dnClone() {
  374. var dn = new DN(this.rdns);
  375. dn._format = this._format;
  376. return dn;
  377. };
  378. DN.prototype.reverse = function dnReverse() {
  379. this.rdns.reverse();
  380. return this;
  381. };
  382. DN.prototype.pop = function dnPop() {
  383. return this.rdns.pop();
  384. };
  385. DN.prototype.push = function dnPush(rdn) {
  386. assert.object(rdn, 'rdn (RDN) required');
  387. return this.rdns.push(rdn);
  388. };
  389. DN.prototype.shift = function dnShift() {
  390. return this.rdns.shift();
  391. };
  392. DN.prototype.unshift = function dnUnshift(rdn) {
  393. assert.object(rdn, 'rdn (RDN) required');
  394. return this.rdns.unshift(rdn);
  395. };
  396. DN.isDN = function isDN(dn) {
  397. if (!dn || typeof (dn) !== 'object') {
  398. return false;
  399. }
  400. if (dn instanceof DN) {
  401. return true;
  402. }
  403. if (Array.isArray(dn.rdns)) {
  404. // Really simple duck-typing for now
  405. return true;
  406. }
  407. return false;
  408. };
  409. ///--- Exports
  410. module.exports = {
  411. parse: parse,
  412. DN: DN,
  413. RDN: RDN
  414. };