First commit

This commit is contained in:
2025-10-08 11:12:59 -04:00
commit b0605a28a9
820 changed files with 100317 additions and 0 deletions

9
node_modules/@ldapjs/controls/.eslintrc generated vendored Normal file
View File

@ -0,0 +1,9 @@
{
"parserOptions": {
"ecmaVersion": "latest"
},
"extends": [
"standard"
]
}

View File

@ -0,0 +1,10 @@
name: "CI"
on:
pull_request:
push:
branches:
- master
jobs:
call-core-ci:
uses: ldapjs/.github/.github/workflows/node-ci.yml@main

5
node_modules/@ldapjs/controls/.taprc.yml generated vendored Normal file
View File

@ -0,0 +1,5 @@
files:
- 'index.test.js'
- 'lib/**/*.test.js'
coverage-map: coverage-map.js

22
node_modules/@ldapjs/controls/LICENSE generated vendored Normal file
View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2011 Mark Cavage, All rights reserved.
Copyright (c) 2022 The LDAPJS Collaborators.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE

12
node_modules/@ldapjs/controls/Readme.md generated vendored Normal file
View File

@ -0,0 +1,12 @@
# @ldapjs/controls
This package provides implementations of [LDAP controls][controls]. The
primary purpose of this library is to facilitate client and server
implementations in the [`ldapjs`](https://npm.im/ldapjs) package.
## Docs
At this time, one must reference the code to learn about the available
controls and their methods.
[controls]: https://datatracker.ietf.org/doc/html/rfc4511#section-4.1.11

13
node_modules/@ldapjs/controls/coverage-map.js generated vendored Normal file
View File

@ -0,0 +1,13 @@
module.exports = testFile => {
if (testFile.startsWith('virtual-list-view-request-control') === true) {
// Do not count towards coverage as it is disabled.
return false
}
if (testFile.startsWith('virtual-list-view-response-control') === true) {
// Do not count towards coverage as it is disabled.
return false
}
testFile.replace(/\.test\.js$/, '.js')
}

103
node_modules/@ldapjs/controls/index.js generated vendored Normal file
View File

@ -0,0 +1,103 @@
'use strict'
const { Ber } = require('@ldapjs/asn1')
const Control = require('./lib/control')
const EntryChangeNotificationControl = require('./lib/controls/entry-change-notification-control')
const PagedResultsControl = require('./lib/controls/paged-results-control')
const PasswordPolicyControl = require('./lib/controls/password-policy-control')
const PersistentSearchControl = require('./lib/controls/persistent-search-control')
const ServerSideSortingRequestControl = require('./lib/controls/server-side-sorting-request-control')
const ServerSideSortingResponseControl = require('./lib/controls/server-side-sorting-response-control')
const VirtualListViewRequestControl = require('./lib/controls/virtual-list-view-request-control')
const VirtualListViewResponseControl = require('./lib/controls/virtual-list-view-response-control')
module.exports = {
getControl: function getControl (ber) {
if (!ber) throw TypeError('ber must be provided')
if (ber.readSequence() === null) { return null }
let type
const opts = {
criticality: false,
value: null
}
/* istanbul ignore else */
if (ber.length) {
const end = ber.offset + ber.length
type = ber.readString()
/* istanbul ignore else */
if (ber.offset < end) {
/* istanbul ignore else */
if (ber.peek() === Ber.Boolean) { opts.criticality = ber.readBoolean() }
}
if (ber.offset < end) { opts.value = ber.readString(Ber.OctetString, true) }
}
let control
switch (type) {
case EntryChangeNotificationControl.OID: {
control = new EntryChangeNotificationControl(opts)
break
}
case PagedResultsControl.OID: {
control = new PagedResultsControl(opts)
break
}
case PasswordPolicyControl.OID: {
control = new PasswordPolicyControl(opts)
break
}
case PersistentSearchControl.OID: {
control = new PersistentSearchControl(opts)
break
}
case ServerSideSortingRequestControl.OID: {
control = new ServerSideSortingRequestControl(opts)
break
}
case ServerSideSortingResponseControl.OID: {
control = new ServerSideSortingResponseControl(opts)
break
}
case VirtualListViewRequestControl.OID: {
control = new VirtualListViewRequestControl(opts)
break
}
case VirtualListViewResponseControl.OID: {
control = new VirtualListViewResponseControl(opts)
break
}
default: {
opts.type = type
control = new Control(opts)
break
}
}
return control
},
Control,
EntryChangeNotificationControl,
PagedResultsControl,
PasswordPolicyControl,
PersistentSearchControl,
ServerSideSortingRequestControl,
ServerSideSortingResponseControl,
VirtualListViewRequestControl,
VirtualListViewResponseControl
}

161
node_modules/@ldapjs/controls/index.test.js generated vendored Normal file
View File

@ -0,0 +1,161 @@
'use strict'
const tap = require('tap')
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const controls = require('.')
tap.test('#getControl', t => {
t.test('requires a BER to parse', async t => {
try {
controls.getControl()
t.fail('should throw exception')
} catch (error) {
t.match(error, /ber must be provided/)
}
})
t.test('returns null for empty BER', async t => {
const result = controls.getControl(new BerReader(Buffer.alloc(0)))
t.equal(result, null)
})
t.test('parses a BER (control)', async t => {
const ber = new BerWriter()
ber.startSequence()
ber.writeString('2.16.840.1.113730.3.4.2')
ber.writeBoolean(true)
ber.writeString('foo')
ber.endSequence()
const control = controls.getControl(new BerReader(ber.buffer))
t.ok(control)
t.equal(control.type, '2.16.840.1.113730.3.4.2')
t.ok(control.criticality)
t.equal(control.value.toString('utf8'), 'foo')
t.end()
})
t.test('parses BER with no value', function (t) {
const ber = new BerWriter()
ber.startSequence()
ber.writeString('2.16.840.1.113730.3.4.2')
ber.endSequence()
const control = controls.getControl(new BerReader(ber.buffer))
t.ok(control)
t.equal(control.type, '2.16.840.1.113730.3.4.2')
t.equal(control.criticality, false)
t.notOk(control.value, null)
t.end()
})
t.test('returns a EntryChangeNotificationControl', async t => {
const ecnc = new controls.EntryChangeNotificationControl({
type: controls.EntryChangeNotificationControl.OID,
criticality: true,
value: {
changeType: 8,
previousDN: 'cn=foobarbazcar',
changeNumber: 123456789
}
})
const ber = new BerWriter()
ecnc.toBer(ber)
const c = controls.getControl(new BerReader(ber.buffer))
t.ok(c)
t.equal(c.type, controls.EntryChangeNotificationControl.OID)
t.ok(c.criticality)
t.equal(c.value.changeType, 8)
t.equal(c.value.previousDN, 'cn=foobarbazcar')
t.equal(c.value.changeNumber, 123456789)
})
t.test('returns a PagedResultsControl', async t => {
const prc = new controls.PagedResultsControl({
type: controls.PagedResultsControl.OID,
criticality: true,
value: {
size: 20,
cookie: Buffer.alloc(0)
}
})
const ber = new BerWriter()
prc.toBer(ber)
const c = controls.getControl(new BerReader(ber.buffer))
t.ok(c)
t.equal(c.type, controls.PagedResultsControl.OID)
t.ok(c.criticality)
t.equal(c.value.size, 20)
t.equal(Buffer.compare(c.value.cookie, Buffer.alloc(0)), 0)
})
t.test('returns a PasswordPolicyControl', async t => {
const ppc = new controls.PasswordPolicyControl({
type: controls.PasswordPolicyControl.OID,
criticality: true,
value: {
error: 1,
timeBeforeExpiration: 2
}
})
const ber = new BerWriter()
ppc.toBer(ber)
const c = controls.getControl(new BerReader(ber.buffer))
t.ok(c)
t.equal(c.type, controls.PasswordPolicyControl.OID)
t.ok(c.criticality)
t.equal(c.value.error, 1)
t.equal(c.value.timeBeforeExpiration, 2)
})
t.test('returns a PersistentSearchControl', async t => {
const buf = Buffer.from([
0x30, 0x26, 0x04, 0x17, 0x32, 0x2e, 0x31, 0x36, 0x2e, 0x38, 0x34, 0x30,
0x2e, 0x31, 0x2e, 0x31, 0x31, 0x33, 0x37, 0x33, 0x30, 0x2e, 0x33, 0x2e,
0x34, 0x2e, 0x33, 0x04, 0x0b, 0x30, 0x09, 0x02, 0x01, 0x0f, 0x01, 0x01,
0xff, 0x01, 0x01, 0xff])
const ber = new BerReader(buf)
const psc = controls.getControl(ber)
t.ok(psc)
t.equal(psc.type, controls.PersistentSearchControl.OID)
t.equal(psc.criticality, false)
t.equal(psc.value.changeTypes, 15)
t.equal(psc.value.changesOnly, true)
t.equal(psc.value.returnECs, true)
})
t.test('returns a ServerSideSortingRequestControl', async t => {
const sssc = new controls.ServerSideSortingRequestControl()
const ber = new BerWriter()
sssc.toBer(ber)
const c = controls.getControl(new BerReader(ber.buffer))
t.ok(c)
t.equal(c.type, controls.ServerSideSortingRequestControl.OID)
t.equal(c.value.length, 0)
})
t.test('returns a ServerSideSortingResponseControl', async t => {
const sssc = new controls.ServerSideSortingResponseControl()
const ber = new BerWriter()
sssc.toBer(ber)
const c = controls.getControl(new BerReader(ber.buffer))
t.ok(c)
t.equal(c.type, controls.ServerSideSortingResponseControl.OID)
t.equal(c.criticality, false)
t.notOk(c.value.result)
t.notOk(c.value.failedAttribute)
})
t.end()
})

88
node_modules/@ldapjs/controls/lib/control.js generated vendored Normal file
View File

@ -0,0 +1,88 @@
'use strict'
const { BerWriter } = require('@ldapjs/asn1')
/**
* Baseline LDAP control object. Implements
* https://tools.ietf.org/html/rfc4511#section-4.1.11
*
* @class
*/
class Control {
/**
* @typedef {object} ControlParams
* @property {string} [type=''] The dotted decimal control type value.
* @property {boolean} [criticality=false] Criticality value for the control.
* @property {string|Buffer} [value] The value for the control. If this is
* a `string` then it will be written as-is. If it is an instance of `Buffer`
* then it will be written by `value.toString()` when generating a BER
* instance.
*/
/**
* Create a new baseline LDAP control.
*
* @param {ControlParams} [options]
*/
constructor (options = {}) {
const opts = Object.assign({ type: '', criticality: false, value: null }, options)
this.type = opts.type
this.criticality = opts.criticality
this.value = opts.value
}
get [Symbol.toStringTag] () {
return 'LdapControl'
}
/**
* Serializes the control into a plain JavaScript object that can be passed
* to the constructor as an options object. If an instance has a `_pojo(obj)`
* method then the built object will be sent to that method and the resulting
* mutated object returned.
*
* @returns {object} A plain JavaScript object that represents an LDAP control.
*/
get pojo () {
const obj = {
type: this.type,
value: this.value,
criticality: this.criticality
}
if (typeof this._pojo === 'function') {
this._pojo(obj)
}
return obj
}
/**
* Converts the instance into a [BER](http://luca.ntop.org/Teaching/Appunti/asn1.html)
* representation.
*
* @param {BerWriter} [ber] An empty `BerWriter` instance to populate.
*
* @returns {object} A BER object.
*/
toBer (ber = new BerWriter()) {
ber.startSequence()
ber.writeString(this.type || '')
ber.writeBoolean(this.criticality)
/* istanbul ignore else */
if (typeof (this._toBer) === 'function') {
this._toBer(ber)
} else if (this.value !== undefined) {
if (typeof this.value === 'string') {
ber.writeString(this.value)
} else if (Buffer.isBuffer(this.value)) {
ber.writeString(this.value.toString())
}
}
ber.endSequence()
return ber
}
}
module.exports = Control

159
node_modules/@ldapjs/controls/lib/control.test.js generated vendored Normal file
View File

@ -0,0 +1,159 @@
'use strict'
const tap = require('tap')
const { BerWriter } = require('@ldapjs/asn1')
const Control = require('./control')
tap.test('constructor', t => {
t.test('new no args', function (t) {
t.ok(new Control())
t.equal(Object.prototype.toString.call(new Control()), '[object LdapControl]')
t.end()
})
t.test('new with args', function (t) {
const c = new Control({
type: '2.16.840.1.113730.3.4.2',
criticality: true
})
t.ok(c)
t.equal(c.type, '2.16.840.1.113730.3.4.2')
t.ok(c.criticality)
t.end()
})
t.end()
})
tap.test('pojo', t => {
t.test('passes through _pojo', async t => {
class Foo extends Control {
_pojo (obj) {
obj.foo = 'foo'
}
}
const control = new Foo()
t.strictSame(control.pojo, {
type: '',
value: null,
criticality: false,
foo: 'foo'
})
})
t.test('returns basic object', async t => {
const control = new Control({ type: '1.2.3', criticality: false, value: 'foo' })
t.strictSame(control.pojo, {
type: '1.2.3',
value: 'foo',
criticality: false
})
})
t.end()
})
tap.test('toBer', t => {
t.test('converts empty instance to BER', async t => {
const target = new BerWriter()
target.startSequence()
target.writeString('')
target.writeBoolean(false)
target.endSequence()
const control = new Control()
const ber = control.toBer()
t.equal(Buffer.compare(ber.buffer, target.buffer), 0)
})
t.test('converts instance to BER', async t => {
const target = new BerWriter()
target.startSequence()
target.writeString('2.16.840.1.113730.3.4.2')
target.writeBoolean(true)
target.writeString('foo')
target.endSequence()
const control = new Control({
type: '2.16.840.1.113730.3.4.2',
criticality: true,
value: Buffer.from('foo', 'utf8')
})
const ber = control.toBer()
t.equal(Buffer.compare(ber.buffer, target.buffer), 0)
})
t.test('converts instance to BER (side effect manner)', async t => {
const target = new BerWriter()
target.startSequence()
target.writeString('2.16.840.1.113730.3.4.2')
target.writeBoolean(true)
target.writeString('foo')
target.endSequence()
const control = new Control({
type: '2.16.840.1.113730.3.4.2',
criticality: true,
value: Buffer.from('foo', 'utf8')
})
const ber = new BerWriter()
control.toBer(ber)
t.equal(Buffer.compare(ber.buffer, target.buffer), 0)
})
t.test('converts instance to BER with string value', async t => {
const target = new BerWriter()
target.startSequence()
target.writeString('2.16.840.1.113730.3.4.2')
target.writeBoolean(true)
target.writeString('foo')
target.endSequence()
const control = new Control({
type: '2.16.840.1.113730.3.4.2',
criticality: true,
value: 'foo'
})
const ber = control.toBer()
t.equal(Buffer.compare(ber.buffer, target.buffer), 0)
})
t.test('ignores unrecognized value', async t => {
const target = new BerWriter()
target.startSequence()
target.writeString('2.16.840.1.113730.3.4.2')
target.writeBoolean(true)
target.writeBoolean(false)
target.endSequence()
const control = new Control({
type: '2.16.840.1.113730.3.4.2',
criticality: true,
value: false
})
const ber = control.toBer()
t.not(Buffer.compare(ber.buffer, target.buffer), 0)
})
t.test('passes through _toBer', async t => {
t.plan(2)
const target = new BerWriter()
target.startSequence()
target.writeString('')
target.writeBoolean(false)
target.endSequence()
const control = new Control()
control._toBer = (ber) => t.ok(ber)
const ber = control.toBer()
t.equal(Buffer.compare(ber.buffer, target.buffer), 0)
})
t.end()
})

View File

@ -0,0 +1,107 @@
'use strict'
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const isObject = require('../is-object')
const hasOwn = require('../has-own')
const Control = require('../control')
/**
* @typedef {object} EntryChangeNotificationControlValue
* @property {number} changeType One of 1 (add), 2 (delete), 4 (modify),
* or 8 (modifyDN).
* @property {string} previousDN Only set when operation is a modifyDN op.
* @property {number} changeNumber
*/
/**
* Implements:
* https://datatracker.ietf.org/doc/html/draft-ietf-ldapext-psearch-03.txt#section-5
*
* @extends Control
*/
class EntryChangeNotificationControl extends Control {
static OID = '2.16.840.1.113730.3.4.7'
/**
* @typedef {ControlParams} EntryChangeNotificationParams
* @property {EntryChangeNotificationControlValue | Buffer} [value]
*/
/**
* Creates a new persistent search control.
*
* @param {EntryChangeNotificationParams} [options]
*/
constructor (options = {}) {
options.type = EntryChangeNotificationControl.OID
super(options)
this._value = {
changeType: 4
}
if (hasOwn(options, 'value') === false) {
return
}
if (Buffer.isBuffer(options.value)) {
this.#parse(options.value)
} else if (isObject(options.value)) {
this._value = options.value
} else {
throw new TypeError('options.value must be a Buffer or Object')
}
}
get value () {
return this._value
}
set value (obj) {
this._value = Object.assign({}, this._value, obj)
}
/**
* Given a BER buffer that represents a
* {@link EntryChangeNotificationControlValue}, read that buffer into the
* current instance.
*/
#parse (buffer) {
const ber = new BerReader(buffer)
/* istanbul ignore else */
if (ber.readSequence()) {
this._value = {
changeType: ber.readInt()
}
/* istanbul ignore else */
if (this._value.changeType === 8) {
// If the operation was moddn, then parse the optional previousDN attr.
this._value.previousDN = ber.readString()
}
this._value.changeNumber = ber.readInt()
}
}
_toBer (ber) {
const writer = new BerWriter()
writer.startSequence()
writer.writeInt(this._value.changeType)
if (this._value.previousDN) { writer.writeString(this._value.previousDN) }
if (Object.prototype.hasOwnProperty.call(this._value, 'changeNumber')) {
writer.writeInt(parseInt(this._value.changeNumber, 10))
}
writer.endSequence()
ber.writeBuffer(writer.buffer, 0x04)
return ber
}
_updatePlainObject (obj) {
obj.controlValue = this.value
return obj
}
}
module.exports = EntryChangeNotificationControl

View File

@ -0,0 +1,133 @@
'use strict'
const tap = require('tap')
const { BerWriter } = require('@ldapjs/asn1')
const ECNC = require('./entry-change-notification-control')
const Control = require('../control')
tap.test('contructor', t => {
t.test('new no args', async t => {
const control = new ECNC()
t.ok(control)
t.type(control, ECNC)
t.type(control, Control)
t.equal(control.type, ECNC.OID)
t.same(control.value, {
changeType: 4
})
})
t.test('new with args', async t => {
const control = new ECNC({
type: '2.16.840.1.113730.3.4.7',
criticality: true,
value: {
changeType: 1
}
})
t.ok(control)
t.equal(control.type, '2.16.840.1.113730.3.4.7')
t.ok(control.criticality)
t.same(control.value, {
changeType: 1
})
})
t.test('with value buffer', async t => {
const value = new BerWriter()
value.startSequence()
value.writeInt(8)
value.writeString('dn=foo')
value.writeInt(42)
value.endSequence()
const control = new ECNC({ value: value.buffer })
t.same(control.value, {
changeType: 8,
previousDN: 'dn=foo',
changeNumber: 42
})
})
t.test('throws for bad value', async t => {
t.throws(() => new ECNC({ value: 42 }))
})
t.end()
})
tap.test('pojo', t => {
t.test('adds control value', async t => {
const control = new ECNC({
value: {
changeType: 8,
previousDN: 'dn=foo',
changeNumber: 42
}
})
t.strictSame(control.pojo, {
type: ECNC.OID,
criticality: false,
value: {
changeType: 8,
previousDN: 'dn=foo',
changeNumber: 42
}
})
})
t.end()
})
tap.test('toBer', t => {
t.test('converts empty instance to BER', async t => {
const target = new BerWriter()
target.startSequence()
target.writeString(ECNC.OID)
target.writeBoolean(false) // Control.criticality
const value = new BerWriter()
value.startSequence()
value.writeInt(4)
// value.writeInt(0)
value.endSequence()
target.writeBuffer(value.buffer, 0x04)
target.endSequence()
const control = new ECNC()
const ber = control.toBer()
t.equal(Buffer.compare(ber.buffer, target.buffer), 0)
})
t.test('converts instance with full values to BER', async t => {
const target = new BerWriter()
target.startSequence()
target.writeString(ECNC.OID)
target.writeBoolean(false) // Control.criticality
const value = new BerWriter()
value.startSequence()
value.writeInt(8)
value.writeString('dn=foo')
value.writeInt(42)
value.endSequence()
target.writeBuffer(value.buffer, 0x04)
target.endSequence()
const control = new ECNC({
value: {
changeType: 8,
previousDN: 'dn=foo',
changeNumber: 42
}
})
const ber = control.toBer()
t.equal(Buffer.compare(ber.buffer, target.buffer), 0)
})
t.end()
})

View File

@ -0,0 +1,103 @@
'use strict'
const { Ber, BerReader, BerWriter } = require('@ldapjs/asn1')
const isObject = require('../is-object')
const hasOwn = require('../has-own')
const Control = require('../control')
/**
* @typedef {object} PagedResultsControlValue
* @property {number} size The requested page size from a client, or the result
* set size estimate from the server.
* @property {Buffer} cookie Identifier for the result set.
*/
/**
* Implements:
* https://datatracker.ietf.org/doc/html/rfc2696#section-2
*
* @extends Control
*/
class PagedResultsControl extends Control {
static OID = '1.2.840.113556.1.4.319'
/**
* @typedef {ControlParams} PagedResultsParams
* @property {PagedResultsControlValue | Buffer} [value]
*/
/**
* Creates a new paged results control.
*
* @param {PagedResultsParams} [options]
*/
constructor (options = {}) {
options.type = PagedResultsControl.OID
super(options)
this._value = {
size: 0,
cookie: Buffer.alloc(0)
}
if (hasOwn(options, 'value') === false) {
return
}
if (Buffer.isBuffer(options.value)) {
this.#parse(options.value)
} else if (isObject(options.value)) {
this.value = options.value
} else {
throw new TypeError('options.value must be a Buffer or Object')
}
}
get value () {
return this._value
}
set value (obj) {
this._value = Object.assign({}, this._value, obj)
if (typeof this._value.cookie === 'string') {
this._value.cookie = Buffer.from(this._value.cookie)
}
}
#parse (buffer) {
const ber = new BerReader(buffer)
/* istanbul ignore else */
if (ber.readSequence()) {
this._value = {}
this._value.size = ber.readInt()
this._value.cookie = ber.readString(Ber.OctetString, true)
// readString returns '' instead of a zero-length buffer
if (!this._value.cookie) {
this._value.cookie = Buffer.alloc(0)
}
}
}
_toBer (ber) {
const writer = new BerWriter()
writer.startSequence()
writer.writeInt(this._value.size)
if (this._value.cookie && this._value.cookie.length > 0) {
writer.writeBuffer(this._value.cookie, Ber.OctetString)
} else {
// writeBuffer rejects zero-length buffers
writer.writeString('')
}
writer.endSequence()
ber.writeBuffer(writer.buffer, Ber.OctetString)
return ber
}
_updatePlainObject (obj) {
obj.controlValue = this.value
return obj
}
}
module.exports = PagedResultsControl

View File

@ -0,0 +1,139 @@
'use strict'
const tap = require('tap')
const { BerWriter } = require('@ldapjs/asn1')
const PSC = require('./paged-results-control')
const Control = require('../control')
tap.test('contructor', t => {
t.test('new no args', async t => {
const control = new PSC()
t.ok(control)
t.type(control, PSC)
t.type(control, Control)
t.equal(control.type, PSC.OID)
t.equal(control.value.size, 0)
t.equal(Buffer.alloc(0).compare(control.value.cookie), 0)
})
t.test('new with args', async t => {
const control = new PSC({
type: '1.2.840.113556.1.4.319',
criticality: true,
value: {
size: 1,
cookie: 'foo'
}
})
t.ok(control)
t.equal(control.type, '1.2.840.113556.1.4.319')
t.ok(control.criticality)
t.equal(control.value.size, 1)
t.equal(Buffer.from('foo').compare(control.value.cookie), 0)
})
t.test('with value buffer', async t => {
const value = new BerWriter()
value.startSequence()
value.writeInt(1)
value.writeBuffer(Buffer.from('foo'), 0x04)
value.endSequence()
const control = new PSC({ value: value.buffer })
t.equal(control.value.size, 1)
t.equal(Buffer.from('foo').compare(control.value.cookie), 0)
})
t.test('with value buffer (empty cookie)', async t => {
const value = new BerWriter()
value.startSequence()
value.writeInt(1)
value.endSequence()
const control = new PSC({ value: value.buffer })
t.equal(control.value.size, 1)
t.equal(Buffer.alloc(0).compare(control.value.cookie), 0)
})
t.test('throws for bad value', async t => {
t.throws(() => new PSC({ value: 42 }))
})
t.end()
})
tap.test('pojo', t => {
t.test('adds control value', async t => {
const control = new PSC({
value: {
size: 1,
cookie: 'foo'
}
})
t.same(control.pojo, {
type: PSC.OID,
criticality: false,
value: {
size: 1,
cookie: Buffer.from('foo')
}
})
})
t.end()
})
tap.test('toBer', t => {
t.test('converts empty instance to BER', async t => {
const target = new BerWriter()
target.startSequence()
target.writeString(PSC.OID)
target.writeBoolean(false) // Control.criticality
const value = new BerWriter()
value.startSequence()
value.writeInt(1)
value.writeString('foo')
value.endSequence()
target.writeBuffer(value.buffer, 0x04)
target.endSequence()
const control = new PSC({
value: {
size: 1,
cookie: 'foo'
}
})
const ber = control.toBer()
t.equal(Buffer.compare(ber.buffer, target.buffer), 0)
})
t.test('converts empty instance to BER (empty cookie)', async t => {
const target = new BerWriter()
target.startSequence()
target.writeString(PSC.OID)
target.writeBoolean(false) // Control.criticality
const value = new BerWriter()
value.startSequence()
value.writeInt(1)
value.writeString('')
value.endSequence()
target.writeBuffer(value.buffer, 0x04)
target.endSequence()
const control = new PSC({
value: {
size: 1
}
})
const ber = control.toBer()
t.equal(Buffer.compare(ber.buffer, target.buffer), 0)
})
t.end()
})

View File

@ -0,0 +1,118 @@
'use strict'
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const isObject = require('../is-object')
const hasOwn = require('../has-own')
const Control = require('../control')
/**
* @typedef {object} PasswordPolicyResponseControlValue
* @property {number} error One of 0 (passwordExpired), 1 (accountLocked),
* 2 (changeAfterReset), 3 (passwordModNotAllowed), 4 (mustSupplyOldPassword),
* 5 (insufficientPasswordQuality), 6 (passwordTooShort), 7 (passwordTooYoung),
* 8 (passwordInHistory), 9 (passwordTooYoung)
* @property {number} timeBeforeExpiration
* @property {number} graceAuthNsRemaining
*/
/**
* Implements both request and response controls:
* https://datatracker.ietf.org/doc/html/draft-behera-ldap-password-policy-11#name-controls-used-for-password-
*
* @extends Control
*/
class PasswordPolicyControl extends Control {
static OID = '1.3.6.1.4.1.42.2.27.8.5.1'
/**
* @typedef {ControlParams} PasswordPolicyResponseParams
* @property {PasswordPolicyResponseControlValue | Buffer} [value]
*/
/**
* Creates a new password policy control.
*
* @param {PasswordPolicyResponseParams} [options]
*/
constructor (options = {}) {
options.type = PasswordPolicyControl.OID
super(options)
this._value = {}
if (hasOwn(options, 'value') === false) {
return
}
if (Buffer.isBuffer(options.value)) {
this.#parse(options.value)
} else if (isObject(options.value)) {
if (hasOwn(options.value, 'timeBeforeExpiration') === true && hasOwn(options.value, 'graceAuthNsRemaining') === true) {
throw new Error('options.value must contain either timeBeforeExpiration or graceAuthNsRemaining, not both')
}
this._value = options.value
} else {
throw new TypeError('options.value must be a Buffer or Object')
}
}
get value () {
return this._value
}
set value (obj) {
this._value = Object.assign({}, this._value, obj)
}
/**
* Given a BER buffer that represents a
* {@link PasswordPolicyResponseControlValue}, read that buffer into the
* current instance.
*/
#parse (buffer) {
const ber = new BerReader(buffer)
if (ber.readSequence()) {
this._value = {}
if (ber.peek() === 0xa0) {
ber.readSequence(0xa0)
if (ber.peek() === 0x80) {
this._value.timeBeforeExpiration = ber._readTag(0x80)
} else if (ber.peek() === 0x81) {
this._value.graceAuthNsRemaining = ber._readTag(0x81)
}
}
if (ber.peek() === 0x81) {
this._value.error = ber._readTag(0x81)
}
}
}
_toBer (ber) {
if (!this._value || Object.keys(this._value).length === 0) { return }
const writer = new BerWriter()
writer.startSequence()
if (hasOwn(this._value, 'timeBeforeExpiration')) {
writer.startSequence(0xa0)
writer.writeInt(this._value.timeBeforeExpiration, 0x80)
writer.endSequence()
} else if (hasOwn(this._value, 'graceAuthNsRemaining')) {
writer.startSequence(0xa0)
writer.writeInt(this._value.graceAuthNsRemaining, 0x81)
writer.endSequence()
}
if (hasOwn(this._value, 'error')) {
writer.writeInt(this._value.error, 0x81)
}
writer.endSequence()
ber.writeBuffer(writer.buffer, 0x04)
return ber
}
_updatePlainObject (obj) {
obj.controlValue = this.value
return obj
}
}
module.exports = PasswordPolicyControl

View File

@ -0,0 +1,113 @@
'use strict'
const tap = require('tap')
const { BerWriter } = require('@ldapjs/asn1')
const PPC = require('./password-policy-control')
const Control = require('../control')
tap.test('contructor', t => {
t.test('new no args', async t => {
const control = new PPC()
t.ok(control)
t.type(control, PPC)
t.type(control, Control)
t.equal(control.type, PPC.OID)
t.same(control.value, {})
})
t.test('new with args', async t => {
const control = new PPC({
type: '1.3.6.1.4.1.42.2.27.8.5.1',
criticality: true,
value: {
error: 1,
timeBeforeExpiration: 2
}
})
t.ok(control)
t.equal(control.type, '1.3.6.1.4.1.42.2.27.8.5.1')
t.ok(control.criticality)
t.same(control.value, {
error: 1,
timeBeforeExpiration: 2
})
})
t.test('with value buffer', async t => {
const value = new BerWriter()
value.startSequence()
value.writeInt(5, 0x81)
value.endSequence()
const control = new PPC({ value: value.buffer })
t.same(control.value, {
error: 5
})
})
t.test('throws for bad value', async t => {
t.throws(() => new PPC({ value: 42 }))
t.throws(() => new PPC({ value: { timeBeforeExpiration: 1, graceAuthNsRemaining: 2 } }))
})
t.end()
})
tap.test('pojo', t => {
t.test('adds control value', async t => {
const control = new PPC()
t.same(control.pojo, {
type: PPC.OID,
criticality: false,
value: {}
})
})
t.end()
})
tap.test('toBer', t => {
t.test('converts empty instance to BER', async t => {
const target = new BerWriter()
target.startSequence()
target.writeString(PPC.OID)
target.writeBoolean(false) // Control.criticality
target.endSequence()
const control = new PPC()
const ber = control.toBer()
t.equal(Buffer.compare(ber.buffer, target.buffer), 0)
})
t.test('converts full instance to BER', async t => {
const target = new BerWriter()
target.startSequence()
target.writeString(PPC.OID)
target.writeBoolean(true) // Control.criticality
const value = new BerWriter()
value.startSequence()
value.startSequence(0xa0)
value.writeInt(2, 0x81)
value.endSequence()
value.writeInt(1, 0x81)
value.endSequence()
target.writeBuffer(value.buffer, 0x04)
target.endSequence()
const control = new PPC({
criticality: true,
value: {
error: 1,
graceAuthNsRemaining: 2
}
})
const ber = control.toBer()
t.equal(Buffer.compare(ber.buffer, target.buffer), 0)
})
t.end()
})

View File

@ -0,0 +1,100 @@
'use strict'
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const isObject = require('../is-object')
const hasOwn = require('../has-own')
const Control = require('../control')
/**
* @typedef {object} PersistentSearchControlValue
* @property {number} changeTypes A bitwise OR of 1 (add), 2 (delete),
* 4 (modify), and 8 (modifyDN).
* @property {boolean} changesOnly
* @property {boolean} returnECs
*/
/**
* Implements:
* https://datatracker.ietf.org/doc/html/draft-ietf-ldapext-psearch-03.txt
*
* @extends Control
*/
class PersistentSearchControl extends Control {
static OID = '2.16.840.1.113730.3.4.3'
/**
* @typedef {ControlParams} PersistentSearchParams
* @property {PersistentSearchControlValue | Buffer} [value]
*/
/**
* Creates a new persistent search control.
*
* @param {PersistentSearchParams} [options]
*/
constructor (options = {}) {
options.type = PersistentSearchControl.OID
super(options)
this._value = {
changeTypes: 15,
changesOnly: true,
returnECs: true
}
if (hasOwn(options, 'value') === false) {
return
}
if (Buffer.isBuffer(options.value)) {
this.#parse(options.value)
} else if (isObject(options.value)) {
this._value = options.value
} else {
throw new TypeError('options.value must be a Buffer or Object')
}
}
get value () {
return this._value
}
set value (obj) {
this._value = Object.assign({}, this._value, obj)
}
/**
* Given a BER buffer that represents a {@link PersistentSearchControlValue},
* read that buffer into the current instance.
*/
#parse (buffer) {
const ber = new BerReader(buffer)
/* istanbul ignore else */
if (ber.readSequence()) {
this._value = {
changeTypes: ber.readInt(),
changesOnly: ber.readBoolean(),
returnECs: ber.readBoolean()
}
}
}
_toBer (ber) {
const writer = new BerWriter()
writer.startSequence()
writer.writeInt(this._value.changeTypes)
writer.writeBoolean(this._value.changesOnly)
writer.writeBoolean(this._value.returnECs)
writer.endSequence()
ber.writeBuffer(writer.buffer, 0x04)
return ber
}
_updatePlainObject (obj) {
obj.controlValue = this.value
return obj
}
}
module.exports = PersistentSearchControl

View File

@ -0,0 +1,106 @@
'use strict'
const tap = require('tap')
const { BerWriter } = require('@ldapjs/asn1')
const PSC = require('./persistent-search-control')
const Control = require('../control')
tap.test('contructor', t => {
t.test('new no args', async t => {
const control = new PSC()
t.ok(control)
t.type(control, PSC)
t.type(control, Control)
t.equal(control.type, PSC.OID)
t.same(control.value, {
changeTypes: 15,
changesOnly: true,
returnECs: true
})
})
t.test('new with args', async t => {
const control = new PSC({
type: '2.16.840.1.113730.3.4.3',
criticality: true,
value: {
changeTypes: 1,
changesOnly: false,
returnECs: true
}
})
t.ok(control)
t.equal(control.type, '2.16.840.1.113730.3.4.3')
t.ok(control.criticality)
t.same(control.value, {
changeTypes: 1,
changesOnly: false,
returnECs: true
})
})
t.test('with value buffer', async t => {
const value = new BerWriter()
value.startSequence()
value.writeInt(2)
value.writeBoolean(true)
value.writeBoolean(false)
value.endSequence()
const control = new PSC({ value: value.buffer })
t.same(control.value, {
changeTypes: 2,
changesOnly: true,
returnECs: false
})
})
t.test('throws for bad value', async t => {
t.throws(() => new PSC({ value: 42 }))
})
t.end()
})
tap.test('pojo', t => {
t.test('adds control value', async t => {
const control = new PSC()
t.same(control.pojo, {
type: PSC.OID,
criticality: false,
value: {
changeTypes: 15,
changesOnly: true,
returnECs: true
}
})
})
t.end()
})
tap.test('toBer', t => {
t.test('converts empty instance to BER', async t => {
const target = new BerWriter()
target.startSequence()
target.writeString(PSC.OID)
target.writeBoolean(false) // Control.criticality
const value = new BerWriter()
value.startSequence()
value.writeInt(15)
value.writeBoolean(true)
value.writeBoolean(true)
value.endSequence()
target.writeBuffer(value.buffer, 0x04)
target.endSequence()
const control = new PSC()
const ber = control.toBer()
t.equal(Buffer.compare(ber.buffer, target.buffer), 0)
})
t.end()
})

View File

@ -0,0 +1,132 @@
'use strict'
const { Ber, BerReader, BerWriter } = require('@ldapjs/asn1')
const isObject = require('../is-object')
const hasOwn = require('../has-own')
const Control = require('../control')
/**
* @typedef {object} SortKeyItem
* @property {string} attributeType
* @property {string} orderingRule
* @property {boolean} reverseOrder
*/
/**
* @typedef {SortKeyItem[]} ServerSideSortingRequestControlValue
*/
/**
* Implements:
* https://datatracker.ietf.org/doc/html/draft-ietf-ldapext-sorting#section-3.1
*
* @extends Control
*/
class ServerSideSortingRequestControl extends Control {
static OID = '1.2.840.113556.1.4.473'
/**
* @typedef {ControlParams} ServerSideSortingRequestParams
* @property {ServerSideSortingRequestControlValue | SortKeyItem | Buffer} [value]
*/
/**
* Creates a new server side sorting request control.
*
* @param {ServerSideSortingRequestParams} [options]
*/
constructor (options = { value: [] }) {
options.type = ServerSideSortingRequestControl.OID
super(options)
const inputValue = options.value ?? []
if (Buffer.isBuffer(inputValue)) {
this.#parse(inputValue)
} else if (Array.isArray(inputValue)) {
for (const obj of inputValue) {
if (isObject(obj) === false) {
throw new Error('Control value must be an object')
}
if (hasOwn(obj, 'attributeType') === false) {
throw new Error('Missing required key: attributeType')
}
}
this.value = inputValue
} else if (isObject(inputValue)) {
if (hasOwn(inputValue, 'attributeType') === false) {
throw new Error('Missing required key: attributeType')
}
this.value = [inputValue]
} else {
throw new TypeError('options.value must be a Buffer, Array or Object')
}
}
get value () {
return this._value
}
set value (items) {
if (Buffer.isBuffer(items) === true) return
if (Array.isArray(items) === false) {
this._value = [items]
return
}
this._value = items
}
#parse (buffer) {
const ber = new BerReader(buffer)
let item
/* istanbul ignore else */
if (ber.readSequence(0x30)) {
this.value = []
while (ber.readSequence(0x30)) {
item = {}
item.attributeType = ber.readString(Ber.OctetString)
/* istanbul ignore else */
if (ber.peek() === 0x80) {
item.orderingRule = ber.readString(0x80)
}
/* istanbul ignore else */
if (ber.peek() === 0x81) {
item.reverseOrder = (ber._readTag(0x81) !== 0)
}
this.value.push(item)
}
}
}
_pojo (obj) {
obj.value = this.value
return obj
}
_toBer (ber) {
if (this.value.length === 0) { return }
const writer = new BerWriter()
writer.startSequence(0x30)
for (let i = 0; i < this.value.length; i++) {
const item = this.value[i]
writer.startSequence(0x30)
/* istanbul ignore else */
if (hasOwn(item, 'attributeType')) {
writer.writeString(item.attributeType, Ber.OctetString)
}
/* istanbul ignore else */
if (hasOwn(item, 'orderingRule')) {
writer.writeString(item.orderingRule, 0x80)
}
/* istanbul ignore else */
if (hasOwn(item, 'reverseOrder')) {
writer.writeBoolean(item.reverseOrder, 0x81)
}
writer.endSequence()
}
writer.endSequence()
ber.writeBuffer(writer.buffer, 0x04)
}
}
module.exports = ServerSideSortingRequestControl

View File

@ -0,0 +1,144 @@
'use strict'
const tap = require('tap')
const { BerWriter } = require('@ldapjs/asn1')
const SSSRC = require('./server-side-sorting-request-control')
const Control = require('../control')
tap.test('contructor', t => {
t.test('new no args', async t => {
const control = new SSSRC()
t.ok(control)
t.type(control, SSSRC)
t.type(control, Control)
t.equal(control.type, SSSRC.OID)
t.same(control.value, [])
})
t.test('new with args', async t => {
const control = new SSSRC({
type: '1.2.840.113556.1.4.473',
criticality: true,
value: [{ attributeType: 'foo' }]
})
t.ok(control)
t.equal(control.type, '1.2.840.113556.1.4.473')
t.ok(control.criticality)
t.same(control.value, [{ attributeType: 'foo' }])
})
t.test('new with object', async t => {
const control = new SSSRC({
type: '1.2.840.113556.1.4.473',
criticality: true,
value: { attributeType: 'foo' }
})
t.ok(control)
t.equal(control.type, '1.2.840.113556.1.4.473')
t.ok(control.criticality)
t.same(control.value, [{ attributeType: 'foo' }])
})
t.test('with value buffer', async t => {
const value = new BerWriter()
value.startSequence(0x30) // Open "array"
value.startSequence(0x30) // Start "item"
value.writeString('foo', 0x04)
value.writeString('bar', 0x80)
value.writeBoolean(false, 0x81)
value.endSequence() // End item
value.endSequence() // Close array
const control = new SSSRC({ value: value.buffer })
t.same(control.value, [{
attributeType: 'foo',
orderingRule: 'bar',
reverseOrder: false
}])
})
t.test('throws for bad value', async t => {
t.throws(() => new SSSRC({ value: 42 }))
})
t.test('throws for bad object value', async t => {
t.throws(() => new SSSRC({ value: { foo: 'bar' } }))
})
t.test('throws for bad array value', async t => {
t.throws(() => new SSSRC({ value: [42] }))
t.throws(() => new SSSRC({ value: [{ foo: 'bar' }] }))
})
t.end()
})
tap.test('pojo', t => {
t.test('adds control value', async t => {
const control = new SSSRC()
t.same(control.pojo, {
type: SSSRC.OID,
criticality: false,
value: []
})
})
t.test('_pojo', async t => {
const control = new SSSRC()
const obj = control._pojo({ value: 'change_me' })
t.strictSame(obj, { value: [] })
})
t.end()
})
tap.test('toBer', t => {
t.test('converts empty instance to BER', async t => {
const target = new BerWriter()
target.startSequence()
target.writeString(SSSRC.OID)
target.writeBoolean(false) // Control.criticality
target.endSequence()
const control = new SSSRC()
const ber = control.toBer()
t.equal(Buffer.compare(ber.buffer, target.buffer), 0)
})
t.test('converts full instance BER', async t => {
const target = new BerWriter()
target.startSequence()
target.writeString(SSSRC.OID)
target.writeBoolean(false) // Control.criticality
const value = new BerWriter()
value.startSequence(0x30) // Open "array"
value.startSequence(0x30) // Start "item"
value.writeString('one', 0x04)
value.writeString('one', 0x80)
value.writeBoolean(false, 0x81)
value.endSequence() // End item
value.startSequence(0x30) // Start "item"
value.writeString('two', 0x04)
value.writeString('two', 0x80)
value.writeBoolean(true, 0x81)
value.endSequence() // End item
value.endSequence() // Close array
target.writeBuffer(value.buffer, 0x04)
target.endSequence()
const control = new SSSRC({
value: [
{ attributeType: 'one', orderingRule: 'one', reverseOrder: false },
{ attributeType: 'two', orderingRule: 'two', reverseOrder: true }
]
})
const ber = control.toBer()
t.equal(Buffer.compare(Buffer.from(target.buffer), ber.buffer), 0)
})
t.end()
})

View File

@ -0,0 +1,129 @@
'use strict'
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const Control = require('../control')
const isObject = require('../is-object')
const hasOwn = require('../has-own')
const { resultCodes: RESULT_CODES } = require('@ldapjs/protocol')
const validCodeNames = [
'SUCCESS',
'OPERATIONS_ERROR',
'TIME_LIMIT_EXCEEDED',
'STRONGER_AUTH_REQUIRED',
'ADMIN_LIMIT_EXCEEDED',
'NO_SUCH_ATTRIBUTE',
'INAPPROPRIATE_MATCHING',
'INSUFFICIENT_ACCESS_RIGHTS',
'BUSY',
'UNWILLING_TO_PERFORM',
'OTHER'
]
const filteredCodes = Object.entries(RESULT_CODES).filter(([k, v]) => validCodeNames.includes(k))
const VALID_CODES = new Map([
...filteredCodes,
...filteredCodes.map(([k, v]) => { return [v, k] })
])
/**
* @typedef {object} ServerSideSortingResponseControlResult
* @property {number} result
* @property {string} failedAttribute
*/
/**
* Implements:
* https://datatracker.ietf.org/doc/html/draft-ietf-ldapext-sorting#section-3.2
*
* @extends Control
*/
class ServerSideSortingResponseControl extends Control {
static OID = '1.2.840.113556.1.4.474'
/**
* A map of possible response codes. Includes `CODE => VALUE` and
* `VALUE => CODE`. For example, `RESPONSE_CODES.get(0)` returns
* `LDAP_SUCCESS`, and `RESPONSE_CODES.get('LDAP_SUCCESS')` returns `0`.
*/
static RESPONSE_CODES = Object.freeze(VALID_CODES)
/**
* @typedef {ControlParams} ServerSideSortingResponseParams
* @property {ServerSideSortingResponseControlResult | Buffer} value
*/
/**
* Creates a new server side sorting response control.
*
* @param {ServerSideSortingResponseParams} [options]
*/
constructor (options = {}) {
options.type = ServerSideSortingResponseControl.OID
options.criticality = false
super(options)
this.value = {}
if (hasOwn(options, 'value') === false || !options.value) {
return
}
const value = options.value
if (Buffer.isBuffer(value)) {
this.#parse(value)
} else if (isObject(value)) {
if (VALID_CODES.has(value.result) === false) {
throw new Error('Invalid result code')
}
if (hasOwn(value, 'failedAttribute') && (typeof value.failedAttribute) !== 'string') {
throw new Error('failedAttribute must be String')
}
this.value = value
} else {
throw new TypeError('options.value must be a Buffer or Object')
}
}
get value () {
return this._value
}
set value (obj) {
this._value = Object.assign({}, this._value, obj)
}
#parse (buffer) {
const ber = new BerReader(buffer)
/* istanbul ignore else */
if (ber.readSequence(0x30)) {
this._value = {}
this._value.result = ber.readEnumeration()
/* istanbul ignore else */
if (ber.peek() === 0x80) {
this._value.failedAttribute = ber.readString(0x80)
}
}
}
_pojo (obj) {
obj.value = this.value
return obj
}
_toBer (ber) {
if (!this._value || Object.keys(this._value).length === 0) { return }
const writer = new BerWriter()
writer.startSequence(0x30)
writer.writeEnumeration(this.value.result)
/* istanbul ignore else */
if (this.value.result !== RESULT_CODES.SUCCESS && this.value.failedAttribute) {
writer.writeString(this.value.failedAttribute, 0x80)
}
writer.endSequence()
ber.writeBuffer(writer.buffer, 0x04)
}
}
module.exports = ServerSideSortingResponseControl

View File

@ -0,0 +1,125 @@
'use strict'
const tap = require('tap')
const { BerWriter } = require('@ldapjs/asn1')
const SSSRC = require('./server-side-sorting-response-control')
const Control = require('../control')
tap.test('constructor', t => {
t.test('new no args', async t => {
const control = new SSSRC()
t.ok(control)
t.type(control, SSSRC)
t.type(control, Control)
t.equal(control.type, SSSRC.OID)
t.same(control.value, {})
})
t.test('new with args', async t => {
const control = new SSSRC({
type: '1.2.840.113556.1.4.474',
criticality: true,
value: {
result: SSSRC.RESPONSE_CODES.get('OPERATIONS_ERROR'),
failedAttribute: 'foo'
}
})
t.ok(control)
t.equal(control.type, '1.2.840.113556.1.4.474')
t.equal(control.criticality, false)
t.same(control.value, {
result: 1,
failedAttribute: 'foo'
})
})
t.test('with value buffer', async t => {
const value = new BerWriter()
value.startSequence(0x30)
value.writeEnumeration(1)
value.writeString('foo', 0x80)
value.endSequence()
const control = new SSSRC({ value: value.buffer })
t.same(control.value, {
result: 1,
failedAttribute: 'foo'
})
})
t.test('throws for bad value', async t => {
t.throws(() => new SSSRC({ value: 42 }))
t.throws(() => new SSSRC({ value: {} }))
t.throws(() => new SSSRC({
value: {
result: 1,
failedAttribute: 42
}
}))
})
t.end()
})
tap.test('pojo', t => {
t.test('adds control value', async t => {
const control = new SSSRC()
t.same(control.pojo, {
type: SSSRC.OID,
criticality: false,
value: {}
})
})
t.test('_pojo', async t => {
const control = new SSSRC()
t.strictSame(control._pojo({ value: 'change_me' }), {
value: {}
})
})
t.end()
})
tap.test('toBer', t => {
t.test('converts empty instance to BER', async t => {
const target = new BerWriter()
target.startSequence()
target.writeString(SSSRC.OID)
target.writeBoolean(false) // Control.criticality
target.endSequence()
const control = new SSSRC()
const ber = control.toBer()
t.equal(Buffer.compare(ber.buffer, target.buffer), 0)
})
t.test('converts full instance to BER', async t => {
const target = new BerWriter()
target.startSequence()
target.writeString(SSSRC.OID)
target.writeBoolean(false) // Control.criticality
const value = new BerWriter()
value.startSequence(0x30)
value.writeEnumeration(1)
value.writeString('foo', 0x80)
value.endSequence()
target.writeBuffer(value.buffer, 0x04)
target.endSequence()
const control = new SSSRC({
value: {
result: SSSRC.RESPONSE_CODES.get('OPERATIONS_ERROR'),
failedAttribute: 'foo'
}
})
const ber = control.toBer()
t.equal(Buffer.compare(ber.buffer, target.buffer), 0)
})
t.end()
})

View File

@ -0,0 +1,116 @@
'use strict'
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const isObject = require('../is-object')
const hasOwn = require('../has-own')
const Control = require('../control')
/**
* @typedef {object} VirtualListViewControlValue
* @property {number} beforeCount
* @property {number} afterCount
*
*/
/**
* Implements:
* https://datatracker.ietf.org/doc/html/draft-ietf-ldapext-ldapv3-vlv-07#section-6.1
*
* @extends Control
*/
class VirtualListViewRequestControl extends Control {
static OID = '2.16.840.1.113730.3.4.9'
/**
* @typedef {ControlParams} VirtualListViewRequestParams
* @property {Buffer|VirtualListViewControlValue} [value]
*/
/**
* @param {VirtualListViewRequestParams} [options]
*/
constructor (options = {}) {
options.type = VirtualListViewRequestControl.OID
super(options)
if (hasOwn(options, 'value') === false) {
// return
throw Error('control is not enabled')
}
if (Buffer.isBuffer(options.value)) {
this.#parse(options.value)
} else if (isObject(options.value)) {
if (Object.prototype.hasOwnProperty.call(options.value, 'beforeCount') === false) {
throw new Error('Missing required key: beforeCount')
}
if (Object.prototype.hasOwnProperty.call(options.value, 'afterCount') === false) {
throw new Error('Missing required key: afterCount')
}
this._value = options.value
} else {
throw new TypeError('options.value must be a Buffer or Object')
}
throw Error('control is not enabled')
}
get value () {
return this._value
}
set value (items) {
if (Buffer.isBuffer(items) === true) return
if (Array.isArray(items) === false) {
this._value = [items]
return
}
this._value = items
}
#parse (buffer) {
const ber = new BerReader(buffer)
if (ber.readSequence()) {
this._value = {}
this._value.beforeCount = ber.readInt()
this._value.afterCount = ber.readInt()
if (ber.peek() === 0xa0) {
if (ber.readSequence(0xa0)) {
this._value.targetOffset = ber.readInt()
this._value.contentCount = ber.readInt()
}
}
if (ber.peek() === 0x81) {
this._value.greaterThanOrEqual = ber.readString(0x81)
}
return true
}
return false
}
_pojo (obj) {
obj.value = this.value
return obj
}
_toBer (ber) {
if (!this._value || this._value.length === 0) {
return
}
const writer = new BerWriter()
writer.startSequence(0x30)
writer.writeInt(this._value.beforeCount)
writer.writeInt(this._value.afterCount)
if (this._value.targetOffset !== undefined) {
writer.startSequence(0xa0)
writer.writeInt(this._value.targetOffset)
writer.writeInt(this._value.contentCount)
writer.endSequence()
} else if (this._value.greaterThanOrEqual !== undefined) {
writer.writeString(this._value.greaterThanOrEqual, 0x81)
}
writer.endSequence()
ber.writeBuffer(writer.buffer, 0x04)
}
}
module.exports = VirtualListViewRequestControl

View File

@ -0,0 +1,110 @@
'use strict'
const tap = require('tap')
tap.test('stubbed', async t => {
t.pass()
})
/**
* This test is disabled. The commented code below is directly copied from
* the original test file in the core `node-ldapjs` repo. The actual test
* suite should follow the patterns of the
* server-side-sorting-request-control.test.js test suite.
*
* See https://github.com/ldapjs/node-ldapjs/pull/797#issuecomment-1094132289
*/
// 'use strict'
// const { test } = require('tap')
// const { BerReader, BerWriter } = require('@ldapjs/asn1')
// const { getControl, VirtualListViewRequestControl: VLVRControl } = require('../../lib')
// test('VLV request - new no args', function (t) {
// t.ok(new VLVRControl())
// t.end()
// })
// test('VLV request - new with args', function (t) {
// const c = new VLVRControl({
// criticality: true,
// value: {
// beforeCount: 0,
// afterCount: 3,
// targetOffset: 1,
// contentCount: 0
// }
// })
// t.ok(c)
// t.equal(c.type, '2.16.840.1.113730.3.4.9')
// t.ok(c.criticality)
// t.equal(c.value.beforeCount, 0)
// t.equal(c.value.afterCount, 3)
// t.equal(c.value.targetOffset, 1)
// t.equal(c.value.contentCount, 0)
// t.end()
// })
// test('VLV request - toBer - with offset', function (t) {
// const vlvc = new VLVRControl({
// criticality: true,
// value: {
// beforeCount: 0,
// afterCount: 3,
// targetOffset: 1,
// contentCount: 0
// }
// })
// const ber = new BerWriter()
// vlvc.toBer(ber)
// const c = getControl(new BerReader(ber.buffer))
// t.ok(c)
// t.equal(c.type, '2.16.840.1.113730.3.4.9')
// t.ok(c.criticality)
// t.equal(c.value.beforeCount, 0)
// t.equal(c.value.afterCount, 3)
// t.equal(c.value.targetOffset, 1)
// t.equal(c.value.contentCount, 0)
// t.end()
// })
// test('VLV request - toBer - with assertion', function (t) {
// const vlvc = new VLVRControl({
// criticality: true,
// value: {
// beforeCount: 0,
// afterCount: 3,
// greaterThanOrEqual: '*foo*'
// }
// })
// const ber = new BerWriter()
// vlvc.toBer(ber)
// const c = getControl(new BerReader(ber.buffer))
// t.ok(c)
// t.equal(c.type, '2.16.840.1.113730.3.4.9')
// t.ok(c.criticality)
// t.equal(c.value.beforeCount, 0)
// t.equal(c.value.afterCount, 3)
// t.equal(c.value.greaterThanOrEqual, '*foo*')
// t.end()
// })
// test('VLV request - toBer - empty', function (t) {
// const vlvc = new VLVRControl()
// const ber = new BerWriter()
// vlvc.toBer(ber)
// const c = getControl(new BerReader(ber.buffer))
// t.ok(c)
// t.equal(c.type, '2.16.840.1.113730.3.4.9')
// t.equal(c.criticality, false)
// t.notOk(c.value.result)
// t.end()
// })

View File

@ -0,0 +1,150 @@
'use strict'
const { Ber, BerReader, BerWriter } = require('@ldapjs/asn1')
const isObject = require('../is-object')
const hasOwn = require('../has-own')
const Control = require('../control')
const { resultCodes: RESULT_CODES } = require('@ldapjs/protocol')
const validCodeNames = [
'SUCCESS',
'OPERATIONS_ERROR',
'UNWILLING_TO_PERFORM',
'INSUFFICIENT_ACCESS_RIGHTS',
'BUSY',
'TIME_LIMIT_EXCEEDED',
'STRONGER_AUTH_REQUIRED',
'ADMIN_LIMIT_EXCEEDED',
'SORT_CONTROL_MISSING',
'OFFSET_RANGE_ERROR',
'CONTROL_ERROR',
'OTHER'
]
const filteredCodes = Object.entries(RESULT_CODES).filter(([k, v]) => validCodeNames.includes(k))
const VALID_CODES = new Map([
...filteredCodes,
...filteredCodes.map(([k, v]) => { return [v, k] })
])
// TODO: complete this doc block based on the "implements" spec link
/**
* @typedef {object} VirtualListViewResponseControlValue
* @property {number} result A valid LDAP response code for the control.
*/
/**
* Implements:
* https://datatracker.ietf.org/doc/html/draft-ietf-ldapext-ldapv3-vlv-07#section-6.2
*
* @extends Control
*/
class VirtualListViewResponseControl extends Control {
static OID = '2.16.840.1.113730.3.4.10'
/**
* A map of possible response codes. Includes `CODE => VALUE` and
* `VALUE => CODE`. For example, `RESPONSE_CODES.get(0)` returns
* `LDAP_SUCCESS`, and `RESPONSE_CODES.get('LDAP_SUCCESS')` returns `0`.
*/
static RESPONSE_CODES = Object.freeze(VALID_CODES)
/**
* @typedef {ControlParams} VirtualListViewResponseParams
* @property {Buffer|VirtualListViewResponseControlValue} [value]
*/
/**
* @param {VirtualListViewResponseParams} options
*/
constructor (options = {}) {
options.type = VirtualListViewResponseControl.OID
options.criticality = false
super(options)
this.value = {}
if (hasOwn(options, 'value') === false || !options.value) {
// return
throw Error('control not enabled')
}
const value = options.value
if (Buffer.isBuffer(value)) {
this.#parse(options.value)
} else if (isObject(value)) {
if (VALID_CODES.has(value.result) === false) {
throw new Error('Invalid result code')
}
this.value = options.value
} else {
throw new TypeError('options.value must be a Buffer or Object')
}
throw Error('control not enabled')
}
get value () {
return this._value
}
set value (obj) {
this._value = Object.assign({}, this._value, obj)
}
#parse (buffer) {
const ber = new BerReader(buffer)
if (ber.readSequence()) {
this._value = {}
if (ber.peek(0x02)) {
this._value.targetPosition = ber.readInt()
}
if (ber.peek(0x02)) {
this._value.contentCount = ber.readInt()
}
this._value.result = ber.readEnumeration()
this._value.cookie = ber.readString(Ber.OctetString, true)
// readString returns '' instead of a zero-length buffer
if (!this._value.cookie) {
this._value.cookie = Buffer.alloc(0)
}
return true
}
return false
}
_pojo (obj) {
obj.value = this.value
return obj
}
_toBer (ber) {
if (this.value.length === 0) { return }
const writer = new BerWriter()
writer.startSequence()
if (this.value.targetPosition !== undefined) {
writer.writeInt(this.value.targetPosition)
}
if (this.value.contentCount !== undefined) {
writer.writeInt(this.value.contentCount)
}
writer.writeEnumeration(this.value.result)
if (this.value.cookie && this.value.cookie.length > 0) {
writer.writeBuffer(this.value.cookie, Ber.OctetString)
} else {
writer.writeString('') // writeBuffer rejects zero-length buffers
}
writer.endSequence()
ber.writeBuffer(writer.buffer, 0x04)
}
}
module.exports = VirtualListViewResponseControl

View File

@ -0,0 +1,84 @@
'use strict'
const tap = require('tap')
tap.test('stubbed', async t => {
t.pass()
})
/**
* This test is disabled. The commented code below is directly copied from
* the original test file in the core `node-ldapjs` repo. The actual test
* suite should follow the patterns of the
* server-side-sorting-response-control.test.js test suite.
*
* See https://github.com/ldapjs/node-ldapjs/pull/797#issuecomment-1094132289
*/
// 'use strict'
// const { test } = require('tap')
// const { BerReader, BerWriter } = require('@ldapjs/asn1')
// const ldap = require('../../lib')
// const { getControl, VirtualListViewResponseControl: VLVResponseControl } = require('../../lib')
// const OID = '2.16.840.1.113730.3.4.10'
// test('VLV response - new no args', function (t) {
// const c = new VLVResponseControl()
// t.ok(c)
// t.equal(c.type, OID)
// t.equal(c.criticality, false)
// t.end()
// })
// test('VLV response - new with args', function (t) {
// const c = new VLVResponseControl({
// criticality: true,
// value: {
// result: ldap.LDAP_SUCCESS,
// targetPosition: 0,
// contentCount: 10
// }
// })
// t.ok(c)
// t.equal(c.type, OID)
// t.equal(c.criticality, false)
// t.equal(c.value.result, ldap.LDAP_SUCCESS)
// t.equal(c.value.targetPosition, 0)
// t.equal(c.value.contentCount, 10)
// t.end()
// })
// test('VLV response - toBer', function (t) {
// const vlpc = new VLVResponseControl({
// value: {
// targetPosition: 0,
// contentCount: 10,
// result: ldap.LDAP_SUCCESS
// }
// })
// const ber = new BerWriter()
// vlpc.toBer(ber)
// const c = getControl(new BerReader(ber.buffer))
// t.ok(c)
// t.equal(c.type, OID)
// t.equal(c.criticality, false)
// t.equal(c.value.result, ldap.LDAP_SUCCESS)
// t.equal(c.value.targetPosition, 0)
// t.equal(c.value.contentCount, 10)
// t.end()
// })
// test('VLV response - toBer - empty', function (t) {
// const vlpc = new VLVResponseControl()
// const ber = new BerWriter()
// vlpc.toBer(ber)
// const c = getControl(new BerReader(ber.buffer))
// t.ok(c)
// t.equal(c.type, OID)
// t.equal(c.criticality, false)
// t.notOk(c.value.result)
// t.end()
// })

5
node_modules/@ldapjs/controls/lib/has-own.js generated vendored Normal file
View File

@ -0,0 +1,5 @@
'use strict'
module.exports = function hasOwn (obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop)
}

5
node_modules/@ldapjs/controls/lib/is-object.js generated vendored Normal file
View File

@ -0,0 +1,5 @@
'use strict'
module.exports = function isObject (input) {
return Object.prototype.toString.call(input) === '[object Object]'
}

View File

@ -0,0 +1,10 @@
name: "CI"
on:
pull_request:
push:
branches:
- master
jobs:
call-core-ci:
uses: ldapjs/.github/.github/workflows/node-ci.yml@main

View File

@ -0,0 +1,4 @@
check-coverage: false
files:
- 'lib/**/*.test.js'

View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2011 Mark Cavage, All rights reserved.
Copyright (c) 2022 The LDAPJS Collaborators.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE

View File

@ -0,0 +1,39 @@
# `@ldapjs/asn1`
`@ldapjs/asn1` is a library for encoding and decoding ASN.1 datatypes in pure
JS. Currently BER encoding is supported.
### Decoding
The following reads an ASN.1 sequence with a boolean.
var Ber = require('@ldapjs/asn1').Ber;
var reader = new Ber.Reader(Buffer.from([0x30, 0x03, 0x01, 0x01, 0xff]));
reader.readSequence();
console.log('Sequence len: ' + reader.length);
if (reader.peek() === Ber.Boolean)
console.log(reader.readBoolean());
### Encoding
The following generates the same payload as above.
var Ber = require('@ldapjs/asn1').Ber;
var writer = new Ber.Writer();
writer.startSequence();
writer.writeBoolean(true);
writer.endSequence();
console.log(writer.buffer);
## Installation
npm install asn1
## Bugs
See <https://github.com/ldapjs/asn1/issues>.

View File

@ -0,0 +1,12 @@
// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
module.exports = {
newInvalidAsn1Error: function (msg) {
const e = new Error()
e.name = 'InvalidAsn1Error'
e.message = msg || ''
return e
}
}

View File

@ -0,0 +1,24 @@
// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
const errors = require('./errors')
const types = require('./types')
const Reader = require('./reader')
const Writer = require('./writer')
// --- Exports
module.exports = {
Reader: Reader,
Writer: Writer
}
for (const t in types) {
if (Object.prototype.hasOwnProperty.call(types, t)) { module.exports[t] = types[t] }
}
for (const e in errors) {
if (Object.prototype.hasOwnProperty.call(errors, e)) { module.exports[e] = errors[e] }
}

View File

@ -0,0 +1,227 @@
// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
const assert = require('assert')
const ASN1 = require('./types')
const errors = require('./errors')
// --- Globals
const newInvalidAsn1Error = errors.newInvalidAsn1Error
// --- API
function Reader (data) {
if (!data || !Buffer.isBuffer(data)) { throw new TypeError('data must be a node Buffer') }
this._buf = data
this._size = data.length
// These hold the "current" state
this._len = 0
this._offset = 0
}
Object.defineProperty(Reader.prototype, Symbol.toStringTag, { value: 'BerReader' })
Object.defineProperty(Reader.prototype, 'length', {
enumerable: true,
get: function () { return (this._len) }
})
Object.defineProperty(Reader.prototype, 'offset', {
enumerable: true,
get: function () { return (this._offset) }
})
Object.defineProperty(Reader.prototype, 'remain', {
get: function () { return (this._size - this._offset) }
})
Object.defineProperty(Reader.prototype, 'buffer', {
get: function () { return (this._buf.slice(this._offset)) }
})
/**
* Reads a single byte and advances offset; you can pass in `true` to make this
* a "peek" operation (i.e., get the byte, but don't advance the offset).
*
* @param {Boolean} peek true means don't move offset.
* @return {Number} the next byte, null if not enough data.
*/
Reader.prototype.readByte = function (peek) {
if (this._size - this._offset < 1) { return null }
const b = this._buf[this._offset] & 0xff
if (!peek) { this._offset += 1 }
return b
}
Reader.prototype.peek = function () {
return this.readByte(true)
}
/**
* Reads a (potentially) variable length off the BER buffer. This call is
* not really meant to be called directly, as callers have to manipulate
* the internal buffer afterwards.
*
* As a result of this call, you can call `Reader.length`, until the
* next thing called that does a readLength.
*
* @return {Number} the amount of offset to advance the buffer.
* @throws {InvalidAsn1Error} on bad ASN.1
*/
Reader.prototype.readLength = function (offset) {
if (offset === undefined) { offset = this._offset }
if (offset >= this._size) { return null }
let lenB = this._buf[offset++] & 0xff
if (lenB === null) { return null }
if ((lenB & 0x80) === 0x80) {
lenB &= 0x7f
if (lenB === 0) { throw newInvalidAsn1Error('Indefinite length not supported') }
if (lenB > 4) { throw newInvalidAsn1Error('encoding too long') }
if (this._size - offset < lenB) { return null }
this._len = 0
for (let i = 0; i < lenB; i++) { this._len = (this._len << 8) + (this._buf[offset++] & 0xff) }
} else {
// Wasn't a variable length
this._len = lenB
}
return offset
}
/**
* Parses the next sequence in this BER buffer.
*
* To get the length of the sequence, call `Reader.length`.
*
* @return {Number} the sequence's tag.
*/
Reader.prototype.readSequence = function (tag) {
const seq = this.peek()
if (seq === null) { return null }
if (tag !== undefined && tag !== seq) {
throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) +
': got 0x' + seq.toString(16))
}
const o = this.readLength(this._offset + 1) // stored in `length`
if (o === null) { return null }
this._offset = o
return seq
}
Reader.prototype.readInt = function () {
return this._readTag(ASN1.Integer)
}
Reader.prototype.readBoolean = function (tag) {
return (this._readTag(tag || ASN1.Boolean) !== 0)
}
Reader.prototype.readEnumeration = function () {
return this._readTag(ASN1.Enumeration)
}
Reader.prototype.readString = function (tag, retbuf) {
if (!tag) { tag = ASN1.OctetString }
const b = this.peek()
if (b === null) { return null }
if (b !== tag) {
throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) +
': got 0x' + b.toString(16))
}
const o = this.readLength(this._offset + 1) // stored in `length`
if (o === null) { return null }
if (this.length > this._size - o) { return null }
this._offset = o
if (this.length === 0) { return retbuf ? Buffer.alloc(0) : '' }
const str = this._buf.slice(this._offset, this._offset + this.length)
this._offset += this.length
return retbuf ? str : str.toString('utf8')
}
Reader.prototype.readOID = function (tag) {
if (!tag) { tag = ASN1.OID }
const b = this.readString(tag, true)
if (b === null) { return null }
const values = []
let value = 0
for (let i = 0; i < b.length; i++) {
const byte = b[i] & 0xff
value <<= 7
value += byte & 0x7f
if ((byte & 0x80) === 0) {
values.push(value)
value = 0
}
}
value = values.shift()
values.unshift(value % 40)
values.unshift((value / 40) >> 0)
return values.join('.')
}
Reader.prototype._readTag = function (tag) {
assert.ok(tag !== undefined)
const b = this.peek()
if (b === null) { return null }
if (b !== tag) {
throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) +
': got 0x' + b.toString(16))
}
const o = this.readLength(this._offset + 1) // stored in `length`
if (o === null) { return null }
if (this.length > 4) { throw newInvalidAsn1Error('Integer too long: ' + this.length) }
if (this.length > this._size - o) { return null }
this._offset = o
const fb = this._buf[this._offset]
let value = 0
let i
for (i = 0; i < this.length; i++) {
value <<= 8
value |= (this._buf[this._offset++] & 0xff)
}
if ((fb & 0x80) === 0x80 && i !== 4) { value -= (1 << (i * 8)) }
return value >> 0
}
// --- Exported API
module.exports = Reader

View File

@ -0,0 +1,182 @@
// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
const { test } = require('tap')
const BerReader = require('./reader')
test('load library', function (t) {
t.ok(BerReader)
try {
const reader = new BerReader()
t.equal(reader, null, 'reader')
t.fail('Should have thrown')
} catch (e) {
t.ok(e instanceof TypeError, 'Should have been a type error')
}
t.end()
})
test('read byte', function (t) {
const reader = new BerReader(Buffer.from([0xde]))
t.ok(reader)
t.equal(reader.readByte(), 0xde, 'wrong value')
t.end()
})
test('read 1 byte int', function (t) {
const reader = new BerReader(Buffer.from([0x02, 0x01, 0x03]))
t.ok(reader)
t.equal(reader.readInt(), 0x03, 'wrong value')
t.equal(reader.length, 0x01, 'wrong length')
t.end()
})
test('read 2 byte int', function (t) {
const reader = new BerReader(Buffer.from([0x02, 0x02, 0x7e, 0xde]))
t.ok(reader)
t.equal(reader.readInt(), 0x7ede, 'wrong value')
t.equal(reader.length, 0x02, 'wrong length')
t.end()
})
test('read 3 byte int', function (t) {
const reader = new BerReader(Buffer.from([0x02, 0x03, 0x7e, 0xde, 0x03]))
t.ok(reader)
t.equal(reader.readInt(), 0x7ede03, 'wrong value')
t.equal(reader.length, 0x03, 'wrong length')
t.end()
})
test('read 4 byte int', function (t) {
const reader = new BerReader(Buffer.from([0x02, 0x04, 0x7e, 0xde, 0x03, 0x01]))
t.ok(reader)
t.equal(reader.readInt(), 0x7ede0301, 'wrong value')
t.equal(reader.length, 0x04, 'wrong length')
t.end()
})
test('read 1 byte negative int', function (t) {
const reader = new BerReader(Buffer.from([0x02, 0x01, 0xdc]))
t.ok(reader)
t.equal(reader.readInt(), -36, 'wrong value')
t.equal(reader.length, 0x01, 'wrong length')
t.end()
})
test('read 2 byte negative int', function (t) {
const reader = new BerReader(Buffer.from([0x02, 0x02, 0xc0, 0x4e]))
t.ok(reader)
t.equal(reader.readInt(), -16306, 'wrong value')
t.equal(reader.length, 0x02, 'wrong length')
t.end()
})
test('read 3 byte negative int', function (t) {
const reader = new BerReader(Buffer.from([0x02, 0x03, 0xff, 0x00, 0x19]))
t.ok(reader)
t.equal(reader.readInt(), -65511, 'wrong value')
t.equal(reader.length, 0x03, 'wrong length')
t.end()
})
test('read 4 byte negative int', function (t) {
const reader = new BerReader(Buffer.from([0x02, 0x04, 0x91, 0x7c, 0x22, 0x1f]))
t.ok(reader)
t.equal(reader.readInt(), -1854135777, 'wrong value')
t.equal(reader.length, 0x04, 'wrong length')
t.end()
})
test('read boolean true', function (t) {
const reader = new BerReader(Buffer.from([0x01, 0x01, 0xff]))
t.ok(reader)
t.equal(reader.readBoolean(), true, 'wrong value')
t.equal(reader.length, 0x01, 'wrong length')
t.end()
})
test('read boolean false', function (t) {
const reader = new BerReader(Buffer.from([0x01, 0x01, 0x00]))
t.ok(reader)
t.equal(reader.readBoolean(), false, 'wrong value')
t.equal(reader.length, 0x01, 'wrong length')
t.end()
})
test('read enumeration', function (t) {
const reader = new BerReader(Buffer.from([0x0a, 0x01, 0x20]))
t.ok(reader)
t.equal(reader.readEnumeration(), 0x20, 'wrong value')
t.equal(reader.length, 0x01, 'wrong length')
t.end()
})
test('read string', function (t) {
const dn = 'cn=foo,ou=unit,o=test'
const buf = Buffer.alloc(dn.length + 2)
buf[0] = 0x04
buf[1] = Buffer.byteLength(dn)
buf.write(dn, 2)
const reader = new BerReader(buf)
t.ok(reader)
t.equal(reader.readString(), dn, 'wrong value')
t.equal(reader.length, dn.length, 'wrong length')
t.end()
})
test('read sequence', function (t) {
const reader = new BerReader(Buffer.from([0x30, 0x03, 0x01, 0x01, 0xff]))
t.ok(reader)
t.equal(reader.readSequence(), 0x30, 'wrong value')
t.equal(reader.length, 0x03, 'wrong length')
t.equal(reader.readBoolean(), true, 'wrong value')
t.equal(reader.length, 0x01, 'wrong length')
t.end()
})
test('anonymous LDAPv3 bind', function (t) {
const BIND = Buffer.alloc(14)
BIND[0] = 0x30 // Sequence
BIND[1] = 12 // len
BIND[2] = 0x02 // ASN.1 Integer
BIND[3] = 1 // len
BIND[4] = 0x04 // msgid (make up 4)
BIND[5] = 0x60 // Bind Request
BIND[6] = 7 // len
BIND[7] = 0x02 // ASN.1 Integer
BIND[8] = 1 // len
BIND[9] = 0x03 // v3
BIND[10] = 0x04 // String (bind dn)
BIND[11] = 0 // len
BIND[12] = 0x80 // ContextSpecific (choice)
BIND[13] = 0 // simple bind
// Start testing ^^
const ber = new BerReader(BIND)
t.equal(ber.readSequence(), 48, 'Not an ASN.1 Sequence')
t.equal(ber.length, 12, 'Message length should be 12')
t.equal(ber.readInt(), 4, 'Message id should have been 4')
t.equal(ber.readSequence(), 96, 'Bind Request should have been 96')
t.equal(ber.length, 7, 'Bind length should have been 7')
t.equal(ber.readInt(), 3, 'LDAP version should have been 3')
t.equal(ber.readString(), '', 'Bind DN should have been empty')
t.equal(ber.length, 0, 'string length should have been 0')
t.equal(ber.readByte(), 0x80, 'Should have been ContextSpecific (choice)')
t.equal(ber.readByte(), 0, 'Should have been simple bind')
t.equal(null, ber.readByte(), 'Should be out of data')
t.end()
})
test('long string', function (t) {
const buf = Buffer.alloc(256)
const s =
'2;649;CN=Red Hat CS 71GA Demo,O=Red Hat CS 71GA Demo,C=US;' +
'CN=RHCS Agent - admin01,UID=admin01,O=redhat,C=US [1] This is ' +
'Teena Vradmin\'s description.'
buf[0] = 0x04
buf[1] = 0x81
buf[2] = 0x94
buf.write(s, 3)
const ber = new BerReader(buf.slice(0, 3 + s.length))
t.equal(ber.readString(), s)
t.end()
})

View File

@ -0,0 +1,35 @@
// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
module.exports = {
EOC: 0,
Boolean: 1,
Integer: 2,
BitString: 3,
OctetString: 4,
Null: 5,
OID: 6,
ObjectDescriptor: 7,
External: 8,
Real: 9, // float
Enumeration: 10,
PDV: 11,
Utf8String: 12,
RelativeOID: 13,
Sequence: 16,
Set: 17,
NumericString: 18,
PrintableString: 19,
T61String: 20,
VideotexString: 21,
IA5String: 22,
UTCTime: 23,
GeneralizedTime: 24,
GraphicString: 25,
VisibleString: 26,
GeneralString: 28,
UniversalString: 29,
CharacterString: 30,
BMPString: 31,
Constructor: 32,
Context: 128
}

View File

@ -0,0 +1,298 @@
// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
const assert = require('assert')
const ASN1 = require('./types')
const errors = require('./errors')
// --- Globals
const newInvalidAsn1Error = errors.newInvalidAsn1Error
const DEFAULT_OPTS = {
size: 1024,
growthFactor: 8
}
// --- Helpers
function merge (from, to) {
assert.ok(from)
assert.equal(typeof (from), 'object')
assert.ok(to)
assert.equal(typeof (to), 'object')
const keys = Object.getOwnPropertyNames(from)
keys.forEach(function (key) {
if (to[key]) { return }
const value = Object.getOwnPropertyDescriptor(from, key)
Object.defineProperty(to, key, value)
})
return to
}
// --- API
function Writer (options) {
options = merge(DEFAULT_OPTS, options || {})
this._buf = Buffer.alloc(options.size || 1024)
this._size = this._buf.length
this._offset = 0
this._options = options
// A list of offsets in the buffer where we need to insert
// sequence tag/len pairs.
this._seq = []
}
Object.defineProperty(Writer.prototype, Symbol.toStringTag, { value: 'BerWriter' })
Object.defineProperty(Writer.prototype, 'buffer', {
get: function () {
if (this._seq.length) { throw newInvalidAsn1Error(this._seq.length + ' unended sequence(s)') }
return (this._buf.slice(0, this._offset))
}
})
/**
* Append a raw buffer to the current writer instance. No validation to
* determine if the buffer represents a valid BER encoding is performed.
*
* @param {Buffer} buffer The buffer to append. If this is not a valid BER
* sequence of data, it will invalidate the BER represented by the `BerWriter`.
*
* @throws If the input is not an instance of Buffer.
*/
Writer.prototype.appendBuffer = function appendBuffer (buffer) {
if (Buffer.isBuffer(buffer) === false) {
throw Error('buffer must be an instance of Buffer')
}
for (const b of buffer.values()) {
this.writeByte(b)
}
}
Writer.prototype.writeByte = function (b) {
if (typeof (b) !== 'number') { throw new TypeError('argument must be a Number') }
this._ensure(1)
this._buf[this._offset++] = b
}
Writer.prototype.writeInt = function (i, tag) {
if (typeof (i) !== 'number') { throw new TypeError('argument must be a Number') }
if (typeof (tag) !== 'number') { tag = ASN1.Integer }
let sz = 4
while ((((i & 0xff800000) === 0) || ((i & 0xff800000) === 0xff800000 >> 0)) &&
(sz > 1)) {
sz--
i <<= 8
}
if (sz > 4) { throw newInvalidAsn1Error('BER ints cannot be > 0xffffffff') }
this._ensure(2 + sz)
this._buf[this._offset++] = tag
this._buf[this._offset++] = sz
while (sz-- > 0) {
this._buf[this._offset++] = ((i & 0xff000000) >>> 24)
i <<= 8
}
}
Writer.prototype.writeNull = function () {
this.writeByte(ASN1.Null)
this.writeByte(0x00)
}
Writer.prototype.writeEnumeration = function (i, tag) {
if (typeof (i) !== 'number') { throw new TypeError('argument must be a Number') }
if (typeof (tag) !== 'number') { tag = ASN1.Enumeration }
return this.writeInt(i, tag)
}
Writer.prototype.writeBoolean = function (b, tag) {
if (typeof (b) !== 'boolean') { throw new TypeError('argument must be a Boolean') }
if (typeof (tag) !== 'number') { tag = ASN1.Boolean }
this._ensure(3)
this._buf[this._offset++] = tag
this._buf[this._offset++] = 0x01
this._buf[this._offset++] = b ? 0xff : 0x00
}
Writer.prototype.writeString = function (s, tag) {
if (typeof (s) !== 'string') { throw new TypeError('argument must be a string (was: ' + typeof (s) + ')') }
if (typeof (tag) !== 'number') { tag = ASN1.OctetString }
const len = Buffer.byteLength(s)
this.writeByte(tag)
this.writeLength(len)
if (len) {
this._ensure(len)
this._buf.write(s, this._offset)
this._offset += len
}
}
Writer.prototype.writeBuffer = function (buf, tag) {
if (typeof (tag) !== 'number') { throw new TypeError('tag must be a number') }
if (!Buffer.isBuffer(buf)) { throw new TypeError('argument must be a buffer') }
this.writeByte(tag)
this.writeLength(buf.length)
this._ensure(buf.length)
buf.copy(this._buf, this._offset, 0, buf.length)
this._offset += buf.length
}
Writer.prototype.writeStringArray = function (strings) {
if (Array.isArray(strings) === false) { throw new TypeError('argument must be an Array[String]') }
const self = this
strings.forEach(function (s) {
self.writeString(s)
})
}
// This is really to solve DER cases, but whatever for now
Writer.prototype.writeOID = function (s, tag) {
if (typeof (s) !== 'string') { throw new TypeError('argument must be a string') }
if (typeof (tag) !== 'number') { tag = ASN1.OID }
if (!/^([0-9]+\.){3,}[0-9]+$/.test(s)) { throw new Error('argument is not a valid OID string') }
function encodeOctet (bytes, octet) {
if (octet < 128) {
bytes.push(octet)
} else if (octet < 16384) {
bytes.push((octet >>> 7) | 0x80)
bytes.push(octet & 0x7F)
} else if (octet < 2097152) {
bytes.push((octet >>> 14) | 0x80)
bytes.push(((octet >>> 7) | 0x80) & 0xFF)
bytes.push(octet & 0x7F)
} else if (octet < 268435456) {
bytes.push((octet >>> 21) | 0x80)
bytes.push(((octet >>> 14) | 0x80) & 0xFF)
bytes.push(((octet >>> 7) | 0x80) & 0xFF)
bytes.push(octet & 0x7F)
} else {
bytes.push(((octet >>> 28) | 0x80) & 0xFF)
bytes.push(((octet >>> 21) | 0x80) & 0xFF)
bytes.push(((octet >>> 14) | 0x80) & 0xFF)
bytes.push(((octet >>> 7) | 0x80) & 0xFF)
bytes.push(octet & 0x7F)
}
}
const tmp = s.split('.')
const bytes = []
bytes.push(parseInt(tmp[0], 10) * 40 + parseInt(tmp[1], 10))
tmp.slice(2).forEach(function (b) {
encodeOctet(bytes, parseInt(b, 10))
})
const self = this
this._ensure(2 + bytes.length)
this.writeByte(tag)
this.writeLength(bytes.length)
bytes.forEach(function (b) {
self.writeByte(b)
})
}
Writer.prototype.writeLength = function (len) {
if (typeof (len) !== 'number') { throw new TypeError('argument must be a Number') }
this._ensure(4)
if (len <= 0x7f) {
this._buf[this._offset++] = len
} else if (len <= 0xff) {
this._buf[this._offset++] = 0x81
this._buf[this._offset++] = len
} else if (len <= 0xffff) {
this._buf[this._offset++] = 0x82
this._buf[this._offset++] = len >> 8
this._buf[this._offset++] = len
} else if (len <= 0xffffff) {
this._buf[this._offset++] = 0x83
this._buf[this._offset++] = len >> 16
this._buf[this._offset++] = len >> 8
this._buf[this._offset++] = len
} else {
throw newInvalidAsn1Error('Length too long (> 4 bytes)')
}
}
Writer.prototype.startSequence = function (tag) {
if (typeof (tag) !== 'number') { tag = ASN1.Sequence | ASN1.Constructor }
this.writeByte(tag)
this._seq.push(this._offset)
this._ensure(3)
this._offset += 3
}
Writer.prototype.endSequence = function () {
const seq = this._seq.pop()
const start = seq + 3
const len = this._offset - start
if (len <= 0x7f) {
this._shift(start, len, -2)
this._buf[seq] = len
} else if (len <= 0xff) {
this._shift(start, len, -1)
this._buf[seq] = 0x81
this._buf[seq + 1] = len
} else if (len <= 0xffff) {
this._buf[seq] = 0x82
this._buf[seq + 1] = len >> 8
this._buf[seq + 2] = len
} else if (len <= 0xffffff) {
this._shift(start, len, 1)
this._buf[seq] = 0x83
this._buf[seq + 1] = len >> 16
this._buf[seq + 2] = len >> 8
this._buf[seq + 3] = len
} else {
throw newInvalidAsn1Error('Sequence too long')
}
}
Writer.prototype._shift = function (start, len, shift) {
assert.ok(start !== undefined)
assert.ok(len !== undefined)
assert.ok(shift)
this._buf.copy(this._buf, start + shift, start, start + len)
this._offset += shift
}
Writer.prototype._ensure = function (len) {
assert.ok(len)
if (this._size - this._offset < len) {
let sz = this._size * this._options.growthFactor
if (sz - this._offset < len) { sz += len }
const buf = Buffer.alloc(sz)
this._buf.copy(buf, 0, 0, this._offset)
this._buf = buf
this._size = sz
}
}
// --- Exported API
module.exports = Writer

View File

@ -0,0 +1,344 @@
// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
const { test } = require('tap')
const BerWriter = require('./writer')
test('write byte', function (t) {
const writer = new BerWriter()
writer.writeByte(0xC2)
const ber = writer.buffer
t.ok(ber)
t.equal(ber.length, 1, 'Wrong length')
t.equal(ber[0], 0xC2, 'value wrong')
t.end()
})
test('write 1 byte int', function (t) {
const writer = new BerWriter()
writer.writeInt(0x7f)
const ber = writer.buffer
t.ok(ber)
t.equal(ber.length, 3, 'Wrong length for an int: ' + ber.length)
t.equal(ber[0], 0x02, 'ASN.1 tag wrong (2) -> ' + ber[0])
t.equal(ber[1], 0x01, 'length wrong(1) -> ' + ber[1])
t.equal(ber[2], 0x7f, 'value wrong(3) -> ' + ber[2])
t.end()
})
test('write 2 byte int', function (t) {
const writer = new BerWriter()
writer.writeInt(0x7ffe)
const ber = writer.buffer
t.ok(ber)
t.equal(ber.length, 4, 'Wrong length for an int')
t.equal(ber[0], 0x02, 'ASN.1 tag wrong')
t.equal(ber[1], 0x02, 'length wrong')
t.equal(ber[2], 0x7f, 'value wrong (byte 1)')
t.equal(ber[3], 0xfe, 'value wrong (byte 2)')
t.end()
})
test('write 3 byte int', function (t) {
const writer = new BerWriter()
writer.writeInt(0x7ffffe)
const ber = writer.buffer
t.ok(ber)
t.equal(ber.length, 5, 'Wrong length for an int')
t.equal(ber[0], 0x02, 'ASN.1 tag wrong')
t.equal(ber[1], 0x03, 'length wrong')
t.equal(ber[2], 0x7f, 'value wrong (byte 1)')
t.equal(ber[3], 0xff, 'value wrong (byte 2)')
t.equal(ber[4], 0xfe, 'value wrong (byte 3)')
t.end()
})
test('write 4 byte int', function (t) {
const writer = new BerWriter()
writer.writeInt(0x7ffffffe)
const ber = writer.buffer
t.ok(ber)
t.equal(ber.length, 6, 'Wrong length for an int')
t.equal(ber[0], 0x02, 'ASN.1 tag wrong')
t.equal(ber[1], 0x04, 'length wrong')
t.equal(ber[2], 0x7f, 'value wrong (byte 1)')
t.equal(ber[3], 0xff, 'value wrong (byte 2)')
t.equal(ber[4], 0xff, 'value wrong (byte 3)')
t.equal(ber[5], 0xfe, 'value wrong (byte 4)')
t.end()
})
test('write 1 byte negative int', function (t) {
const writer = new BerWriter()
writer.writeInt(-128)
const ber = writer.buffer
t.ok(ber)
t.equal(ber.length, 3, 'Wrong length for an int')
t.equal(ber[0], 0x02, 'ASN.1 tag wrong')
t.equal(ber[1], 0x01, 'length wrong')
t.equal(ber[2], 0x80, 'value wrong (byte 1)')
t.end()
})
test('write 2 byte negative int', function (t) {
const writer = new BerWriter()
writer.writeInt(-22400)
const ber = writer.buffer
t.ok(ber)
t.equal(ber.length, 4, 'Wrong length for an int')
t.equal(ber[0], 0x02, 'ASN.1 tag wrong')
t.equal(ber[1], 0x02, 'length wrong')
t.equal(ber[2], 0xa8, 'value wrong (byte 1)')
t.equal(ber[3], 0x80, 'value wrong (byte 2)')
t.end()
})
test('write 3 byte negative int', function (t) {
const writer = new BerWriter()
writer.writeInt(-481653)
const ber = writer.buffer
t.ok(ber)
t.equal(ber.length, 5, 'Wrong length for an int')
t.equal(ber[0], 0x02, 'ASN.1 tag wrong')
t.equal(ber[1], 0x03, 'length wrong')
t.equal(ber[2], 0xf8, 'value wrong (byte 1)')
t.equal(ber[3], 0xa6, 'value wrong (byte 2)')
t.equal(ber[4], 0x8b, 'value wrong (byte 3)')
t.end()
})
test('write 4 byte negative int', function (t) {
const writer = new BerWriter()
writer.writeInt(-1522904131)
const ber = writer.buffer
t.ok(ber)
t.equal(ber.length, 6, 'Wrong length for an int')
t.equal(ber[0], 0x02, 'ASN.1 tag wrong')
t.equal(ber[1], 0x04, 'length wrong')
t.equal(ber[2], 0xa5, 'value wrong (byte 1)')
t.equal(ber[3], 0x3a, 'value wrong (byte 2)')
t.equal(ber[4], 0x53, 'value wrong (byte 3)')
t.equal(ber[5], 0xbd, 'value wrong (byte 4)')
t.end()
})
test('write boolean', function (t) {
const writer = new BerWriter()
writer.writeBoolean(true)
writer.writeBoolean(false)
const ber = writer.buffer
t.ok(ber)
t.equal(ber.length, 6, 'Wrong length')
t.equal(ber[0], 0x01, 'tag wrong')
t.equal(ber[1], 0x01, 'length wrong')
t.equal(ber[2], 0xff, 'value wrong')
t.equal(ber[3], 0x01, 'tag wrong')
t.equal(ber[4], 0x01, 'length wrong')
t.equal(ber[5], 0x00, 'value wrong')
t.end()
})
test('write string', function (t) {
const writer = new BerWriter()
writer.writeString('hello world')
const ber = writer.buffer
t.ok(ber)
t.equal(ber.length, 13, 'wrong length')
t.equal(ber[0], 0x04, 'wrong tag')
t.equal(ber[1], 11, 'wrong length')
t.equal(ber.slice(2).toString('utf8'), 'hello world', 'wrong value')
t.end()
})
test('write buffer', function (t) {
const writer = new BerWriter()
// write some stuff to start with
writer.writeString('hello world')
let ber = writer.buffer
const buf = Buffer.from([0x04, 0x0b, 0x30, 0x09, 0x02, 0x01, 0x0f, 0x01, 0x01,
0xff, 0x01, 0x01, 0xff])
writer.writeBuffer(buf.slice(2, buf.length), 0x04)
ber = writer.buffer
t.ok(ber)
t.equal(ber.length, 26, 'wrong length')
t.equal(ber[0], 0x04, 'wrong tag')
t.equal(ber[1], 11, 'wrong length')
t.equal(ber.slice(2, 13).toString('utf8'), 'hello world', 'wrong value')
t.equal(ber[13], buf[0], 'wrong tag')
t.equal(ber[14], buf[1], 'wrong length')
for (let i = 13, j = 0; i < ber.length && j < buf.length; i++, j++) {
t.equal(ber[i], buf[j], 'buffer contents not identical')
}
t.end()
})
test('write string array', function (t) {
const writer = new BerWriter()
writer.writeStringArray(['hello world', 'fubar!'])
const ber = writer.buffer
t.ok(ber)
t.equal(ber.length, 21, 'wrong length')
t.equal(ber[0], 0x04, 'wrong tag')
t.equal(ber[1], 11, 'wrong length')
t.equal(ber.slice(2, 13).toString('utf8'), 'hello world', 'wrong value')
t.equal(ber[13], 0x04, 'wrong tag')
t.equal(ber[14], 6, 'wrong length')
t.equal(ber.slice(15).toString('utf8'), 'fubar!', 'wrong value')
t.end()
})
test('resize internal buffer', function (t) {
const writer = new BerWriter({ size: 2 })
writer.writeString('hello world')
const ber = writer.buffer
t.ok(ber)
t.equal(ber.length, 13, 'wrong length')
t.equal(ber[0], 0x04, 'wrong tag')
t.equal(ber[1], 11, 'wrong length')
t.equal(ber.slice(2).toString('utf8'), 'hello world', 'wrong value')
t.end()
})
test('sequence', function (t) {
const writer = new BerWriter({ size: 25 })
writer.startSequence()
writer.writeString('hello world')
writer.endSequence()
const ber = writer.buffer
t.ok(ber)
t.equal(ber.length, 15, 'wrong length')
t.equal(ber[0], 0x30, 'wrong tag')
t.equal(ber[1], 13, 'wrong length')
t.equal(ber[2], 0x04, 'wrong tag')
t.equal(ber[3], 11, 'wrong length')
t.equal(ber.slice(4).toString('utf8'), 'hello world', 'wrong value')
t.end()
})
test('nested sequence', function (t) {
const writer = new BerWriter({ size: 25 })
writer.startSequence()
writer.writeString('hello world')
writer.startSequence()
writer.writeString('hello world')
writer.endSequence()
writer.endSequence()
const ber = writer.buffer
t.ok(ber)
t.equal(ber.length, 30, 'wrong length')
t.equal(ber[0], 0x30, 'wrong tag')
t.equal(ber[1], 28, 'wrong length')
t.equal(ber[2], 0x04, 'wrong tag')
t.equal(ber[3], 11, 'wrong length')
t.equal(ber.slice(4, 15).toString('utf8'), 'hello world', 'wrong value')
t.equal(ber[15], 0x30, 'wrong tag')
t.equal(ber[16], 13, 'wrong length')
t.equal(ber[17], 0x04, 'wrong tag')
t.equal(ber[18], 11, 'wrong length')
t.equal(ber.slice(19, 30).toString('utf8'), 'hello world', 'wrong value')
t.end()
})
test('LDAP bind message', function (t) {
const dn = 'cn=foo,ou=unit,o=test'
const writer = new BerWriter()
writer.startSequence()
writer.writeInt(3) // msgid = 3
writer.startSequence(0x60) // ldap bind
writer.writeInt(3) // ldap v3
writer.writeString(dn)
writer.writeByte(0x80)
writer.writeByte(0x00)
writer.endSequence()
writer.endSequence()
const ber = writer.buffer
t.ok(ber)
t.equal(ber.length, 35, 'wrong length (buffer)')
t.equal(ber[0], 0x30, 'wrong tag')
t.equal(ber[1], 33, 'wrong length')
t.equal(ber[2], 0x02, 'wrong tag')
t.equal(ber[3], 1, 'wrong length')
t.equal(ber[4], 0x03, 'wrong value')
t.equal(ber[5], 0x60, 'wrong tag')
t.equal(ber[6], 28, 'wrong length')
t.equal(ber[7], 0x02, 'wrong tag')
t.equal(ber[8], 1, 'wrong length')
t.equal(ber[9], 0x03, 'wrong value')
t.equal(ber[10], 0x04, 'wrong tag')
t.equal(ber[11], dn.length, 'wrong length')
t.equal(ber.slice(12, 33).toString('utf8'), dn, 'wrong value')
t.equal(ber[33], 0x80, 'wrong tag')
t.equal(ber[34], 0x00, 'wrong len')
t.end()
})
test('Write OID', function (t) {
const oid = '1.2.840.113549.1.1.1'
const writer = new BerWriter()
writer.writeOID(oid)
const expected = Buffer.from([0x06, 0x09, 0x2a, 0x86,
0x48, 0x86, 0xf7, 0x0d,
0x01, 0x01, 0x01])
const ber = writer.buffer
t.equal(ber.compare(expected), 0)
t.end()
})
test('appendBuffer appends a buffer', async t => {
const expected = Buffer.from([0x04, 0x03, 0x66, 0x6f, 0x6f, 0x66, 0x6f, 0x6f])
const writer = new BerWriter()
writer.writeString('foo')
writer.appendBuffer(Buffer.from('foo'))
t.equal(Buffer.compare(writer.buffer, expected), 0)
})

View File

@ -0,0 +1,18 @@
// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
// If you have no idea what ASN.1 or BER is, see this:
// https://web.archive.org/web/20220314051854/http://luca.ntop.org/Teaching/Appunti/asn1.html
const Ber = require('./ber/index')
// --- Exported API
module.exports = {
Ber: Ber,
BerReader: Ber.Reader,
BerWriter: Ber.Writer
}

View File

@ -0,0 +1,34 @@
{
"originalAuthor": "Joyent (joyent.com)",
"contributors": [
"Mark Cavage <mcavage@gmail.com>",
"David Gwynne <loki@animata.net>",
"Yunong Xiao <yunong@joyent.com>",
"Alex Wilson <alex.wilson@joyent.com>"
],
"name": "@ldapjs/asn1",
"description": "Contains parsers and serializers for ASN.1 (currently BER only)",
"version": "1.2.0",
"repository": {
"type": "git",
"url": "git://github.com/ldapjs/asn1.git"
},
"main": "lib/index.js",
"devDependencies": {
"@fastify/pre-commit": "^2.0.2",
"standard": "^16.0.4",
"tap": "^16.0.1"
},
"scripts": {
"test": "tap --no-coverage-report -R terse",
"test:cov": "tap -R terse",
"test:cov:html": "tap -R terse --coverage-report=html",
"test:watch": "tap -w --no-coverage-report -R terse",
"lint": "standard"
},
"license": "MIT",
"pre-commit": [
"lint",
"test"
]
}

45
node_modules/@ldapjs/controls/package.json generated vendored Normal file
View File

@ -0,0 +1,45 @@
{
"name": "@ldapjs/controls",
"version": "2.1.0",
"description": "LDAP control objects",
"main": "index.js",
"scripts": {
"lint": "eslint .",
"lint:ci": "eslint .",
"test": "tap --no-coverage-report -R terse",
"test:cov": "tap -R terse",
"test:cov:html": "tap -R terse --coverage-report=html",
"test:watch": "tap -w --no-coverage-report -R terse"
},
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/ldapjs/controls.git"
},
"keywords": [
"ldapjs"
],
"author": "James Sumners",
"license": "MIT",
"bugs": {
"url": "https://github.com/ldapjs/controls/issues"
},
"homepage": "https://github.com/ldapjs/controls#readme",
"devDependencies": {
"@fastify/pre-commit": "^2.0.2",
"eslint": "^8.34.0",
"eslint-config-standard": "^17.0.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-n": "^15.6.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.1.1",
"tap": "^16.3.4"
},
"dependencies": {
"@ldapjs/asn1": "^1.2.0",
"@ldapjs/protocol": "^1.2.1"
},
"pre-commit": [
"lint",
"test"
]
}