Skip to content

Commit 0fbbf3d

Browse files
committed
Initial commit
Implements a baseline set of message types such that we cover all parsing paths in unit tests.
0 parents  commit 0fbbf3d

21 files changed

+1428
-0
lines changed

.eslintrc

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"parserOptions": {
3+
"ecmaVersion": "latest"
4+
},
5+
6+
"extends": [
7+
"standard"
8+
]
9+
}

.github/workflows/main.yml

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
name: "CI"
2+
on:
3+
pull_request:
4+
push:
5+
branches:
6+
- master
7+
8+
jobs:
9+
call-core-ci:
10+
uses: ldapjs/.github/.github/workflows/node-ci.yml@main

.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
*.swp
2+
node_modules
3+
coverage
4+
npm-debug.log
5+
.nyc_output
6+
.vscode

.taprc.yaml

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
reporter: terse
2+
coverage-map: coverage-map.js
3+
4+
lines: 90
5+
functions: 80
6+
branches: 85
7+
statements: 90
8+
9+
files:
10+
- 'lib/**/*.test.js'

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Copyright (c) 2014 Patrick Mooney. All rights reserved.
2+
Copyright (c) 2014 Mark Cavage, Inc. All rights reserved.
3+
Copyright (c) 2022 The LDAPJS Collaborators.
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# @ldapjs/messages
2+
3+
Provides methods and objects to represent LDAP messages.
4+
5+
## License
6+
7+
MIT.

coverage-map.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
'use strict'
2+
3+
module.exports = testFile => testFile.replace(/\.test\.js$/, '.js')

index.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
'use strict'
2+
3+
module.exports = {
4+
LdapMessage: require('./lib/ldap-message'),
5+
6+
AbandonRequest: require('./lib/messages/abandon-request'),
7+
BindRequest: require('./lib/messages/bind-request')
8+
}

lib/deprecations.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
'use strict'
2+
3+
const warning = require('process-warning')()
4+
const clazz = 'LdapjsMessageWarning'
5+
6+
warning.create(clazz, 'LDAP_MESSAGE_DEP_001', 'messageID is deprecated. Use messageId instead.')
7+
8+
warning.create(clazz, 'LDAP_MESSAGE_DEP_002', 'The .json property is deprecated. Use .pojo instead.')
9+
10+
warning.create(clazz, 'LDAP_MESSAGE_DEP_003', 'abandonID is deprecated. Use abandonId instead.')
11+
12+
module.exports = warning

lib/ldap-message.js

+182
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
'use strict'
2+
3+
const { BerReader, BerWriter } = require('@ldapjs/asn1')
4+
const warning = require('./deprecations')
5+
6+
/**
7+
* Implements a base LDAP message as defined in
8+
* https://www.rfc-editor.org/rfc/rfc4511.html#section-4.1.1.
9+
*/
10+
class LdapMessage {
11+
#messageId = 0
12+
#protocolOp
13+
#controls = []
14+
15+
/**
16+
* @param {object} [options]
17+
* @param {number} [options.messageId=1] An identifier for the message.
18+
* @param {number} [options.protocolOp] The tag for the message operation.
19+
* @param {object[]} [options.controls] A set of LDAP controls to send with
20+
* the message. See the `@ldapjs/controls` package.
21+
*/
22+
constructor (options = {}) {
23+
this.#messageId = parseInt(options.messageId || options.messageID || '1', 10)
24+
if (options.messageID) {
25+
warning.emit('LDAP_MESSAGE_DEP_001')
26+
}
27+
28+
if (options.protocolOp) {
29+
this.#protocolOp = options.protocolOp
30+
}
31+
32+
if (Array.isArray(options.controls)) {
33+
Array.prototype.push.apply(this.#controls, options.controls)
34+
}
35+
}
36+
37+
get [Symbol.toStringTag] () {
38+
return 'LdapMessage'
39+
}
40+
41+
/**
42+
* The message identifier.
43+
*
44+
* @type {number}
45+
*/
46+
get id () { return this.#messageId }
47+
48+
/**
49+
* Message type specific. Each message type must implement a `_dn` property
50+
* that provides this value.
51+
*
52+
* @type {string}
53+
*/
54+
get dn () {
55+
return this._dn
56+
}
57+
58+
/**
59+
* The name of the message class.
60+
*
61+
* @type {string}
62+
*/
63+
get type () { return 'LdapMessage' }
64+
65+
/**
66+
* Use {@link pojo} instead.
67+
*
68+
* @deprecated
69+
*/
70+
get json () {
71+
warning.emit('LDAP_MESSAGE_DEP_002')
72+
return this.pojo
73+
}
74+
75+
/**
76+
* A serialized representation of the message as a plain JavaScript object.
77+
* Specific message types must implement the `_pojo(obj)` method. The passed
78+
* in `obj` must be extended with the specific message's unique properties
79+
* and returned as the result.
80+
*
81+
* @returns {object}
82+
*/
83+
get pojo () {
84+
let result = {
85+
messageId: this.id,
86+
protocolOp: this.#protocolOp,
87+
type: this.type
88+
}
89+
90+
if (typeof this._pojo === 'function') {
91+
result = this._pojo(result)
92+
}
93+
94+
result.controls = this.#controls
95+
96+
return result
97+
}
98+
99+
addControl (control) {
100+
this.#controls.push(control)
101+
}
102+
103+
/**
104+
* Converts an {@link LdapMessage} object into a set of BER bytes that can
105+
* be sent across the wire. Specific message implementations must implement
106+
* the `_toBer(ber)` method. This method will write its unique sequence(s)
107+
* to the passed in `ber` object.
108+
*
109+
* @returns {import('@ldapjs/asn1').BerReader}
110+
*/
111+
toBer () {
112+
if (typeof this._toBer !== 'function') {
113+
throw Error(`${this.type} does not implement _toBer`)
114+
}
115+
116+
const writer = new BerWriter()
117+
writer.startSequence()
118+
writer.writeInt(this.id)
119+
120+
this._toBer(writer)
121+
122+
if (Array.isArray(this.#controls) === true && this.#controls.length > 0) {
123+
writer.startSequence(0xa0)
124+
this.#controls.forEach(function (c) {
125+
c.toBer(writer)
126+
})
127+
writer.endSequence()
128+
}
129+
130+
writer.endSequence()
131+
return new BerReader(writer.buffer)
132+
}
133+
134+
/**
135+
* Serializes the message into a JSON representation.
136+
*
137+
* @returns {string}
138+
*/
139+
toString () {
140+
return JSON.stringify(this.pojo)
141+
}
142+
143+
/**
144+
* Parses a BER into a message object. The offset of the BER _must_ point
145+
* to the start of an LDAP Message sequence. That is, the first few bytes
146+
* must indicate:
147+
*
148+
* 1. a sequence tag and how many bytes are in that sequence
149+
* 2. an integer representing the message identifier
150+
* 3. a protocol operation, e.g. BindRequest, and the number of bytes in
151+
* that operation
152+
*
153+
* @param {import('@ldapjs/asn1').BerReader} ber
154+
*
155+
* @returns {LdapMessage}
156+
*/
157+
static parse (ber) {
158+
// We must require here because `parseToMessage` imports subclasses
159+
// that need `LdapMessage` to be defined. If we try importing earlier,
160+
// then `LdapMessage` will not be available and we will get errors about
161+
// trying to subclass null objects.
162+
return require('./parse-to-message')(ber)
163+
}
164+
165+
/**
166+
* When invoked on specific message types, e.g. {@link BindRequest}, this
167+
* method will parse a BER into a plain JavaScript object that is usable as
168+
* an options object for constructing that specific message object.
169+
*
170+
* @param {import('@ldapjs/asn1').BerReader} ber A BER to parse. The reader
171+
* offset must point to the start of a valid sequence, i.e. the "tag" byte
172+
* in the TLV tuple, that represents the message to be parsed. For example,
173+
* in a {@link BindRequest} the starting sequence and message identifier must
174+
* already be read such that the offset is at the protocol operation sequence
175+
* byte.
176+
*/
177+
static parseToPojo (ber) {
178+
throw Error('Use LdapMessage.parse, or a specific message type\'s parseToPojo, instead.')
179+
}
180+
}
181+
182+
module.exports = LdapMessage

0 commit comments

Comments
 (0)