First commit
This commit is contained in:
104
node_modules/@ldapjs/dn/lib/utils/escape-value.js
generated
vendored
Normal file
104
node_modules/@ldapjs/dn/lib/utils/escape-value.js
generated
vendored
Normal file
@ -0,0 +1,104 @@
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Converts an attribute value into an escaped string as described in
|
||||
* https://www.rfc-editor.org/rfc/rfc4514#section-2.4.
|
||||
*
|
||||
* This function supports up to 4 byte unicode characters.
|
||||
*
|
||||
* @param {string} value
|
||||
* @returns {string} The escaped string.
|
||||
*/
|
||||
module.exports = function escapeValue (value) {
|
||||
if (typeof value !== 'string') {
|
||||
throw Error('value must be a string')
|
||||
}
|
||||
|
||||
const toEscape = Buffer.from(value, 'utf8')
|
||||
const escaped = []
|
||||
|
||||
// We will handle the reverse solidus ('\') on its own.
|
||||
const embeddedReservedChars = [
|
||||
0x22, // '"'
|
||||
0x2b, // '+'
|
||||
0x2c, // ','
|
||||
0x3b, // ';'
|
||||
0x3c, // '<'
|
||||
0x3e // '>'
|
||||
]
|
||||
for (let i = 0; i < toEscape.byteLength;) {
|
||||
const charHex = toEscape[i]
|
||||
|
||||
// Handle leading space or #.
|
||||
if (i === 0 && (charHex === 0x20 || charHex === 0x23)) {
|
||||
escaped.push(toEscapedHexString(charHex))
|
||||
i += 1
|
||||
continue
|
||||
}
|
||||
// Handle trailing space.
|
||||
if (i === toEscape.byteLength - 1 && charHex === 0x20) {
|
||||
escaped.push(toEscapedHexString(charHex))
|
||||
i += 1
|
||||
continue
|
||||
}
|
||||
|
||||
if (embeddedReservedChars.includes(charHex) === true) {
|
||||
escaped.push(toEscapedHexString(charHex))
|
||||
i += 1
|
||||
continue
|
||||
}
|
||||
|
||||
if (charHex >= 0xc0 && charHex <= 0xdf) {
|
||||
// Represents the first byte in a 2-byte UTF-8 character.
|
||||
escaped.push(toEscapedHexString(charHex))
|
||||
escaped.push(toEscapedHexString(toEscape[i + 1]))
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
|
||||
if (charHex >= 0xe0 && charHex <= 0xef) {
|
||||
// Represents the first byte in a 3-byte UTF-8 character.
|
||||
escaped.push(toEscapedHexString(charHex))
|
||||
escaped.push(toEscapedHexString(toEscape[i + 1]))
|
||||
escaped.push(toEscapedHexString(toEscape[i + 2]))
|
||||
i += 3
|
||||
continue
|
||||
}
|
||||
|
||||
if (charHex >= 0xf0 && charHex <= 0xf7) {
|
||||
// Represents the first byte in a 4-byte UTF-8 character.
|
||||
escaped.push(toEscapedHexString(charHex))
|
||||
escaped.push(toEscapedHexString(toEscape[i + 1]))
|
||||
escaped.push(toEscapedHexString(toEscape[i + 2]))
|
||||
escaped.push(toEscapedHexString(toEscape[i + 3]))
|
||||
i += 4
|
||||
continue
|
||||
}
|
||||
|
||||
if (charHex <= 31) {
|
||||
// Represents an ASCII control character.
|
||||
escaped.push(toEscapedHexString(charHex))
|
||||
i += 1
|
||||
continue
|
||||
}
|
||||
|
||||
escaped.push(String.fromCharCode(charHex))
|
||||
i += 1
|
||||
continue
|
||||
}
|
||||
|
||||
return escaped.join('')
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a byte, convert it to an escaped hex string.
|
||||
*
|
||||
* @example
|
||||
* toEscapedHexString(0x20) // '\20'
|
||||
*
|
||||
* @param {number} char
|
||||
* @returns {string}
|
||||
*/
|
||||
function toEscapedHexString (char) {
|
||||
return '\\' + char.toString(16).padStart(2, '0')
|
||||
}
|
||||
62
node_modules/@ldapjs/dn/lib/utils/escape-value.test.js
generated
vendored
Normal file
62
node_modules/@ldapjs/dn/lib/utils/escape-value.test.js
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
'use strict'
|
||||
|
||||
const tap = require('tap')
|
||||
const escapeValue = require('./escape-value')
|
||||
|
||||
tap.test('throws for bad input', async t => {
|
||||
t.throws(
|
||||
() => escapeValue(42),
|
||||
Error('value must be a string')
|
||||
)
|
||||
})
|
||||
|
||||
tap.test('reserved chars', t => {
|
||||
t.test('space', async t => {
|
||||
const input = ' has a leading and trailing space '
|
||||
const expected = '\\20has a leading and trailing space\\20'
|
||||
const result = escapeValue(input)
|
||||
t.equal(result, expected)
|
||||
})
|
||||
|
||||
t.test('leading #', async t => {
|
||||
t.equal(escapeValue('#hashtag'), '\\23hashtag')
|
||||
})
|
||||
|
||||
t.test('pompous name', async t => {
|
||||
t.equal(
|
||||
escapeValue('James "Jim" Smith, III'),
|
||||
'James \\22Jim\\22 Smith\\2c III'
|
||||
)
|
||||
})
|
||||
|
||||
t.test('carriage return', async t => {
|
||||
t.equal(escapeValue('Before\rAfter'), 'Before\\0dAfter')
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('2-byte utf-8', t => {
|
||||
t.test('Lučić', async t => {
|
||||
const expected = 'Lu\\c4\\8di\\c4\\87'
|
||||
t.equal(escapeValue('Lučić'), expected)
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('3-byte utf-8', t => {
|
||||
t.test('₠', async t => {
|
||||
t.equal(escapeValue('₠'), '\\e2\\82\\a0')
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('4-byte utf-8', t => {
|
||||
t.test('😀', async t => {
|
||||
t.equal(escapeValue('😀'), '\\f0\\9f\\98\\80')
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
19
node_modules/@ldapjs/dn/lib/utils/is-dotted-decimal.js
generated
vendored
Normal file
19
node_modules/@ldapjs/dn/lib/utils/is-dotted-decimal.js
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
'use strict'
|
||||
|
||||
const partIsNotNumeric = part => /^\d+$/.test(part) === false
|
||||
|
||||
/**
|
||||
* Determines if a passed in string is a dotted decimal string.
|
||||
*
|
||||
* @param {string} value
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
module.exports = function isDottedDecimal (value) {
|
||||
if (typeof value !== 'string') return false
|
||||
|
||||
const parts = value.split('.')
|
||||
const nonNumericParts = parts.filter(partIsNotNumeric)
|
||||
|
||||
return nonNumericParts.length === 0
|
||||
}
|
||||
24
node_modules/@ldapjs/dn/lib/utils/is-dotted-decimal.test.js
generated
vendored
Normal file
24
node_modules/@ldapjs/dn/lib/utils/is-dotted-decimal.test.js
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
'use strict'
|
||||
|
||||
const tap = require('tap')
|
||||
const isDottedDecimal = require('./is-dotted-decimal')
|
||||
|
||||
tap.test('false for non-string', async t => {
|
||||
t.equal(isDottedDecimal(), false)
|
||||
})
|
||||
|
||||
tap.test('false for empty string', async t => {
|
||||
t.equal(isDottedDecimal(''), false)
|
||||
})
|
||||
|
||||
tap.test('false for alpha string', async t => {
|
||||
t.equal(isDottedDecimal('foo'), false)
|
||||
})
|
||||
|
||||
tap.test('false for alpha-num string', async t => {
|
||||
t.equal(isDottedDecimal('foo.123'), false)
|
||||
})
|
||||
|
||||
tap.test('true for valid string', async t => {
|
||||
t.equal(isDottedDecimal('1.2.3'), true)
|
||||
})
|
||||
58
node_modules/@ldapjs/dn/lib/utils/parse-string/find-name-end.js
generated
vendored
Normal file
58
node_modules/@ldapjs/dn/lib/utils/parse-string/find-name-end.js
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Find the ending position of the attribute type name portion of an RDN.
|
||||
* This function does not verify if the name is a valid description string
|
||||
* or numeric OID. It merely reads a string from the given starting position
|
||||
* to the spec defined end of an attribute type string.
|
||||
*
|
||||
* @param {Buffer} searchBuffer A buffer representing the RDN.
|
||||
* @param {number} startPos The position in the `searchBuffer` to start
|
||||
* searching from.
|
||||
*
|
||||
* @returns {number} The position of the end of the RDN's attribute type name,
|
||||
* or `-1` if an invalid character has been encountered.
|
||||
*/
|
||||
module.exports = function findNameEnd ({ searchBuffer, startPos }) {
|
||||
let pos = startPos
|
||||
|
||||
while (pos < searchBuffer.byteLength) {
|
||||
const char = searchBuffer[pos]
|
||||
if (char === 0x20 || char === 0x3d) {
|
||||
// Name ends with a space or an '=' character.
|
||||
break
|
||||
}
|
||||
if (isValidNameChar(char) === true) {
|
||||
pos += 1
|
||||
continue
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
return pos
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a character is a valid `attributeType` character as defined
|
||||
* in RFC 4514 §3.
|
||||
*
|
||||
* @param {number} c The character to verify. Should be the byte representation
|
||||
* of the character from a {@link Buffer} instance.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isValidNameChar (c) {
|
||||
if (c >= 0x41 && c <= 0x5a) { // A - Z
|
||||
return true
|
||||
}
|
||||
if (c >= 0x61 && c <= 0x7a) { // a - z
|
||||
return true
|
||||
}
|
||||
if (c >= 0x30 && c <= 0x39) { // 0 - 9
|
||||
return true
|
||||
}
|
||||
if (c === 0x2d || c === 0x2e) { // - or .
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
28
node_modules/@ldapjs/dn/lib/utils/parse-string/find-name-end.test.js
generated
vendored
Normal file
28
node_modules/@ldapjs/dn/lib/utils/parse-string/find-name-end.test.js
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
'use strict'
|
||||
|
||||
const tap = require('tap')
|
||||
const findNameEnd = require('./find-name-end')
|
||||
|
||||
tap.test('stops on a space', async t => {
|
||||
const input = Buffer.from('foo = bar')
|
||||
const pos = findNameEnd({ searchBuffer: input, startPos: 0 })
|
||||
t.equal(pos, 3)
|
||||
})
|
||||
|
||||
tap.test('stops on an equals', async t => {
|
||||
const input = Buffer.from('foo=bar')
|
||||
const pos = findNameEnd({ searchBuffer: input, startPos: 0 })
|
||||
t.equal(pos, 3)
|
||||
})
|
||||
|
||||
tap.test('returns -1 for bad character', async t => {
|
||||
const input = Buffer.from('føø=bar')
|
||||
const pos = findNameEnd({ searchBuffer: input, startPos: 0 })
|
||||
t.equal(pos, -1)
|
||||
})
|
||||
|
||||
tap.test('recognizes all valid characters', async t => {
|
||||
const input = Buffer.from('Foo.0-bar=baz')
|
||||
const pos = findNameEnd({ searchBuffer: input, startPos: 0 })
|
||||
t.equal(pos, 9)
|
||||
})
|
||||
34
node_modules/@ldapjs/dn/lib/utils/parse-string/find-name-start.js
generated
vendored
Normal file
34
node_modules/@ldapjs/dn/lib/utils/parse-string/find-name-start.js
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
'use strict'
|
||||
|
||||
// Attribute types must start with an ASCII alphanum character.
|
||||
// https://www.rfc-editor.org/rfc/rfc4514#section-3
|
||||
// https://www.rfc-editor.org/rfc/rfc4512#section-1.4
|
||||
const isLeadChar = (c) => /[a-zA-Z0-9]/.test(c) === true
|
||||
|
||||
/**
|
||||
* Find the starting position of an attribute type (name). Leading spaces and
|
||||
* commas are ignored. If an invalid leading character is encountered, an
|
||||
* invalid position will be returned.
|
||||
*
|
||||
* @param {Buffer} searchBuffer
|
||||
* @param {number} startPos
|
||||
*
|
||||
* @returns {number} The position in the buffer where the name starts, or `-1`
|
||||
* if an invalid name starting character is encountered.
|
||||
*/
|
||||
module.exports = function findNameStart ({ searchBuffer, startPos }) {
|
||||
let pos = startPos
|
||||
while (pos < searchBuffer.byteLength) {
|
||||
if (searchBuffer[pos] === 0x20 || searchBuffer[pos] === 0x2c) {
|
||||
// Skip leading space and comma.
|
||||
pos += 1
|
||||
continue
|
||||
}
|
||||
const char = String.fromCharCode(searchBuffer[pos])
|
||||
if (isLeadChar(char) === true) {
|
||||
return pos
|
||||
}
|
||||
break
|
||||
}
|
||||
return -1
|
||||
}
|
||||
28
node_modules/@ldapjs/dn/lib/utils/parse-string/find-name-start.test.js
generated
vendored
Normal file
28
node_modules/@ldapjs/dn/lib/utils/parse-string/find-name-start.test.js
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
'use strict'
|
||||
|
||||
const tap = require('tap')
|
||||
const findNameStart = require('./find-name-start')
|
||||
|
||||
tap.test('returns correct position', async t => {
|
||||
const input = Buffer.from(' foo')
|
||||
t.equal(
|
||||
findNameStart({ searchBuffer: input, startPos: 0 }),
|
||||
3
|
||||
)
|
||||
})
|
||||
|
||||
tap.test('skips leading comma', async t => {
|
||||
const input = Buffer.from(' , foo=bar')
|
||||
t.equal(
|
||||
findNameStart({ searchBuffer: input, startPos: 0 }),
|
||||
3
|
||||
)
|
||||
})
|
||||
|
||||
tap.test('returns -1 for invalid lead char', async t => {
|
||||
const input = Buffer.from(' øfoo')
|
||||
t.equal(
|
||||
findNameStart({ searchBuffer: input, startPos: 0 }),
|
||||
-1
|
||||
)
|
||||
})
|
||||
105
node_modules/@ldapjs/dn/lib/utils/parse-string/index.js
generated
vendored
Normal file
105
node_modules/@ldapjs/dn/lib/utils/parse-string/index.js
generated
vendored
Normal file
@ -0,0 +1,105 @@
|
||||
'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
|
||||
}
|
||||
214
node_modules/@ldapjs/dn/lib/utils/parse-string/index.test.js
generated
vendored
Normal file
214
node_modules/@ldapjs/dn/lib/utils/parse-string/index.test.js
generated
vendored
Normal file
@ -0,0 +1,214 @@
|
||||
'use strict'
|
||||
|
||||
const tap = require('tap')
|
||||
const { BerReader } = require('@ldapjs/asn1')
|
||||
const parseString = require('./index')
|
||||
|
||||
tap.test('throws for non-string input', async t => {
|
||||
const input = ['cn=foo']
|
||||
t.throws(
|
||||
() => parseString(input),
|
||||
'input must be a string'
|
||||
)
|
||||
})
|
||||
|
||||
tap.test('short circuits for root dse', async t => {
|
||||
t.same(parseString(''), [])
|
||||
})
|
||||
|
||||
tap.test('parses basic single rdn', async t => {
|
||||
const input = 'cn=foo'
|
||||
const result = parseString(input)
|
||||
t.same(result, [{ cn: 'foo' }])
|
||||
})
|
||||
|
||||
tap.test('skips leading whitespace', async t => {
|
||||
const input = ' cn=foo'
|
||||
const result = parseString(input)
|
||||
t.same(result, [{ cn: 'foo' }])
|
||||
})
|
||||
|
||||
tap.test('parses basic multiple rdns', async t => {
|
||||
let input = 'dc=example,dc=com'
|
||||
let result = parseString(input)
|
||||
t.same(
|
||||
result,
|
||||
[
|
||||
{ dc: 'example' },
|
||||
{ dc: 'com' }
|
||||
]
|
||||
)
|
||||
|
||||
// RFC 2253 §4 separator is supported.
|
||||
input = 'dc=example;dc=com'
|
||||
result = parseString(input)
|
||||
t.same(
|
||||
result,
|
||||
[
|
||||
{ dc: 'example' },
|
||||
{ dc: 'com' }
|
||||
]
|
||||
)
|
||||
})
|
||||
|
||||
tap.test('handles multivalued rdn', async t => {
|
||||
const input = 'foo=bar+baz=bif'
|
||||
const result = parseString(input)
|
||||
t.same(result, [{ foo: 'bar', baz: 'bif' }])
|
||||
})
|
||||
|
||||
tap.test('abruptly ending strings throw', async t => {
|
||||
const baseError = 'rdn string ends abruptly with character: '
|
||||
const tests = [
|
||||
{ input: 'foo=bar+', expected: baseError + '+' },
|
||||
{ input: 'foo=bar,', expected: baseError + ',' },
|
||||
{ input: 'foo=bar;', expected: baseError + ';' }
|
||||
]
|
||||
for (const test of tests) {
|
||||
t.throws(() => parseString(test.input), test.expected)
|
||||
}
|
||||
})
|
||||
|
||||
tap.test('adds rdn with trailing whitespace', async t => {
|
||||
const input = 'foo=bar '
|
||||
const result = parseString(input)
|
||||
t.same(result, [{ foo: 'bar' }])
|
||||
})
|
||||
|
||||
tap.test('parses rdn with attribute name in OID form', async t => {
|
||||
const input = '0.9.2342.19200300.100.1.25=Example'
|
||||
const result = parseString(input)
|
||||
t.same(result, [{ '0.9.2342.19200300.100.1.25': 'Example' }])
|
||||
})
|
||||
|
||||
tap.test('throws for invalid attribute type name', async t => {
|
||||
let input = '3foo=bar'
|
||||
t.throws(
|
||||
() => parseString(input),
|
||||
'invalid attribute type name: 3foo'
|
||||
)
|
||||
|
||||
input = '1.2.3.abc=bar'
|
||||
t.throws(
|
||||
() => parseString(input),
|
||||
'invalid attribute type name: 1.2.3.abc=bar'
|
||||
)
|
||||
|
||||
input = 'føø=bar'
|
||||
t.throws(
|
||||
() => parseString(input),
|
||||
'invalid attribute type name: føø'
|
||||
)
|
||||
})
|
||||
|
||||
tap.test('throws for abrupt end', async t => {
|
||||
const input = 'foo=bar,'
|
||||
t.throws(
|
||||
() => parseString(input),
|
||||
'rdn string ends abruptly with character: ,'
|
||||
)
|
||||
})
|
||||
|
||||
tap.test('rfc 4514 §4 examples', async t => {
|
||||
const tests = [
|
||||
{
|
||||
input: 'UID=jsmith,DC=example,DC=net',
|
||||
expected: [{ UID: 'jsmith' }, { DC: 'example' }, { DC: 'net' }]
|
||||
},
|
||||
{
|
||||
input: 'OU=Sales+CN=J. Smith,DC=example,DC=net',
|
||||
expected: [
|
||||
{ OU: 'Sales', CN: 'J. Smith' },
|
||||
{ DC: 'example' },
|
||||
{ DC: 'net' }
|
||||
]
|
||||
},
|
||||
{
|
||||
input: 'CN=James \\"Jim\\" Smith\\, III,DC=example,DC=net',
|
||||
expected: [{ CN: 'James "Jim" Smith, III' }, { DC: 'example' }, { DC: 'net' }]
|
||||
},
|
||||
{
|
||||
input: 'CN=Before\\0dAfter,DC=example,DC=net',
|
||||
expected: [{ CN: 'Before\rAfter' }, { DC: 'example' }, { DC: 'net' }]
|
||||
},
|
||||
{
|
||||
checkBuffer: true,
|
||||
input: '1.3.6.1.4.1.1466.0=#04024869',
|
||||
expected: [{ '1.3.6.1.4.1.1466.0': new BerReader(Buffer.from([0x04, 0x02, 0x48, 0x69])) }]
|
||||
},
|
||||
{
|
||||
input: 'CN=Lu\\C4\\8Di\\C4\\87',
|
||||
expected: [{ CN: 'Lučić' }]
|
||||
}
|
||||
]
|
||||
|
||||
for (const test of tests) {
|
||||
const result = parseString(test.input)
|
||||
if (test.checkBuffer) {
|
||||
for (const [i, rdn] of test.expected.entries()) {
|
||||
for (const key of Object.keys(rdn)) {
|
||||
t.equal(
|
||||
rdn[key].buffer.compare(result[i][key].buffer),
|
||||
0
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
t.same(result, test.expected)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
tap.test('rfc 2253 §5 examples', async t => {
|
||||
const tests = [
|
||||
{
|
||||
input: 'CN=Steve Kille,O=Isode Limited,C=GB',
|
||||
expected: [{ CN: 'Steve Kille' }, { O: 'Isode Limited' }, { C: 'GB' }]
|
||||
},
|
||||
{
|
||||
input: 'OU=Sales+CN=J. Smith,O=Widget Inc.,C=US',
|
||||
expected: [{ OU: 'Sales', CN: 'J. Smith' }, { O: 'Widget Inc.' }, { C: 'US' }]
|
||||
},
|
||||
{
|
||||
input: 'CN=L. Eagle,O=Sue\\, Grabbit and Runn,C=GB',
|
||||
expected: [{ CN: 'L. Eagle' }, { O: 'Sue, Grabbit and Runn' }, { C: 'GB' }]
|
||||
},
|
||||
{
|
||||
input: 'CN=Before\\0DAfter,O=Test,C=GB',
|
||||
expected: [{ CN: 'Before\rAfter' }, { O: 'Test' }, { C: 'GB' }]
|
||||
},
|
||||
{
|
||||
checkBuffer: true,
|
||||
input: '1.3.6.1.4.1.1466.0=#04024869,O=Test,C=GB',
|
||||
expected: [
|
||||
{ '1.3.6.1.4.1.1466.0': new BerReader(Buffer.from([0x04, 0x02, 0x48, 0x69])) },
|
||||
{ O: 'Test' },
|
||||
{ C: 'GB' }
|
||||
]
|
||||
},
|
||||
{
|
||||
input: 'SN=Lu\\C4\\8Di\\C4\\87',
|
||||
expected: [{ SN: 'Lučić' }]
|
||||
}
|
||||
]
|
||||
|
||||
for (const test of tests) {
|
||||
const result = parseString(test.input)
|
||||
if (test.checkBuffer) {
|
||||
for (const [i, rdn] of test.expected.entries()) {
|
||||
for (const key of Object.keys(rdn)) {
|
||||
if (typeof rdn[key] !== 'string') {
|
||||
t.equal(
|
||||
rdn[key].buffer.compare(result[i][key].buffer),
|
||||
0
|
||||
)
|
||||
} else {
|
||||
t.equal(rdn[key], result[i][key])
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
t.same(result, test.expected)
|
||||
}
|
||||
}
|
||||
})
|
||||
28
node_modules/@ldapjs/dn/lib/utils/parse-string/is-valid-attribute-type-name.js
generated
vendored
Normal file
28
node_modules/@ldapjs/dn/lib/utils/parse-string/is-valid-attribute-type-name.js
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
'use strict'
|
||||
|
||||
const isDigit = c => /[0-9]/.test(c) === true
|
||||
const hasKeyChars = input => /[a-zA-Z-]/.test(input) === true
|
||||
const isValidLeadChar = c => /[a-zA-Z]/.test(c) === true
|
||||
const hasInvalidChars = input => /[^a-zA-Z0-9-]/.test(input) === true
|
||||
|
||||
/**
|
||||
* An attribute type name is defined by RFC 4514 §3 as a "descr" or
|
||||
* "numericoid". These are defined by RFC 4512 §1.4. This function validates
|
||||
* the given name as matching the spec.
|
||||
*
|
||||
* @param {string} name
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
module.exports = function isValidAttributeTypeName (name) {
|
||||
if (isDigit(name[0]) === true) {
|
||||
// A leading digit indicates that the name should be a numericoid.
|
||||
return hasKeyChars(name) === false
|
||||
}
|
||||
|
||||
if (isValidLeadChar(name[0]) === false) {
|
||||
return false
|
||||
}
|
||||
|
||||
return hasInvalidChars(name) === false
|
||||
}
|
||||
36
node_modules/@ldapjs/dn/lib/utils/parse-string/is-valid-attribute-type-name.test.js
generated
vendored
Normal file
36
node_modules/@ldapjs/dn/lib/utils/parse-string/is-valid-attribute-type-name.test.js
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
'use strict'
|
||||
|
||||
const tap = require('tap')
|
||||
const isValidAttributeTypeName = require('./is-valid-attribute-type-name')
|
||||
|
||||
tap.test('validates numericoids', async t => {
|
||||
let input = '1.2.3.4'
|
||||
let result = isValidAttributeTypeName(input)
|
||||
t.equal(result, true)
|
||||
|
||||
input = '1.2.3.4.abc'
|
||||
result = isValidAttributeTypeName(input)
|
||||
t.equal(result, false)
|
||||
})
|
||||
|
||||
tap.test('validates descrs', async t => {
|
||||
let input = 'foo'
|
||||
let result = isValidAttributeTypeName(input)
|
||||
t.equal(result, true)
|
||||
|
||||
input = '3foo'
|
||||
result = isValidAttributeTypeName(input)
|
||||
t.equal(result, false)
|
||||
|
||||
input = 'foo-3'
|
||||
result = isValidAttributeTypeName(input)
|
||||
t.equal(result, true)
|
||||
|
||||
input = 'føø3'
|
||||
result = isValidAttributeTypeName(input)
|
||||
t.equal(result, false)
|
||||
|
||||
input = 'ƒ00'
|
||||
result = isValidAttributeTypeName(input)
|
||||
t.equal(result, false)
|
||||
})
|
||||
73
node_modules/@ldapjs/dn/lib/utils/parse-string/read-attribute-pair.js
generated
vendored
Normal file
73
node_modules/@ldapjs/dn/lib/utils/parse-string/read-attribute-pair.js
generated
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
'use strict'
|
||||
|
||||
const findNameStart = require('./find-name-start')
|
||||
const findNameEnd = require('./find-name-end')
|
||||
const isValidAttributeTypeName = require('./is-valid-attribute-type-name')
|
||||
const readAttributeValue = require('./read-attribute-value')
|
||||
|
||||
/**
|
||||
* @typedef {object} AttributePair
|
||||
* @property {string | import('@ldapjs/asn1').BerReader} name Property name is
|
||||
* actually the property name of the attribute pair. The value will be a string,
|
||||
* or, in the case of the value being a hex encoded string, an instance of
|
||||
* `BerReader`.
|
||||
*
|
||||
* @example
|
||||
* const input = 'foo=bar'
|
||||
* const pair = { foo: 'bar' }
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} ReadAttributePairResult
|
||||
* @property {number} endPos The ending position in the input search buffer that
|
||||
* is the end of the read attribute pair.
|
||||
* @property {AttributePair} pair The parsed attribute pair.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Read an RDN attribute type and attribute value pair from the provided
|
||||
* search buffer at the given starting position.
|
||||
*
|
||||
* @param {Buffer} searchBuffer
|
||||
* @param {number} startPos
|
||||
*
|
||||
* @returns {ReadAttributePairResult}
|
||||
*
|
||||
* @throws When there is some problem with the input string.
|
||||
*/
|
||||
module.exports = function readAttributePair ({ searchBuffer, startPos }) {
|
||||
let pos = startPos
|
||||
|
||||
const nameStartPos = findNameStart({
|
||||
searchBuffer,
|
||||
startPos: pos
|
||||
})
|
||||
if (nameStartPos < 0) {
|
||||
throw Error('invalid attribute name leading character encountered')
|
||||
}
|
||||
|
||||
const nameEndPos = findNameEnd({
|
||||
searchBuffer,
|
||||
startPos: nameStartPos
|
||||
})
|
||||
if (nameStartPos < 0) {
|
||||
throw Error('invalid character in attribute name encountered')
|
||||
}
|
||||
|
||||
const attributeName = searchBuffer.subarray(nameStartPos, nameEndPos).toString('utf8')
|
||||
if (isValidAttributeTypeName(attributeName) === false) {
|
||||
throw Error('invalid attribute type name: ' + attributeName)
|
||||
}
|
||||
|
||||
const valueReadResult = readAttributeValue({
|
||||
searchBuffer,
|
||||
startPos: nameEndPos
|
||||
})
|
||||
pos = valueReadResult.endPos
|
||||
const attributeValue = valueReadResult.value
|
||||
|
||||
return {
|
||||
endPos: pos,
|
||||
pair: { [attributeName]: attributeValue }
|
||||
}
|
||||
}
|
||||
165
node_modules/@ldapjs/dn/lib/utils/parse-string/read-attribute-value.js
generated
vendored
Normal file
165
node_modules/@ldapjs/dn/lib/utils/parse-string/read-attribute-value.js
generated
vendored
Normal file
@ -0,0 +1,165 @@
|
||||
'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
|
||||
}
|
||||
}
|
||||
119
node_modules/@ldapjs/dn/lib/utils/parse-string/read-attribute-value.test.js
generated
vendored
Normal file
119
node_modules/@ldapjs/dn/lib/utils/parse-string/read-attribute-value.test.js
generated
vendored
Normal file
@ -0,0 +1,119 @@
|
||||
'use strict'
|
||||
|
||||
const tap = require('tap')
|
||||
const readAttributeValue = require('./read-attribute-value')
|
||||
const { BerReader } = require('@ldapjs/asn1')
|
||||
|
||||
const missingError = Error('attribute value does not start with equals sign')
|
||||
|
||||
tap.test('throws if missing equals sign', async t => {
|
||||
let input = Buffer.from('foo')
|
||||
t.throws(
|
||||
() => readAttributeValue({ searchBuffer: input, startPos: 3 }),
|
||||
missingError
|
||||
)
|
||||
|
||||
input = Buffer.from('foo ≠')
|
||||
t.throws(
|
||||
() => readAttributeValue({ searchBuffer: input, startPos: 3 }),
|
||||
missingError
|
||||
)
|
||||
})
|
||||
|
||||
tap.test('handles empty attribute value', async t => {
|
||||
const input = Buffer.from('foo=')
|
||||
const result = readAttributeValue({ searchBuffer: input, startPos: 3 })
|
||||
t.same(result, { endPos: 4, value: '' })
|
||||
})
|
||||
|
||||
tap.test('returns attribute value', async t => {
|
||||
const input = Buffer.from('foo=bar')
|
||||
const result = readAttributeValue({ searchBuffer: input, startPos: 3 })
|
||||
t.same(result, { endPos: 7, value: 'bar' })
|
||||
})
|
||||
|
||||
tap.test('quoted values', t => {
|
||||
t.test('throws if quote is unexpected', async t => {
|
||||
const input = Buffer.from('=foo"bar')
|
||||
t.throws(
|
||||
() => readAttributeValue({ searchBuffer: input, startPos: 0 }),
|
||||
'unexpected quote (") in rdn string at position 4'
|
||||
)
|
||||
})
|
||||
|
||||
t.test('handles a basic quoted value', async t => {
|
||||
const input = Buffer.from('="bar"')
|
||||
const result = readAttributeValue({ searchBuffer: input, startPos: 0 })
|
||||
t.same(result, { endPos: 6, value: 'bar' })
|
||||
})
|
||||
|
||||
t.test('handles quote followed by end char', async t => {
|
||||
const input = Buffer.from('="bar",another=rdn')
|
||||
const result = readAttributeValue({ searchBuffer: input, startPos: 0 })
|
||||
t.same(result, { endPos: 6, value: 'bar' })
|
||||
})
|
||||
|
||||
t.test('significant spaces in quoted values are part of the value', async t => {
|
||||
const input = Buffer.from('="foo bar "')
|
||||
const result = readAttributeValue({ searchBuffer: input, startPos: 0 })
|
||||
t.same(result, { endPos: 13, value: 'foo bar' })
|
||||
})
|
||||
|
||||
t.test('throws if next significant char is not an end char', async t => {
|
||||
const input = Buffer.from('="foo bar" baz')
|
||||
t.throws(
|
||||
() => readAttributeValue({ searchBuffer: input, startPos: 0 }),
|
||||
'significant rdn character found outside of quotes at position 7'
|
||||
)
|
||||
})
|
||||
|
||||
t.test('throws if ending quote not found', async t => {
|
||||
const input = Buffer.from('="foo')
|
||||
t.throws(
|
||||
() => readAttributeValue({ searchBuffer: input, startPos: 0 }),
|
||||
'missing ending double quote for attribute value'
|
||||
)
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('leading and trailing spaces are omitted', async t => {
|
||||
const input = Buffer.from('= foo ')
|
||||
const result = readAttributeValue({ searchBuffer: input, startPos: 0 })
|
||||
t.same(result, { endPos: 9, value: 'foo' })
|
||||
})
|
||||
|
||||
tap.test('parses escaped attribute values', async t => {
|
||||
const input = Buffer.from('foo=foo\\#bar')
|
||||
const result = readAttributeValue({ searchBuffer: input, startPos: 3 })
|
||||
t.same(result, { endPos: 12, value: 'foo#bar' })
|
||||
})
|
||||
|
||||
tap.test('stops reading at all ending characters', async t => {
|
||||
const tests = [
|
||||
{ input: '=foo,bar', expected: { endPos: 4, value: 'foo' } },
|
||||
{ input: '=foo+bar', expected: { endPos: 4, value: 'foo' } },
|
||||
{ input: '=foo;bar', expected: { endPos: 4, value: 'foo' } }
|
||||
]
|
||||
|
||||
for (const test of tests) {
|
||||
const result = readAttributeValue({
|
||||
searchBuffer: Buffer.from(test.input),
|
||||
startPos: 0
|
||||
})
|
||||
t.same(result, test.expected)
|
||||
}
|
||||
})
|
||||
|
||||
tap.test('reads hex encoded string', async t => {
|
||||
const input = Buffer.from('=#0403666f6f')
|
||||
const result = readAttributeValue({ searchBuffer: input, startPos: 0 })
|
||||
const expected = {
|
||||
endPos: 12,
|
||||
value: new BerReader(Buffer.from([0x04, 0x03, 0x66, 0x6f, 0x6f]))
|
||||
}
|
||||
|
||||
t.same(result, expected)
|
||||
t.equal(result.value.buffer.compare(expected.value.buffer), 0)
|
||||
})
|
||||
137
node_modules/@ldapjs/dn/lib/utils/parse-string/read-escape-sequence.js
generated
vendored
Normal file
137
node_modules/@ldapjs/dn/lib/utils/parse-string/read-escape-sequence.js
generated
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* @typedef ReadEscapeSequenceResult
|
||||
* @property {number} endPos The position in the buffer that marks the end of
|
||||
* the escape sequence.
|
||||
* @property {Buffer} parsed The parsed escape sequence as a buffer of bytes.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Read an escape sequence from a buffer. It reads until no escape sequences
|
||||
* are found. Thus, a sequence of escape sequences will all be parsed at once
|
||||
* and returned as a single result.
|
||||
*
|
||||
* @example A Single ASCII Sequence
|
||||
* const toParse = Buffer.from('foo\\#bar', 'utf8')
|
||||
* const {parsed, endPos} = readEscapeSequence({
|
||||
* searchBuffer: toParse,
|
||||
* startPos: 3
|
||||
* })
|
||||
* // => parsed = '#', endPos = 5
|
||||
*
|
||||
* @example Multiple ASCII Sequences In Succession
|
||||
* const toParse = Buffer.from('foo\\#\\!bar', 'utf8')
|
||||
* const {parsed, endPos} = readEscapeSequence({
|
||||
* searchBuffer: toParse,
|
||||
* startPos: 3
|
||||
* })
|
||||
* // => parsed = '#!', endPos = 7
|
||||
*
|
||||
* @param searchBuffer
|
||||
* @param startPos
|
||||
*
|
||||
* @returns {ReadEscapeSequenceResult}
|
||||
*
|
||||
* @throws When an escaped sequence is not a valid hexadecimal value.
|
||||
*/
|
||||
module.exports = function readEscapeSequence ({ searchBuffer, startPos }) {
|
||||
// This is very similar to the `readEscapedCharacters` algorithm in
|
||||
// the `utils/escape-filter-value` in `@ldapjs/filter`. The difference being
|
||||
// that here we want to interpret the escape sequence instead of return it
|
||||
// as a string to be embedded in an "escaped" string.
|
||||
// https://github.com/ldapjs/filter/blob/1423612/lib/utils/escape-filter-value.js
|
||||
|
||||
let pos = startPos
|
||||
const buf = []
|
||||
|
||||
while (pos < searchBuffer.byteLength) {
|
||||
const char = searchBuffer[pos]
|
||||
const nextChar = searchBuffer[pos + 1]
|
||||
|
||||
if (char !== 0x5c) {
|
||||
// End of sequence reached.
|
||||
break
|
||||
}
|
||||
|
||||
const strHexCode = String.fromCharCode(nextChar) +
|
||||
String.fromCharCode(searchBuffer[pos + 2])
|
||||
const hexCode = parseInt(strHexCode, 16)
|
||||
if (Number.isNaN(hexCode) === true) {
|
||||
if (nextChar >= 0x00 && nextChar <= 0x7f) {
|
||||
// Sequence is a single escaped ASCII character
|
||||
buf.push(nextChar)
|
||||
pos += 2
|
||||
continue
|
||||
} else {
|
||||
throw Error('invalid hex code in escape sequence')
|
||||
}
|
||||
}
|
||||
|
||||
if (hexCode >= 0xc0 && hexCode <= 0xdf) {
|
||||
// Sequence is a 2-byte utf-8 character.
|
||||
const secondByte = parseInt(
|
||||
String.fromCharCode(searchBuffer[pos + 4]) +
|
||||
String.fromCharCode(searchBuffer[pos + 5]),
|
||||
16
|
||||
)
|
||||
buf.push(hexCode)
|
||||
buf.push(secondByte)
|
||||
pos += 6
|
||||
continue
|
||||
}
|
||||
|
||||
if (hexCode >= 0xe0 && hexCode <= 0xef) {
|
||||
// Sequence is a 3-byte utf-8 character.
|
||||
const secondByte = parseInt(
|
||||
String.fromCharCode(searchBuffer[pos + 4]) +
|
||||
String.fromCharCode(searchBuffer[pos + 5]),
|
||||
16
|
||||
)
|
||||
const thirdByte = parseInt(
|
||||
String.fromCharCode(searchBuffer[pos + 7]) +
|
||||
String.fromCharCode(searchBuffer[pos + 8]),
|
||||
16
|
||||
)
|
||||
buf.push(hexCode)
|
||||
buf.push(secondByte)
|
||||
buf.push(thirdByte)
|
||||
pos += 9
|
||||
continue
|
||||
}
|
||||
|
||||
if (hexCode >= 0xf0 && hexCode <= 0xf7) {
|
||||
// Sequence is a 4-byte utf-8 character.
|
||||
const secondByte = parseInt(
|
||||
String.fromCharCode(searchBuffer[pos + 4]) +
|
||||
String.fromCharCode(searchBuffer[pos + 5]),
|
||||
16
|
||||
)
|
||||
const thirdByte = parseInt(
|
||||
String.fromCharCode(searchBuffer[pos + 7]) +
|
||||
String.fromCharCode(searchBuffer[pos + 8]),
|
||||
16
|
||||
)
|
||||
const fourthByte = parseInt(
|
||||
String.fromCharCode(searchBuffer[pos + 10]) +
|
||||
String.fromCharCode(searchBuffer[pos + 11]),
|
||||
16
|
||||
)
|
||||
buf.push(hexCode)
|
||||
buf.push(secondByte)
|
||||
buf.push(thirdByte)
|
||||
buf.push(fourthByte)
|
||||
pos += 12
|
||||
continue
|
||||
}
|
||||
|
||||
// The escaped character should be a single hex value.
|
||||
buf.push(hexCode)
|
||||
pos += 3
|
||||
}
|
||||
|
||||
return {
|
||||
endPos: pos,
|
||||
parsed: Buffer.from(buf)
|
||||
}
|
||||
}
|
||||
72
node_modules/@ldapjs/dn/lib/utils/parse-string/read-escape-sequence.test.js
generated
vendored
Normal file
72
node_modules/@ldapjs/dn/lib/utils/parse-string/read-escape-sequence.test.js
generated
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
'use strict'
|
||||
|
||||
const tap = require('tap')
|
||||
const readEscapeSequence = require('./read-escape-sequence')
|
||||
|
||||
tap.test('throws for bad sequence', async t => {
|
||||
const input = Buffer.from('foo\\ø')
|
||||
t.throws(
|
||||
() => readEscapeSequence({ searchBuffer: input, startPos: 3 }),
|
||||
Error('invalid hex code in escape sequence')
|
||||
)
|
||||
})
|
||||
|
||||
tap.test('reads a single ascii sequence', async t => {
|
||||
const input = Buffer.from('foo\\#bar', 'utf8')
|
||||
const { parsed, endPos } = readEscapeSequence({
|
||||
searchBuffer: input,
|
||||
startPos: 3
|
||||
})
|
||||
t.equal(parsed.toString(), '#')
|
||||
t.equal(endPos, 5)
|
||||
})
|
||||
|
||||
tap.test('reads a sequence of ascii sequences', async t => {
|
||||
const input = Buffer.from('foo\\#\\!bar', 'utf8')
|
||||
const { parsed, endPos } = readEscapeSequence({
|
||||
searchBuffer: input,
|
||||
startPos: 3
|
||||
})
|
||||
t.equal(parsed.toString(), '#!')
|
||||
t.equal(endPos, 7)
|
||||
})
|
||||
|
||||
tap.test('reads a single hex sequence', async t => {
|
||||
const input = Buffer.from('foo\\2abar', 'utf8')
|
||||
const { parsed, endPos } = readEscapeSequence({
|
||||
searchBuffer: input,
|
||||
startPos: 3
|
||||
})
|
||||
t.equal(parsed.toString(), '*')
|
||||
t.equal(endPos, 6)
|
||||
})
|
||||
|
||||
tap.test('reads 2-byte utf-8 sequence', async t => {
|
||||
const input = Buffer.from('fo\\c5\\8f bar')
|
||||
const { parsed, endPos } = readEscapeSequence({
|
||||
searchBuffer: input,
|
||||
startPos: 2
|
||||
})
|
||||
t.equal(parsed.toString(), 'ŏ')
|
||||
t.equal(endPos, 8)
|
||||
})
|
||||
|
||||
tap.test('reads 3-byte utf-8 sequence', async t => {
|
||||
const input = Buffer.from('fo\\e0\\b0\\b0 bar')
|
||||
const { parsed, endPos } = readEscapeSequence({
|
||||
searchBuffer: input,
|
||||
startPos: 2
|
||||
})
|
||||
t.equal(parsed.toString(), 'ర')
|
||||
t.equal(endPos, 11)
|
||||
})
|
||||
|
||||
tap.test('reads 4-byte utf-8 sequence', async t => {
|
||||
const input = Buffer.from('fo\\f0\\92\\84\\ad bar')
|
||||
const { parsed, endPos } = readEscapeSequence({
|
||||
searchBuffer: input,
|
||||
startPos: 2
|
||||
})
|
||||
t.equal(parsed.toString(), '𒄭')
|
||||
t.equal(endPos, 14)
|
||||
})
|
||||
61
node_modules/@ldapjs/dn/lib/utils/parse-string/read-hex-string.js
generated
vendored
Normal file
61
node_modules/@ldapjs/dn/lib/utils/parse-string/read-hex-string.js
generated
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
'use strict'
|
||||
|
||||
const { BerReader } = require('@ldapjs/asn1')
|
||||
|
||||
const isValidHexCode = code => /[0-9a-fA-F]{2}/.test(code) === true
|
||||
|
||||
/**
|
||||
* @typedef {object} ReadHexStringResult
|
||||
* @property {number} endPos The position in the buffer where the end of the
|
||||
* hex string was encountered.
|
||||
* @property {import('@ldapjs/asn1').BerReader} berReader The parsed hex string
|
||||
* as an BER object.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Read a sequence of bytes as a hex encoded octet string. The sequence is
|
||||
* assumed to be a spec compliant encoded BER object.
|
||||
*
|
||||
* @param {Buffer} searchBuffer The buffer to read.
|
||||
* @param {number} startPos The position in the buffer to start reading from.
|
||||
*
|
||||
* @returns {ReadHexStringResult}
|
||||
*
|
||||
* @throws When an invalid hex pair has been encountered.
|
||||
*/
|
||||
module.exports = function readHexString ({ searchBuffer, startPos }) {
|
||||
const bytes = []
|
||||
|
||||
let pos = startPos
|
||||
while (pos < searchBuffer.byteLength) {
|
||||
if (isEndChar(searchBuffer[pos])) {
|
||||
break
|
||||
}
|
||||
|
||||
const hexPair = String.fromCharCode(searchBuffer[pos]) +
|
||||
String.fromCharCode(searchBuffer[pos + 1])
|
||||
if (isValidHexCode(hexPair) === false) {
|
||||
throw Error('invalid hex pair encountered: 0x' + hexPair)
|
||||
}
|
||||
|
||||
bytes.push(parseInt(hexPair, 16))
|
||||
pos += 2
|
||||
}
|
||||
|
||||
return {
|
||||
endPos: pos,
|
||||
berReader: new BerReader(Buffer.from(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
function isEndChar (c) {
|
||||
switch (c) {
|
||||
case 0x20: // space
|
||||
case 0x2b: // +
|
||||
case 0x2c: // ,
|
||||
case 0x3b: // ;
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
52
node_modules/@ldapjs/dn/lib/utils/parse-string/read-hex-string.test.js
generated
vendored
Normal file
52
node_modules/@ldapjs/dn/lib/utils/parse-string/read-hex-string.test.js
generated
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
'use strict'
|
||||
|
||||
const tap = require('tap')
|
||||
const readHexString = require('./read-hex-string')
|
||||
|
||||
tap.test('throws for invalid hex pair', async t => {
|
||||
let input = Buffer.from('1z2f')
|
||||
t.throws(
|
||||
() => readHexString({ searchBuffer: input, startPos: 0 }),
|
||||
'invalid hex pair encountered: 0x1z'
|
||||
)
|
||||
|
||||
input = Buffer.from('a0b1g692')
|
||||
t.throws(
|
||||
() => readHexString({ searchBuffer: input, startPos: 0 }),
|
||||
'invalid hex pair encountered: 0xg6'
|
||||
)
|
||||
})
|
||||
|
||||
tap.test('handles incorrect length string', async t => {
|
||||
const input = Buffer.from('a1b')
|
||||
t.throws(
|
||||
() => readHexString({ searchBuffer: input, startPos: 0 }),
|
||||
'invalid hex pair encountered: 0xb'
|
||||
)
|
||||
})
|
||||
|
||||
tap.test('reads hex string', async t => {
|
||||
let input = Buffer.from('0403666f6f')
|
||||
let result = readHexString({ searchBuffer: input, startPos: 0 })
|
||||
t.equal(result.endPos, 10)
|
||||
t.equal(result.berReader.readString(), 'foo')
|
||||
|
||||
input = Buffer.from('uid=#0409746573742E75736572')
|
||||
result = readHexString({ searchBuffer: input, startPos: 5 })
|
||||
t.equal(result.endPos, input.byteLength)
|
||||
t.equal(result.berReader.readString(), 'test.user')
|
||||
})
|
||||
|
||||
tap.test('stops on end chars', async t => {
|
||||
const inputs = [
|
||||
Buffer.from('0403666f6f foo'),
|
||||
Buffer.from('0403666f6f+foo'),
|
||||
Buffer.from('0403666f6f,foo'),
|
||||
Buffer.from('0403666f6f;foo')
|
||||
]
|
||||
for (const input of inputs) {
|
||||
const result = readHexString({ searchBuffer: input, startPos: 0 })
|
||||
t.equal(result.endPos, 10)
|
||||
t.equal(result.berReader.readString(), 'foo')
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user