Skip to content
This repository was archived by the owner on May 14, 2024. It is now read-only.

Commit e2d516f

Browse files
committed
Address crash for unmatched server responses
1 parent 70ce9c3 commit e2d516f

File tree

2 files changed

+110
-1
lines changed

2 files changed

+110
-1
lines changed

lib/client/client.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -881,7 +881,13 @@ Client.prototype.connect = function connect () {
881881
// object.
882882
tracker.parser.on('message', function onMessage (message) {
883883
message.connection = self._socket
884-
const { message: trackedMessage, callback } = tracker.fetch(message.messageId)
884+
const trackedObject = tracker.fetch(message.messageId)
885+
if (!trackedObject) {
886+
log.error({ message: message.pojo }, 'unmatched server message received')
887+
return false
888+
}
889+
890+
const { message: trackedMessage, callback } = trackedObject
885891

886892
if (!callback) {
887893
log.error({ message: message.pojo }, 'unsolicited message')

test/issue-890.test.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
'use strict'
2+
3+
// This test is complicated. It must simulate a server sending an unsolicited,
4+
// or a mismatched, message in order to force the client's internal message
5+
// tracker to try and find a corresponding sent message that does not exist.
6+
// In order to do that, we need to set a high test timeout and wait for the
7+
// error message to be logged.
8+
9+
const tap = require('tap')
10+
const ldapjs = require('../')
11+
const { SearchResultEntry } = require('@ldapjs/messages')
12+
const server = ldapjs.createServer()
13+
const SUFFIX = ''
14+
15+
tap.timeout = 10000
16+
17+
server.bind(SUFFIX, (res, done) => {
18+
res.end()
19+
return done()
20+
})
21+
22+
server.search(SUFFIX, (req, res, done) => {
23+
const result = new SearchResultEntry({
24+
objectName: `dc=${req.scopeName}`
25+
})
26+
27+
// Respond to the search request with a matched response.
28+
res.send(result)
29+
res.end()
30+
31+
// After a short delay, send ANOTHER response to the client that will not
32+
// be matched by the client's internal tracker.
33+
setTimeout(
34+
() => {
35+
res.send(result)
36+
res.end()
37+
done()
38+
},
39+
100
40+
)
41+
})
42+
43+
tap.beforeEach(t => {
44+
return new Promise((resolve, reject) => {
45+
server.listen(0, '127.0.0.1', (err) => {
46+
if (err) return reject(err)
47+
48+
t.context.logMessages = []
49+
t.context.logger = {
50+
child () { return this },
51+
debug () {},
52+
error (...args) {
53+
t.context.logMessages.push(args)
54+
},
55+
trace () {}
56+
}
57+
58+
t.context.url = server.url
59+
t.context.client = ldapjs.createClient({
60+
url: [server.url],
61+
timeout: 5,
62+
log: t.context.logger
63+
})
64+
65+
resolve()
66+
})
67+
})
68+
})
69+
70+
tap.afterEach(t => {
71+
return new Promise((resolve, reject) => {
72+
t.context.client.destroy()
73+
server.close((err) => {
74+
if (err) return reject(err)
75+
resolve()
76+
})
77+
})
78+
})
79+
80+
tap.test('handle null messages', t => {
81+
const { client, logMessages } = t.context
82+
83+
// There's no way to get an error from the client when it has received an
84+
// unmatched response from the server. So we need to poll our logger instance
85+
// and detect when the corresponding error message has been logged.
86+
const timer = setInterval(
87+
() => {
88+
if (logMessages.length > 0) {
89+
t.equal(
90+
logMessages.some(msg => msg[1] === 'unmatched server message received'),
91+
true
92+
)
93+
clearInterval(timer)
94+
t.end()
95+
}
96+
},
97+
100
98+
)
99+
100+
client.search('dc=test', (error) => {
101+
t.error(error)
102+
})
103+
})

0 commit comments

Comments
 (0)