Files
ldap-to-oauth2/node_modules/@ldapjs/dn/lib/utils/parse-string/read-attribute-value.js
2025-10-08 11:12:59 -04:00

166 lines
4.2 KiB
JavaScript

'use strict'
const readHexString = require('./read-hex-string')
const readEscapeSequence = require('./read-escape-sequence')
/**
* @typedef {object} ReadAttributeValueResult
* @property {number} endPos The position in the buffer that marks the end of
* the value.
* @property {string | import('@ldapjs/asn1').BerReader} value
*/
/**
* Read an attribute value string from a given {@link Buffer} and return it.
* If the value is an encoded octet string, it will be decoded and returned
* as a {@link Buffer}.
*
* @param {Buffer} searchBuffer
* @param {number} startPos
*
* @returns {ReadAttributeValueResult}
*
* @throws When there is a syntax error in the attribute value string.
*/
module.exports = function readAttributeValue ({ searchBuffer, startPos }) {
let pos = startPos
while (pos < searchBuffer.byteLength && searchBuffer[pos] === 0x20) {
// Skip over any leading whitespace before the '='.
pos += 1
}
if (pos >= searchBuffer.byteLength || searchBuffer[pos] !== 0x3d) {
throw Error('attribute value does not start with equals sign')
}
// Advance past the equals sign.
pos += 1
while (pos <= searchBuffer.byteLength && searchBuffer[pos] === 0x20) {
// Advance past any leading whitespace.
pos += 1
}
if (pos >= searchBuffer.byteLength) {
return { endPos: pos, value: '' }
}
if (searchBuffer[pos] === 0x23) {
const result = readHexString({ searchBuffer, startPos: pos + 1 })
pos = result.endPos
return { endPos: pos, value: result.berReader }
}
const readValueResult = readValueString({ searchBuffer, startPos: pos })
pos = readValueResult.endPos
return {
endPos: pos,
value: readValueResult.value.toString('utf8').trim()
}
}
/**
* @typedef {object} ReadValueStringResult
* @property {number} endPos
* @property {Buffer} value
* @private
*/
/**
* Read a series of bytes from the buffer as a plain string.
*
* @param {Buffer} searchBuffer
* @param {number} startPos
*
* @returns {ReadValueStringResult}
*
* @throws When the attribute value is malformed.
*
* @private
*/
function readValueString ({ searchBuffer, startPos }) {
let pos = startPos
let inQuotes = false
let endQuotePresent = false
const bytes = []
while (pos <= searchBuffer.byteLength) {
const char = searchBuffer[pos]
if (pos === searchBuffer.byteLength) {
if (inQuotes === true && endQuotePresent === false) {
throw Error('missing ending double quote for attribute value')
}
break
}
if (char === 0x22) {
// Handle the double quote (") character.
// RFC 2253 §4 allows for attribute values to be wrapped in double
// quotes in order to allow certain characters to be unescaped.
// We are not enforcing escaping of characters in this parser, so we only
// need to recognize that the quotes are present. Our RDN string encoder
// will escape characters as necessary.
if (inQuotes === true) {
pos += 1
endQuotePresent = true
// We should be at the end of the value.
while (pos < searchBuffer.byteLength) {
const nextChar = searchBuffer[pos]
if (isEndChar(nextChar) === true) {
break
}
if (nextChar !== 0x20) {
throw Error('significant rdn character found outside of quotes at position ' + pos)
}
pos += 1
}
break
}
if (pos !== startPos) {
throw Error('unexpected quote (") in rdn string at position ' + pos)
}
inQuotes = true
pos += 1
continue
}
if (isEndChar(char) === true && inQuotes === false) {
break
}
if (char === 0x5c) {
// We have encountered the start of an escape sequence.
const seqResult = readEscapeSequence({
searchBuffer,
startPos: pos
})
pos = seqResult.endPos
Array.prototype.push.apply(bytes, seqResult.parsed)
continue
}
bytes.push(char)
pos += 1
}
return {
endPos: pos,
value: Buffer.from(bytes)
}
}
function isEndChar (c) {
switch (c) {
case 0x2b: // +
case 0x2c: // ,
case 0x3b: // ; -- Allowed by RFC 2253 §4 in place of a comma.
return true
default:
return false
}
}