index.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. 'use strict'
  2. const readAttributePair = require('./read-attribute-pair')
  3. /**
  4. * @typedef {object} ParsedPojoRdn
  5. * @property {string} name Either the name of an RDN attribute, or the
  6. * equivalent numeric OID.
  7. * @property {string | import('@ldapjs/asn1').BerReader} value The attribute
  8. * value as a plain string, or a `BerReader` if the string value was an encoded
  9. * hex string.
  10. */
  11. /**
  12. * Parse a string into a set of plain JavaScript object representations of
  13. * RDNs.
  14. *
  15. * @example A plain string with multiple RDNs and multiple attribute assertions.
  16. * const input = 'cn=foo+sn=bar,dc=example,dc=com
  17. * const result = parseString(input)
  18. * // [
  19. * // { cn: 'foo', sn: 'bar' },
  20. * // { dc: 'example' }
  21. * // { dc: 'com' }
  22. * // ]
  23. *
  24. * @param {string} input The RDN string to parse.
  25. *
  26. * @returns {ParsedPojoRdn[]}
  27. *
  28. * @throws When there is some problem parsing the RDN string.
  29. */
  30. module.exports = function parseString (input) {
  31. if (typeof input !== 'string') {
  32. throw Error('input must be a string')
  33. }
  34. if (input.length === 0) {
  35. // Short circuit because the input is an empty DN (i.e. "root DSE").
  36. return []
  37. }
  38. const searchBuffer = Buffer.from(input, 'utf8')
  39. const length = searchBuffer.byteLength
  40. const rdns = []
  41. let pos = 0
  42. let rdn = {}
  43. readRdnLoop:
  44. while (pos <= length) {
  45. if (pos === length) {
  46. const char = searchBuffer[pos - 1]
  47. /* istanbul ignore else */
  48. if (char === 0x2b || char === 0x2c || char === 0x3b) {
  49. throw Error('rdn string ends abruptly with character: ' + String.fromCharCode(char))
  50. }
  51. }
  52. // Find the start of significant characters by skipping over any leading
  53. // whitespace.
  54. while (pos < length && searchBuffer[pos] === 0x20) {
  55. pos += 1
  56. }
  57. const readAttrPairResult = readAttributePair({ searchBuffer, startPos: pos })
  58. pos = readAttrPairResult.endPos
  59. rdn = { ...rdn, ...readAttrPairResult.pair }
  60. if (pos >= length) {
  61. // We've reached the end of the string. So push the current RDN and stop.
  62. rdns.push(rdn)
  63. break
  64. }
  65. // Next, we need to determine if the next set of significant characters
  66. // denotes another attribute pair for the current RDN, or is the indication
  67. // of another RDN.
  68. while (pos < length) {
  69. const char = searchBuffer[pos]
  70. // We don't need to skip whitespace before the separator because the
  71. // attribute pair function has already advanced us past any such
  72. // whitespace.
  73. if (char === 0x2b) { // +
  74. // We need to continue adding attribute pairs to the current RDN.
  75. pos += 1
  76. continue readRdnLoop
  77. }
  78. /* istanbul ignore else */
  79. if (char === 0x2c || char === 0x3b) { // , or ;
  80. // The current RDN has been fully parsed, so push it to the list,
  81. // reset the collector, and start parsing the next RDN.
  82. rdns.push(rdn)
  83. rdn = {}
  84. pos += 1
  85. continue readRdnLoop
  86. }
  87. }
  88. }
  89. return rdns
  90. }