166 lines
4.2 KiB
JavaScript
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
|
|
}
|
|
}
|