// Copyright 2011 Mark Cavage, Inc. All rights reserved. function invalidDN(name) { var e = new Error(); e.name = 'InvalidDistinguishedNameError'; e.message = name; return e; } function isAlphaNumeric(c) { var re = /[A-Za-z0-9]/; return re.test(c); } function isWhitespace(c) { var re = /\s/; return re.test(c); } function RDN(obj) { var self = this; if (obj) { Object.keys(obj).forEach(function (k) { self[k.toLowerCase()] = obj[k]; }); } } RDN.prototype.toString = function () { var self = this; var str = ''; Object.keys(this).forEach(function (k) { if (str.length) str += '+'; str += k + '=' + self[k]; }); return str; }; // Thank you OpenJDK! function parse(name) { if (typeof (name) !== 'string') throw new TypeError('name (string) required'); var cur = 0; var len = name.length; var rdnSpaced = false; function parseRdn() { var rdn = new RDN(); while (cur < len) { var leading = trim(); rdnSpaced = rdnSpaced || (leading > 0); var attr = parseAttrType(); trim(); if (cur >= len || name[cur++] !== '=') throw invalidDN(name); trim(); var value = parseAttrValue(); trim(); rdn[attr.toLowerCase()] = value; if (cur >= len || name[cur] !== '+') break; ++cur; } return rdn; } function trim() { var count = 0; while ((cur < len) && isWhitespace(name[cur])) { ++cur; count++; } return count; } function parseAttrType() { var beg = cur; while (cur < len) { var c = name[cur]; if (isAlphaNumeric(c) || c == '.' || c == '-' || c == ' ') { ++cur; } else { break; } } // Back out any trailing spaces. while ((cur > beg) && (name[cur - 1] == ' ')) --cur; if (beg == cur) throw invalidDN(name); return name.slice(beg, cur); } function parseAttrValue() { if (cur < len && name[cur] == '#') { return parseBinaryAttrValue(); } else if (cur < len && name[cur] == '"') { return parseQuotedAttrValue(); } else { return parseStringAttrValue(); } } function parseBinaryAttrValue() { var beg = cur++; while (cur < len && isAlphaNumeric(name[cur])) ++cur; return name.slice(beg, cur); } function parseQuotedAttrValue() { var beg = cur++; while ((cur < len) && name[cur] != '"') { if (name[cur] === '\\') ++cur; // consume backslash, then what follows ++cur; } if (cur++ >= len) // no closing quote throw invalidDN(name); return name.slice(beg, cur); } function parseStringAttrValue() { var beg = cur; var esc = -1; while ((cur < len) && !atTerminator()) { if (name[cur] === '\\') { ++cur; // consume backslash, then what follows esc = cur; } ++cur; } if (cur > len) // backslash followed by nothing throw invalidDN(name); // Trim off (unescaped) trailing whitespace. var end; for (end = cur; end > beg; end--) { if (!isWhitespace(name[end - 1]) || (esc === (end - 1))) break; } return name.slice(beg, end); } function atTerminator() { return (cur < len && (name[cur] === ',' || name[cur] === ';' || name[cur] === '+')); } var rdns = []; rdns.push(parseRdn()); while (cur < len) { if (name[cur] === ',' || name[cur] === ';') { ++cur; rdns.push(parseRdn()); } else { throw invalidDN(name); } } return new DN(rdns).spaced(rdnSpaced); } ///--- API function DN(rdns) { if (!Array.isArray(rdns)) throw new TypeError('rdns ([object]) required'); rdns.forEach(function (rdn) { if (typeof (rdn) !== 'object') throw new TypeError('rdns ([object]) required'); }); this.rdns = rdns.slice(); this.rdnSpaced = true; this.__defineGetter__('length', function () { return this.rdns.length; }); } DN.prototype.toString = function () { var _dn = []; this.rdns.forEach(function (rdn) { _dn.push(rdn.toString()); }); return _dn.join(this.rdnSpaced ? ', ' : ','); }; DN.prototype.spaced = function (spaces) { this.rdnSpaced = (spaces === false) ? false : true; return this; }; DN.prototype.childOf = function (dn) { if (typeof (dn) !== 'object') dn = parse(dn); if (this.rdns.length <= dn.rdns.length) return false; var diff = this.rdns.length - dn.rdns.length; for (var i = dn.rdns.length - 1; i >= 0; i--) { var rdn = dn.rdns[i]; var keys = Object.keys(rdn); if (!keys.length) return false; for (var j = 0; j < keys.length; j++) { var k = keys[j]; var ourRdn = this.rdns[i + diff]; if (ourRdn[k] !== rdn[k]) return false; } } return true; }; DN.prototype.parentOf = function (dn) { if (typeof (dn) !== 'object') dn = parse(dn); if (!this.rdns.length || this.rdns.length >= dn.rdns.length) return false; var diff = dn.rdns.length - this.rdns.length; for (var i = this.rdns.length - 1; i >= 0; i--) { var rdn = this.rdns[i]; var keys = Object.keys(rdn); if (!keys.length) return false; for (var j = 0; j < keys.length; j++) { var k = keys[j]; var theirRdn = dn.rdns[i + diff]; if (theirRdn[k] !== rdn[k]) return false; } } return true; }; DN.prototype.equals = function (dn) { if (typeof (dn) !== 'object') dn = parse(dn); if (this.rdns.length !== dn.rdns.length) return false; for (var i = 0; i < this.rdns.length; i++) { var ours = this.rdns[i]; var theirs = dn.rdns[i]; var ourKeys = Object.keys(ours); var theirKeys = Object.keys(theirs); if (ourKeys.length !== theirKeys.length) return false; ourKeys.sort(); theirKeys.sort(); for (var j = 0; j < ourKeys.length; j++) { if (ourKeys[j] !== theirKeys[j]) return false; if (ours[ourKeys[j]] !== theirs[ourKeys[j]]) return false; } } return true; }; DN.prototype.parent = function () { if (this.rdns.length > 1) { var save = this.rdns.shift(); var dn = new DN(this.rdns); this.rdns.unshift(save); return dn; } return null; }; DN.prototype.clone = function () { return new DN(this.rdns); }; DN.prototype.reverse = function () { this.rdns.reverse(); return this; }; DN.prototype.pop = function () { return this.rdns.pop(); }; DN.prototype.push = function (rdn) { if (typeof (rdn) !== 'object') throw new TypeError('rdn (RDN) required'); return this.rdns.push(rdn); }; DN.prototype.shift = function () { return this.rdns.shift(); }; DN.prototype.unshift = function (rdn) { if (typeof (rdn) !== 'object') throw new TypeError('rdn (RDN) required'); return this.rdns.unshift(rdn); }; DN.isDN = function (dn) { if (!dn || typeof (dn) !== 'object') { return false; } if (dn instanceof DN) { return true; } if (Array.isArray(dn.rdns)) { // Really simple duck-typing for now return true; } return false; }; ///--- Exports module.exports = { parse: parse, DN: DN, RDN: RDN };