amqplib 2.2.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/lib/connect.js +8 -0
- package/lib/connection.js +25 -1
- package/lib/frame.js +5 -1
- package/lib/properties.d.ts +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Change log for amqplib
|
|
2
2
|
|
|
3
|
+
## v2.3.0
|
|
4
|
+
- Reject a `connection.tune` whose `frameMax` is below the protocol minimum of 4096 (the spec allows 0 or at least frame-min-size). Previously such a value was accepted, and the first publish whose encoded frames total 2048 bytes or more entered a loop in `sendContent` that never advanced, exhausting memory. `sendContent` now also throws if `frameMax` leaves no room for a body frame (GHSA-37f9-7m79-6866, reported by Rizwan Bedekar ([@r-bedekar](https://github.com/r-bedekar)); fix based in part on their patch and tests)
|
|
5
|
+
- Add a `maxFrameSize` connection option, also settable as a URL query parameter, bounding the payload size of frames accepted from the server. The default is 64 MiB, or the negotiated `frameMax` if that is larger, so raising `frameMax` needs no second option; `0` disables the limit. Incoming frame sizes were previously unchecked, so a frame header declaring up to 4 GiB made the client buffer, and repeatedly re-copy, that much data before looking at it. An oversized frame is now refused from its header alone and closes the connection with `Frame size N exceeds maxFrameSize M`. The limit is deliberately separate from the negotiated `frameMax`, since some brokers send larger frames without splitting them (see #784). **Upgrade note:** a broker that delivers an unsplit frame larger than the limit will now have that connection closed; pass `maxFrameSize: 0`, or a higher value, to restore the previous behaviour (GHSA-37f9-7m79-6866)
|
|
6
|
+
|
|
3
7
|
## v2.2.1
|
|
4
8
|
- Return the server acknowledgement from promise-based `channel.cancel`, matching the callback API (thanks [@edsonsims](https://github.com/edsonsims)).
|
|
5
9
|
- Type `cancel` replies as `Replies.Cancel` (`{ consumerTag }`) in both the promise and callback API declarations, which previously claimed `Replies.Empty`.
|
package/lib/connect.js
CHANGED
|
@@ -47,6 +47,11 @@ function openFrames(vhost, query, credentials, extraClientProperties) {
|
|
|
47
47
|
return val === undefined ? def : parseInt(val, 10);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
function maxFrameSizeOrUndefined(val) {
|
|
51
|
+
if (val == null) return undefined;
|
|
52
|
+
return /^\d+$/.test(String(val)) ? Number(val) : NaN;
|
|
53
|
+
}
|
|
54
|
+
|
|
50
55
|
const clientProperties = Object.create(CLIENT_PROPERTIES);
|
|
51
56
|
|
|
52
57
|
return {
|
|
@@ -61,6 +66,8 @@ function openFrames(vhost, query, credentials, extraClientProperties) {
|
|
|
61
66
|
frameMax: intOrDefault(query.frameMax, 131072),
|
|
62
67
|
heartbeat: query.heartbeat != null ? intOrDefault(query.heartbeat, 0) : null,
|
|
63
68
|
|
|
69
|
+
maxFrameSize: maxFrameSizeOrUndefined(query.maxFrameSize),
|
|
70
|
+
|
|
64
71
|
// open
|
|
65
72
|
virtualHost: vhost,
|
|
66
73
|
capabilities: '',
|
|
@@ -120,6 +127,7 @@ function connect(url, socketOptions, openCallback) {
|
|
|
120
127
|
channelMax: url.channelMax,
|
|
121
128
|
frameMax: url.frameMax,
|
|
122
129
|
heartbeat: url.heartbeat,
|
|
130
|
+
maxFrameSize: url.maxFrameSize,
|
|
123
131
|
};
|
|
124
132
|
|
|
125
133
|
fields = openFrames(url.vhost, config, sockopts.credentials || credentials.plain(user, pass), extraClientProperties);
|
package/lib/connection.js
CHANGED
|
@@ -27,6 +27,7 @@ const DEFAULT_WRITE_HWM = 1024;
|
|
|
27
27
|
// at once. Note that this is less than the minimum frame size: if it
|
|
28
28
|
// was greater, we might have to fragment the content.
|
|
29
29
|
const SINGLE_CHUNK_THRESHOLD = 2048;
|
|
30
|
+
const DEFAULT_MAX_FRAME_SIZE = 64 * 1024 * 1024;
|
|
30
31
|
|
|
31
32
|
class Connection extends EventEmitter {
|
|
32
33
|
constructor(underlying) {
|
|
@@ -38,6 +39,7 @@ class Connection extends EventEmitter {
|
|
|
38
39
|
// frames
|
|
39
40
|
this.rest = Buffer.alloc(0);
|
|
40
41
|
this.frameMax = constants.FRAME_MIN_SIZE;
|
|
42
|
+
this.maxFrameSize = DEFAULT_MAX_FRAME_SIZE;
|
|
41
43
|
this.sentSinceLastCheck = false;
|
|
42
44
|
this.recvSinceLastCheck = false;
|
|
43
45
|
|
|
@@ -86,6 +88,17 @@ class Connection extends EventEmitter {
|
|
|
86
88
|
const self = this;
|
|
87
89
|
const openCallback = openCallback0 || (() => {});
|
|
88
90
|
|
|
91
|
+
const maxFrameSize = allFields.maxFrameSize;
|
|
92
|
+
const maxFrameSizeDefaulted = maxFrameSize === undefined || maxFrameSize === null;
|
|
93
|
+
if (maxFrameSizeDefaulted) {
|
|
94
|
+
self.maxFrameSize = DEFAULT_MAX_FRAME_SIZE;
|
|
95
|
+
} else if (!Number.isInteger(maxFrameSize) || (maxFrameSize !== 0 && maxFrameSize < constants.FRAME_MIN_SIZE)) {
|
|
96
|
+
bail(new TypeError(fmt('maxFrameSize must be 0 (no limit) or an integer of at least %d; got %s', constants.FRAME_MIN_SIZE, maxFrameSize)));
|
|
97
|
+
return;
|
|
98
|
+
} else {
|
|
99
|
+
self.maxFrameSize = maxFrameSize;
|
|
100
|
+
}
|
|
101
|
+
|
|
89
102
|
// This is where we'll put our negotiated values
|
|
90
103
|
const tunedOptions = Object.create(allFields);
|
|
91
104
|
|
|
@@ -157,6 +170,10 @@ class Connection extends EventEmitter {
|
|
|
157
170
|
break;
|
|
158
171
|
case defs.ConnectionTune: {
|
|
159
172
|
const fields = reply.fields;
|
|
173
|
+
if (fields.frameMax !== 0 && fields.frameMax < constants.FRAME_MIN_SIZE) {
|
|
174
|
+
bail(new Error(fmt('Server frameMax %d is below the protocol minimum %d', fields.frameMax, constants.FRAME_MIN_SIZE)));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
160
177
|
tunedOptions.frameMax = negotiate(fields.frameMax, allFields.frameMax);
|
|
161
178
|
tunedOptions.channelMax = negotiate(fields.channelMax, allFields.channelMax);
|
|
162
179
|
if (allFields.heartbeat == null) {
|
|
@@ -191,6 +208,9 @@ class Connection extends EventEmitter {
|
|
|
191
208
|
// value is zero, meaning "no, no limits"
|
|
192
209
|
self.channelMax = tunedOptions.channelMax || 0xffff;
|
|
193
210
|
self.frameMax = tunedOptions.frameMax || 0xffffffff;
|
|
211
|
+
if (maxFrameSizeDefaulted && tunedOptions.frameMax !== 0 && tunedOptions.frameMax > self.maxFrameSize) {
|
|
212
|
+
self.maxFrameSize = tunedOptions.frameMax;
|
|
213
|
+
}
|
|
194
214
|
// 0 means "no heartbeat", rather than "maximum period of
|
|
195
215
|
// heartbeating"
|
|
196
216
|
self.heartbeat = tunedOptions.heartbeat;
|
|
@@ -526,6 +546,9 @@ class Connection extends EventEmitter {
|
|
|
526
546
|
const buffer = this.channels[channel].buffer;
|
|
527
547
|
|
|
528
548
|
const maxBody = this.frameMax - FRAME_OVERHEAD;
|
|
549
|
+
if (!(maxBody > 0)) {
|
|
550
|
+
throw new Error(fmt('Cannot send content: frameMax %d leaves no room for a body frame', this.frameMax));
|
|
551
|
+
}
|
|
529
552
|
|
|
530
553
|
for (let offset = 0; offset < body.length; offset += maxBody) {
|
|
531
554
|
const end = offset + maxBody;
|
|
@@ -539,7 +562,7 @@ class Connection extends EventEmitter {
|
|
|
539
562
|
|
|
540
563
|
recvFrame() {
|
|
541
564
|
// %%% identifying invariants might help here?
|
|
542
|
-
const frame = parseFrame(this.rest);
|
|
565
|
+
const frame = parseFrame(this.rest, this.maxFrameSize);
|
|
543
566
|
|
|
544
567
|
if (!frame) {
|
|
545
568
|
const incoming = this.stream.read();
|
|
@@ -654,3 +677,4 @@ function isFatalError(error) {
|
|
|
654
677
|
|
|
655
678
|
module.exports.Connection = Connection;
|
|
656
679
|
module.exports.isFatalError = isFatalError;
|
|
680
|
+
module.exports.DEFAULT_MAX_FRAME_SIZE = DEFAULT_MAX_FRAME_SIZE;
|
package/lib/frame.js
CHANGED
|
@@ -78,7 +78,7 @@ module.exports.makeBodyFrame = (channel, payload) => {
|
|
|
78
78
|
* @arg { number } max
|
|
79
79
|
* @returns { FrameStructure | boolean }
|
|
80
80
|
*/
|
|
81
|
-
function parseFrame(bin) {
|
|
81
|
+
function parseFrame(bin, max) {
|
|
82
82
|
if (bin.length < FRAME_HEADER_BYTES) {
|
|
83
83
|
return false;
|
|
84
84
|
}
|
|
@@ -87,6 +87,10 @@ function parseFrame(bin) {
|
|
|
87
87
|
const channel = bin.readUInt16BE(1);
|
|
88
88
|
const size = bin.readUInt32BE(3);
|
|
89
89
|
|
|
90
|
+
if (max > 0 && size > max) {
|
|
91
|
+
throw new Error(`Frame size ${size} exceeds maxFrameSize ${max}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
90
94
|
const totalSize = FRAME_HEADER_BYTES + size + FRAME_END_BYTES;
|
|
91
95
|
|
|
92
96
|
if (bin.length < totalSize) {
|
package/lib/properties.d.ts
CHANGED