106 lines
2.9 KiB
JavaScript
106 lines
2.9 KiB
JavaScript
'use strict'
|
|
|
|
const readAttributePair = require('./read-attribute-pair')
|
|
|
|
/**
|
|
* @typedef {object} ParsedPojoRdn
|
|
* @property {string} name Either the name of an RDN attribute, or the
|
|
* equivalent numeric OID.
|
|
* @property {string | import('@ldapjs/asn1').BerReader} value The attribute
|
|
* value as a plain string, or a `BerReader` if the string value was an encoded
|
|
* hex string.
|
|
*/
|
|
|
|
/**
|
|
* Parse a string into a set of plain JavaScript object representations of
|
|
* RDNs.
|
|
*
|
|
* @example A plain string with multiple RDNs and multiple attribute assertions.
|
|
* const input = 'cn=foo+sn=bar,dc=example,dc=com
|
|
* const result = parseString(input)
|
|
* // [
|
|
* // { cn: 'foo', sn: 'bar' },
|
|
* // { dc: 'example' }
|
|
* // { dc: 'com' }
|
|
* // ]
|
|
*
|
|
* @param {string} input The RDN string to parse.
|
|
*
|
|
* @returns {ParsedPojoRdn[]}
|
|
*
|
|
* @throws When there is some problem parsing the RDN string.
|
|
*/
|
|
module.exports = function parseString (input) {
|
|
if (typeof input !== 'string') {
|
|
throw Error('input must be a string')
|
|
}
|
|
if (input.length === 0) {
|
|
// Short circuit because the input is an empty DN (i.e. "root DSE").
|
|
return []
|
|
}
|
|
|
|
const searchBuffer = Buffer.from(input, 'utf8')
|
|
const length = searchBuffer.byteLength
|
|
const rdns = []
|
|
|
|
let pos = 0
|
|
let rdn = {}
|
|
|
|
readRdnLoop:
|
|
while (pos <= length) {
|
|
if (pos === length) {
|
|
const char = searchBuffer[pos - 1]
|
|
|
|
/* istanbul ignore else */
|
|
if (char === 0x2b || char === 0x2c || char === 0x3b) {
|
|
throw Error('rdn string ends abruptly with character: ' + String.fromCharCode(char))
|
|
}
|
|
}
|
|
|
|
// Find the start of significant characters by skipping over any leading
|
|
// whitespace.
|
|
while (pos < length && searchBuffer[pos] === 0x20) {
|
|
pos += 1
|
|
}
|
|
|
|
const readAttrPairResult = readAttributePair({ searchBuffer, startPos: pos })
|
|
pos = readAttrPairResult.endPos
|
|
rdn = { ...rdn, ...readAttrPairResult.pair }
|
|
|
|
if (pos >= length) {
|
|
// We've reached the end of the string. So push the current RDN and stop.
|
|
rdns.push(rdn)
|
|
break
|
|
}
|
|
|
|
// Next, we need to determine if the next set of significant characters
|
|
// denotes another attribute pair for the current RDN, or is the indication
|
|
// of another RDN.
|
|
while (pos < length) {
|
|
const char = searchBuffer[pos]
|
|
|
|
// We don't need to skip whitespace before the separator because the
|
|
// attribute pair function has already advanced us past any such
|
|
// whitespace.
|
|
|
|
if (char === 0x2b) { // +
|
|
// We need to continue adding attribute pairs to the current RDN.
|
|
pos += 1
|
|
continue readRdnLoop
|
|
}
|
|
|
|
/* istanbul ignore else */
|
|
if (char === 0x2c || char === 0x3b) { // , or ;
|
|
// The current RDN has been fully parsed, so push it to the list,
|
|
// reset the collector, and start parsing the next RDN.
|
|
rdns.push(rdn)
|
|
rdn = {}
|
|
pos += 1
|
|
continue readRdnLoop
|
|
}
|
|
}
|
|
}
|
|
|
|
return rdns
|
|
}
|