123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- 'use strict'
- const { BerReader, BerWriter } = require('@ldapjs/asn1')
- const warning = require('./deprecations')
- /**
- * Implements a base LDAP message as defined in
- * https://www.rfc-editor.org/rfc/rfc4511.html#section-4.1.1.
- */
- class LdapMessage {
- #messageId = 0
- #protocolOp
- #controls = []
- /**
- * @typedef {object} LdapMessageOptions
- * @property {number} [messageId=1] An identifier for the message.
- * @property {number} [protocolOp] The tag for the message operation.
- * @property {import('@ldapjs/controls').Control[]} [controls] A set of LDAP
- * controls to send with the message. See the `@ldapjs/controls` package.
- */
- /**
- * @param {LdapMessageOptions} [options]
- */
- constructor (options = {}) {
- this.#messageId = parseInt(options.messageId ?? options.messageID ?? '1', 10)
- if (options.messageID !== undefined) {
- warning.emit('LDAP_MESSAGE_DEP_001')
- }
- if (typeof options.protocolOp === 'number') {
- this.#protocolOp = options.protocolOp
- }
- this.controls = options.controls ?? []
- }
- get [Symbol.toStringTag] () {
- return 'LdapMessage'
- }
- /**
- * A copy of the list of controls that will be sent with the request.
- *
- * @returns {import('@ldapjs/controls').Control[]}
- */
- get controls () {
- return this.#controls.slice(0)
- }
- /**
- * Define the list of controls that will be sent with the request. Any
- * existing controls will be discarded.
- *
- * @param {import('@ldapjs/controls').Control[]} values
- *
- * @throws When a control value is invalid.
- */
- set controls (values) {
- if (Array.isArray(values) !== true) {
- throw Error('controls must be an array')
- }
- const newControls = []
- for (const val of values) {
- if (Object.prototype.toString.call(val) !== '[object LdapControl]') {
- throw Error('control must be an instance of LdapControl')
- }
- newControls.push(val)
- }
- this.#controls = newControls
- }
- /**
- * The message identifier.
- *
- * @type {number}
- */
- get id () {
- return this.#messageId
- }
- /**
- * Define the message identifier for the request.
- *
- * @param {number} value
- */
- set id (value) {
- if (Number.isInteger(value) === false) {
- throw Error('id must be an integer')
- }
- this.#messageId = value
- }
- /**
- * Alias for {@link id}.
- *
- * @returns {number}
- */
- get messageId () {
- return this.id
- }
- /**
- * Alias for {@link id}.
- *
- * @param {number} value
- */
- set messageId (value) {
- this.id = value
- }
- /**
- * Alias for {@link id}.
- *
- * @returns {number}
- *
- * @deprecated
- */
- get messageID () {
- warning.emit('LDAP_MESSAGE_DEP_001')
- return this.id
- }
- /**
- * Alias for {@link id}.
- *
- * @param {number} value
- *
- * @deprecated
- */
- set messageID (value) {
- warning.emit('LDAP_MESSAGE_DEP_001')
- this.id = value
- }
- /**
- * Message type specific. Each message type must implement a `_dn` property
- * that provides this value.
- *
- * @type {import('@ldapjs/dn').DN}
- */
- get dn () {
- return this._dn
- }
- /**
- * The LDAP protocol operation code for the message.
- *
- * @type {number}
- */
- get protocolOp () {
- return this.#protocolOp
- }
- /**
- * The name of the message class.
- *
- * @type {string}
- */
- get type () {
- return 'LdapMessage'
- }
- /**
- * Use {@link pojo} instead.
- *
- * @deprecated
- */
- get json () {
- warning.emit('LDAP_MESSAGE_DEP_002')
- return this.pojo
- }
- /**
- * A serialized representation of the message as a plain JavaScript object.
- * Specific message types must implement the `_pojo(obj)` method. The passed
- * in `obj` must be extended with the specific message's unique properties
- * and returned as the result.
- *
- * @returns {object}
- */
- get pojo () {
- let result = {
- messageId: this.id,
- protocolOp: this.#protocolOp,
- type: this.type
- }
- if (typeof this._pojo === 'function') {
- result = this._pojo(result)
- }
- result.controls = this.#controls.map(c => c.pojo)
- return result
- }
- addControl (control) {
- this.#controls.push(control)
- }
- /**
- * Converts an {@link LdapMessage} object into a set of BER bytes that can
- * be sent across the wire. Specific message implementations must implement
- * the `_toBer(ber)` method. This method will write its unique sequence(s)
- * to the passed in `ber` object.
- *
- * @returns {import('@ldapjs/asn1').BerReader}
- */
- toBer () {
- if (typeof this._toBer !== 'function') {
- throw Error(`${this.type} does not implement _toBer`)
- }
- const writer = new BerWriter()
- writer.startSequence()
- writer.writeInt(this.id)
- this._toBer(writer)
- if (this.#controls.length > 0) {
- writer.startSequence(0xa0)
- for (const control of this.#controls) {
- control.toBer(writer)
- }
- writer.endSequence()
- }
- writer.endSequence()
- return new BerReader(writer.buffer)
- }
- /**
- * Serializes the message into a JSON representation.
- *
- * @returns {string}
- */
- toString () {
- return JSON.stringify(this.pojo)
- }
- /**
- * Parses a BER into a message object. The offset of the BER _must_ point
- * to the start of an LDAP Message sequence. That is, the first few bytes
- * must indicate:
- *
- * 1. a sequence tag and how many bytes are in that sequence
- * 2. an integer representing the message identifier
- * 3. a protocol operation, e.g. BindRequest, and the number of bytes in
- * that operation
- *
- * @param {import('@ldapjs/asn1').BerReader} ber
- *
- * @returns {LdapMessage}
- */
- static parse (ber) {
- // We must require here because `parseToMessage` imports subclasses
- // that need `LdapMessage` to be defined. If we try importing earlier,
- // then `LdapMessage` will not be available, and we will get errors about
- // trying to subclass null objects.
- return require('./parse-to-message')(ber)
- }
- /**
- * When invoked on specific message types, e.g. {@link BindRequest}, this
- * method will parse a BER into a plain JavaScript object that is usable as
- * an options object for constructing that specific message object.
- *
- * @param {import('@ldapjs/asn1').BerReader} ber A BER to parse. The reader
- * offset must point to the start of a valid sequence, i.e. the "tag" byte
- * in the TLV tuple, that represents the message to be parsed. For example,
- * in a {@link BindRequest} the starting sequence and message identifier must
- * already be read such that the offset is at the protocol operation sequence
- * byte.
- */
- static parseToPojo (ber) {
- throw Error('Use LdapMessage.parse, or a specific message type\'s parseToPojo, instead.')
- }
- }
- module.exports = LdapMessage
|