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

1345
node_modules/ldapjs/lib/client/client.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

7
node_modules/ldapjs/lib/client/constants.js generated vendored Normal file
View File

@ -0,0 +1,7 @@
'use strict'
module.exports = {
// https://tools.ietf.org/html/rfc4511#section-4.1.1
// Message identifiers are an integer between (0, maxint).
MAX_MSGID: Math.pow(2, 31) - 1
}

23
node_modules/ldapjs/lib/client/index.js generated vendored Normal file
View File

@ -0,0 +1,23 @@
'use strict'
const logger = require('../logger')
const Client = require('./client')
module.exports = {
Client,
createClient: function createClient (options) {
if (isObject(options) === false) throw TypeError('options (object) required')
if (options.url && typeof options.url !== 'string' && !Array.isArray(options.url)) throw TypeError('options.url (string|array) required')
if (options.socketPath && typeof options.socketPath !== 'string') throw TypeError('options.socketPath must be a string')
if ((options.url && options.socketPath) || !(options.url || options.socketPath)) throw TypeError('options.url ^ options.socketPath (String) required')
if (!options.log) options.log = logger
if (isObject(options.log) !== true) throw TypeError('options.log must be an object')
if (!options.log.child) options.log.child = function () { return options.log }
return new Client(options)
}
}
function isObject (input) {
return Object.prototype.toString.apply(input) === '[object Object]'
}

View File

@ -0,0 +1,25 @@
'use strict'
const { MAX_MSGID } = require('../constants')
/**
* Compare a reference id with another id to determine "greater than or equal"
* between the two values according to a sliding window.
*
* @param {integer} ref
* @param {integer} comp
*
* @returns {boolean} `true` if the `comp` value is >= to the `ref` value
* within the computed window, otherwise `false`.
*/
module.exports = function geWindow (ref, comp) {
let max = ref + Math.floor(MAX_MSGID / 2)
const min = ref
if (max >= MAX_MSGID) {
// Handle roll-over
max = max - MAX_MSGID - 1
return ((comp <= max) || (comp >= min))
} else {
return ((comp <= max) && (comp >= min))
}
}

View File

@ -0,0 +1,23 @@
'use strict'
const { MAX_MSGID } = require('../constants')
/**
* Returns a function that generates message identifiers. According to RFC 4511
* the identifers should be `(0, MAX_MSGID)`. The returned function handles
* this and wraps around when the maximum has been reached.
*
* @param {integer} [start=0] Starting number in the identifier sequence.
*
* @returns {function} This function accepts no parameters and returns an
* increasing sequence identifier each invocation until it reaches the maximum
* identifier. At this point the sequence starts over.
*/
module.exports = function idGeneratorFactory (start = 0) {
let currentID = start
return function nextID () {
const id = currentID + 1
currentID = (id >= MAX_MSGID) ? 1 : id
return currentID
}
}

161
node_modules/ldapjs/lib/client/message-tracker/index.js generated vendored Normal file
View File

@ -0,0 +1,161 @@
'use strict'
const idGeneratorFactory = require('./id-generator')
const purgeAbandoned = require('./purge-abandoned')
/**
* Returns a message tracker object that keeps track of which message
* identifiers correspond to which message handlers. Also handles keeping track
* of abandoned messages.
*
* @param {object} options
* @param {string} options.id An identifier for the tracker.
* @param {object} options.parser An object that will be used to parse messages.
*
* @returns {MessageTracker}
*/
module.exports = function messageTrackerFactory (options) {
if (Object.prototype.toString.call(options) !== '[object Object]') {
throw Error('options object is required')
}
if (!options.id || typeof options.id !== 'string') {
throw Error('options.id string is required')
}
if (!options.parser || Object.prototype.toString.call(options.parser) !== '[object Object]') {
throw Error('options.parser object is required')
}
let currentID = 0
const nextID = idGeneratorFactory()
const messages = new Map()
const abandoned = new Map()
/**
* @typedef {object} MessageTracker
* @property {string} id The identifier of the tracker as supplied via the options.
* @property {object} parser The parser object given by the the options.
*/
const tracker = {
id: options.id,
parser: options.parser
}
/**
* Count of messages awaiting response.
*
* @alias pending
* @memberof! MessageTracker#
*/
Object.defineProperty(tracker, 'pending', {
get () {
return messages.size
}
})
/**
* Move a specific message to the abanded track.
*
* @param {integer} msgID The identifier for the message to move.
*
* @memberof MessageTracker
* @method abandon
*/
tracker.abandon = function abandonMessage (msgID) {
if (messages.has(msgID) === false) return false
const toAbandon = messages.get(msgID)
abandoned.set(msgID, {
age: currentID,
message: toAbandon.message,
cb: toAbandon.callback
})
return messages.delete(msgID)
}
/**
* @typedef {object} Tracked
* @property {object} message The tracked message. Usually the outgoing
* request object.
* @property {Function} callback The handler to use when receiving a
* response to the tracked message.
*/
/**
* Retrieves the message handler for a message. Removes abandoned messages
* that have been given time to be resolved.
*
* @param {integer} msgID The identifier for the message to get the handler for.
*
* @memberof MessageTracker
* @method fetch
*/
tracker.fetch = function fetchMessage (msgID) {
const tracked = messages.get(msgID)
if (tracked) {
purgeAbandoned(msgID, abandoned)
return tracked
}
// We sent an abandon request but the server either wasn't able to process
// it or has not received it yet. Therefore, we received a response for the
// abandoned message. So we must return the abandoned message's callback
// to be processed normally.
const abandonedMsg = abandoned.get(msgID)
if (abandonedMsg) {
return { message: abandonedMsg, callback: abandonedMsg.cb }
}
return null
}
/**
* Removes all message tracks, cleans up the abandoned track, and invokes
* a callback for each message purged.
*
* @param {function} cb A function with the signature `(msgID, handler)`.
*
* @memberof MessageTracker
* @method purge
*/
tracker.purge = function purgeMessages (cb) {
messages.forEach((val, key) => {
purgeAbandoned(key, abandoned)
tracker.remove(key)
cb(key, val.callback)
})
}
/**
* Removes a message from all tracking.
*
* @param {integer} msgID The identifier for the message to remove from tracking.
*
* @memberof MessageTracker
* @method remove
*/
tracker.remove = function removeMessage (msgID) {
if (messages.delete(msgID) === false) {
abandoned.delete(msgID)
}
}
/**
* Add a message handler to be tracked.
*
* @param {object} message The message object to be tracked. This object will
* have a new property added to it: `messageId`.
* @param {function} callback The handler for the message.
*
* @memberof MessageTracker
* @method track
*/
tracker.track = function trackMessage (message, callback) {
currentID = nextID()
// This side effect is not ideal but the client doesn't attach the tracker
// to itself until after the `.connect` method has fired. If this can be
// refactored later, then we can possibly get rid of this side effect.
message.messageId = currentID
messages.set(currentID, { callback, message })
}
return tracker
}

View File

@ -0,0 +1,34 @@
'use strict'
const { AbandonedError } = require('../../errors')
const geWindow = require('./ge-window')
/**
* Given a `msgID` and a set of `abandoned` messages, remove any abandoned
* messages that existed _prior_ to the specified `msgID`. For example, let's
* assume the server has sent 3 messages:
*
* 1. A search message.
* 2. An abandon message for the search message.
* 3. A new search message.
*
* When the response for message #1 comes in, if it does, it will be processed
* normally due to the specification. Message #2 will not receive a response, or
* if the server does send one since the spec sort of allows it, we won't do
* anything with it because we just discard that listener. Now the response
* for message #3 comes in. At this point, we will issue a purge of responses
* by passing in `msgID = 3`. This result is that we will remove the tracking
* for message #1.
*
* @param {integer} msgID An upper bound for the messages to be purged.
* @param {Map} abandoned A set of abandoned messages. Each message is an object
* `{ age: <id>, cb: <func> }` where `age` was the current message id when the
* abandon message was sent.
*/
module.exports = function purgeAbandoned (msgID, abandoned) {
abandoned.forEach((val, key) => {
if (geWindow(val.age, msgID) === false) return
val.cb(new AbandonedError('client request abandoned'))
abandoned.delete(key)
})
}

View File

@ -0,0 +1,36 @@
'use strict'
/**
* Adds requests to the queue. If a timeout has been added to the queue then
* this will freeze the queue with the newly added item, flush it, and then
* unfreeze it when the queue has been cleared.
*
* @param {object} message An LDAP message object.
* @param {object} expect An expectation object.
* @param {object} emitter An event emitter or `null`.
* @param {function} cb A callback to invoke when the request is finished.
*
* @returns {boolean} `true` if the requested was queued. `false` if the queue
* is not accepting any requests.
*/
module.exports = function enqueue (message, expect, emitter, cb) {
if (this._queue.size >= this.size || this._frozen) {
return false
}
this._queue.add({ message, expect, emitter, cb })
if (this.timeout === 0) return true
if (this._timer === null) return true
// A queue can have a specified time allotted for it to be cleared. If that
// time has been reached, reject new entries until the queue has been cleared.
this._timer = setTimeout(queueTimeout.bind(this), this.timeout)
return true
function queueTimeout () {
this.freeze()
this.purge()
}
}

24
node_modules/ldapjs/lib/client/request-queue/flush.js generated vendored Normal file
View File

@ -0,0 +1,24 @@
'use strict'
/**
* Invokes all requests in the queue by passing them to the supplied callback
* function and then clears all items from the queue.
*
* @param {function} cb A function used to handle the requests.
*/
module.exports = function flush (cb) {
if (this._timer) {
clearTimeout(this._timer)
this._timer = null
}
// We must get a local copy of the queue and clear it before iterating it.
// The client will invoke this flush function _many_ times. If we try to
// iterate it without a local copy and clearing first then we will overflow
// the stack.
const requests = Array.from(this._queue.values())
this._queue.clear()
for (const req of requests) {
cb(req.message, req.expect, req.emitter, req.cb)
}
}

39
node_modules/ldapjs/lib/client/request-queue/index.js generated vendored Normal file
View File

@ -0,0 +1,39 @@
'use strict'
const enqueue = require('./enqueue')
const flush = require('./flush')
const purge = require('./purge')
/**
* Builds a request queue object and returns it.
*
* @param {object} [options]
* @param {integer} [options.size] Maximum size of the request queue. Must be
* a number greater than `0` if supplied. Default: `Infinity`.
* @param {integer} [options.timeout] Time in milliseconds a queue has to
* complete the requests it contains.
*
* @returns {object} A queue instance.
*/
module.exports = function requestQueueFactory (options) {
const opts = Object.assign({}, options)
const q = {
size: (opts.size > 0) ? opts.size : Infinity,
timeout: (opts.timeout > 0) ? opts.timeout : 0,
_queue: new Set(),
_timer: null,
_frozen: false
}
q.enqueue = enqueue.bind(q)
q.flush = flush.bind(q)
q.purge = purge.bind(q)
q.freeze = function freeze () {
this._frozen = true
}
q.thaw = function thaw () {
this._frozen = false
}
return q
}

12
node_modules/ldapjs/lib/client/request-queue/purge.js generated vendored Normal file
View File

@ -0,0 +1,12 @@
'use strict'
const { TimeoutError } = require('../../errors')
/**
* Flushes the queue by rejecting all pending requests with a timeout error.
*/
module.exports = function purge () {
this.flush(function flushCB (a, b, c, cb) {
cb(new TimeoutError('request queue timeout'))
})
}

167
node_modules/ldapjs/lib/client/search_pager.js generated vendored Normal file
View File

@ -0,0 +1,167 @@
'use strict'
const EventEmitter = require('events').EventEmitter
const util = require('util')
const assert = require('assert-plus')
const { PagedResultsControl } = require('@ldapjs/controls')
const CorkedEmitter = require('../corked_emitter.js')
/// --- API
/**
* Handler object for paged search operations.
*
* Provided to consumers in place of the normal search EventEmitter it adds the
* following new events:
* 1. page - Emitted whenever the end of a result page is encountered.
* If this is the last page, 'end' will also be emitted.
* The event passes two arguments:
* 1. The result object (similar to 'end')
* 2. A callback function optionally used to continue the search
* operation if the pagePause option was specified during
* initialization.
* 2. pageError - Emitted if the server does not support paged search results
* If there are no listeners for this event, the 'error' event
* will be emitted (and 'end' will not be). By listening to
* 'pageError', a successful search that lacks paging will be
* able to emit 'end'.
*/
function SearchPager (opts) {
assert.object(opts)
assert.func(opts.callback)
assert.number(opts.pageSize)
assert.func(opts.sendRequest)
CorkedEmitter.call(this, {})
this.callback = opts.callback
this.controls = opts.controls
this.pageSize = opts.pageSize
this.pagePause = opts.pagePause
this.sendRequest = opts.sendRequest
this.controls.forEach(function (control) {
if (control.type === PagedResultsControl.OID) {
// The point of using SearchPager is not having to do this.
// Toss an error if the pagedResultsControl is present
throw new Error('redundant pagedResultControl')
}
})
this.finished = false
this.started = false
const emitter = new EventEmitter()
emitter.on('searchRequest', this.emit.bind(this, 'searchRequest'))
emitter.on('searchEntry', this.emit.bind(this, 'searchEntry'))
emitter.on('end', this._onEnd.bind(this))
emitter.on('error', this._onError.bind(this))
this.childEmitter = emitter
}
util.inherits(SearchPager, CorkedEmitter)
module.exports = SearchPager
/**
* Start the paged search.
*/
SearchPager.prototype.begin = function begin () {
// Starting first page
this._nextPage(null)
}
SearchPager.prototype._onEnd = function _onEnd (res) {
const self = this
let cookie = null
res.controls.forEach(function (control) {
if (control.type === PagedResultsControl.OID) {
cookie = control.value.cookie
}
})
// Pass a noop callback by default for page events
const nullCb = function () { }
if (cookie === null) {
// paged search not supported
this.finished = true
this.emit('page', res, nullCb)
const err = new Error('missing paged control')
err.name = 'PagedError'
if (this.listeners('pageError').length > 0) {
this.emit('pageError', err)
// If the consumer as subscribed to pageError, SearchPager is absolved
// from delivering the fault via the 'error' event. Emitting an 'end'
// event after 'error' breaks the contract that the standard client
// provides, so it's only a possibility if 'pageError' is used instead.
this.emit('end', res)
} else {
this.emit('error', err)
// No end event possible per explanation above.
}
return
}
if (cookie.length === 0) {
// end of paged results
this.finished = true
this.emit('page', nullCb)
this.emit('end', res)
} else {
if (this.pagePause) {
// Wait to fetch next page until callback is invoked
// Halt page fetching if called with error
this.emit('page', res, function (err) {
if (!err) {
self._nextPage(cookie)
} else {
// the paged search has been canceled so emit an end
self.emit('end', res)
}
})
} else {
this.emit('page', res, nullCb)
this._nextPage(cookie)
}
}
}
SearchPager.prototype._onError = function _onError (err) {
this.finished = true
this.emit('error', err)
}
/**
* Initiate a search for the next page using the returned cookie value.
*/
SearchPager.prototype._nextPage = function _nextPage (cookie) {
const controls = this.controls.slice(0)
controls.push(new PagedResultsControl({
value: {
size: this.pageSize,
cookie
}
}))
this.sendRequest(controls, this.childEmitter, this._sendCallback.bind(this))
}
/**
* Callback provided to the client API for successful transmission.
*/
SearchPager.prototype._sendCallback = function _sendCallback (err) {
if (err) {
this.finished = true
if (!this.started) {
// EmitSend error during the first page, bail via callback
this.callback(err, null)
} else {
this.emit('error', err)
}
} else {
// search successfully send
if (!this.started) {
this.started = true
// send self as emitter as the client would
this.callback(null, this)
}
}
}