read-attribute-value.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. 'use strict'
  2. const readHexString = require('./read-hex-string')
  3. const readEscapeSequence = require('./read-escape-sequence')
  4. /**
  5. * @typedef {object} ReadAttributeValueResult
  6. * @property {number} endPos The position in the buffer that marks the end of
  7. * the value.
  8. * @property {string | import('@ldapjs/asn1').BerReader} value
  9. */
  10. /**
  11. * Read an attribute value string from a given {@link Buffer} and return it.
  12. * If the value is an encoded octet string, it will be decoded and returned
  13. * as a {@link Buffer}.
  14. *
  15. * @param {Buffer} searchBuffer
  16. * @param {number} startPos
  17. *
  18. * @returns {ReadAttributeValueResult}
  19. *
  20. * @throws When there is a syntax error in the attribute value string.
  21. */
  22. module.exports = function readAttributeValue ({ searchBuffer, startPos }) {
  23. let pos = startPos
  24. while (pos < searchBuffer.byteLength && searchBuffer[pos] === 0x20) {
  25. // Skip over any leading whitespace before the '='.
  26. pos += 1
  27. }
  28. if (pos >= searchBuffer.byteLength || searchBuffer[pos] !== 0x3d) {
  29. throw Error('attribute value does not start with equals sign')
  30. }
  31. // Advance past the equals sign.
  32. pos += 1
  33. while (pos <= searchBuffer.byteLength && searchBuffer[pos] === 0x20) {
  34. // Advance past any leading whitespace.
  35. pos += 1
  36. }
  37. if (pos >= searchBuffer.byteLength) {
  38. return { endPos: pos, value: '' }
  39. }
  40. if (searchBuffer[pos] === 0x23) {
  41. const result = readHexString({ searchBuffer, startPos: pos + 1 })
  42. pos = result.endPos
  43. return { endPos: pos, value: result.berReader }
  44. }
  45. const readValueResult = readValueString({ searchBuffer, startPos: pos })
  46. pos = readValueResult.endPos
  47. return {
  48. endPos: pos,
  49. value: readValueResult.value.toString('utf8').trim()
  50. }
  51. }
  52. /**
  53. * @typedef {object} ReadValueStringResult
  54. * @property {number} endPos
  55. * @property {Buffer} value
  56. * @private
  57. */
  58. /**
  59. * Read a series of bytes from the buffer as a plain string.
  60. *
  61. * @param {Buffer} searchBuffer
  62. * @param {number} startPos
  63. *
  64. * @returns {ReadValueStringResult}
  65. *
  66. * @throws When the attribute value is malformed.
  67. *
  68. * @private
  69. */
  70. function readValueString ({ searchBuffer, startPos }) {
  71. let pos = startPos
  72. let inQuotes = false
  73. let endQuotePresent = false
  74. const bytes = []
  75. while (pos <= searchBuffer.byteLength) {
  76. const char = searchBuffer[pos]
  77. if (pos === searchBuffer.byteLength) {
  78. if (inQuotes === true && endQuotePresent === false) {
  79. throw Error('missing ending double quote for attribute value')
  80. }
  81. break
  82. }
  83. if (char === 0x22) {
  84. // Handle the double quote (") character.
  85. // RFC 2253 §4 allows for attribute values to be wrapped in double
  86. // quotes in order to allow certain characters to be unescaped.
  87. // We are not enforcing escaping of characters in this parser, so we only
  88. // need to recognize that the quotes are present. Our RDN string encoder
  89. // will escape characters as necessary.
  90. if (inQuotes === true) {
  91. pos += 1
  92. endQuotePresent = true
  93. // We should be at the end of the value.
  94. while (pos < searchBuffer.byteLength) {
  95. const nextChar = searchBuffer[pos]
  96. if (isEndChar(nextChar) === true) {
  97. break
  98. }
  99. if (nextChar !== 0x20) {
  100. throw Error('significant rdn character found outside of quotes at position ' + pos)
  101. }
  102. pos += 1
  103. }
  104. break
  105. }
  106. if (pos !== startPos) {
  107. throw Error('unexpected quote (") in rdn string at position ' + pos)
  108. }
  109. inQuotes = true
  110. pos += 1
  111. continue
  112. }
  113. if (isEndChar(char) === true && inQuotes === false) {
  114. break
  115. }
  116. if (char === 0x5c) {
  117. // We have encountered the start of an escape sequence.
  118. const seqResult = readEscapeSequence({
  119. searchBuffer,
  120. startPos: pos
  121. })
  122. pos = seqResult.endPos
  123. Array.prototype.push.apply(bytes, seqResult.parsed)
  124. continue
  125. }
  126. bytes.push(char)
  127. pos += 1
  128. }
  129. return {
  130. endPos: pos,
  131. value: Buffer.from(bytes)
  132. }
  133. }
  134. function isEndChar (c) {
  135. switch (c) {
  136. case 0x2b: // +
  137. case 0x2c: // ,
  138. case 0x3b: // ; -- Allowed by RFC 2253 §4 in place of a comma.
  139. return true
  140. default:
  141. return false
  142. }
  143. }