1 | // imported from https://github.com/socketio/engine.io-parser/tree/2.2.x
|
---|
2 |
|
---|
3 | /**
|
---|
4 | * Module dependencies.
|
---|
5 | */
|
---|
6 |
|
---|
7 | var utf8 = require('./utf8');
|
---|
8 |
|
---|
9 | /**
|
---|
10 | * Current protocol version.
|
---|
11 | */
|
---|
12 | exports.protocol = 3;
|
---|
13 |
|
---|
14 | const hasBinary = (packets) => {
|
---|
15 | for (const packet of packets) {
|
---|
16 | if (packet.data instanceof ArrayBuffer || ArrayBuffer.isView(packet.data)) {
|
---|
17 | return true;
|
---|
18 | }
|
---|
19 | }
|
---|
20 | return false;
|
---|
21 | }
|
---|
22 |
|
---|
23 | /**
|
---|
24 | * Packet types.
|
---|
25 | */
|
---|
26 |
|
---|
27 | var packets = exports.packets = {
|
---|
28 | open: 0 // non-ws
|
---|
29 | , close: 1 // non-ws
|
---|
30 | , ping: 2
|
---|
31 | , pong: 3
|
---|
32 | , message: 4
|
---|
33 | , upgrade: 5
|
---|
34 | , noop: 6
|
---|
35 | };
|
---|
36 |
|
---|
37 | var packetslist = Object.keys(packets);
|
---|
38 |
|
---|
39 | /**
|
---|
40 | * Premade error packet.
|
---|
41 | */
|
---|
42 |
|
---|
43 | var err = { type: 'error', data: 'parser error' };
|
---|
44 |
|
---|
45 | const EMPTY_BUFFER = Buffer.concat([]);
|
---|
46 |
|
---|
47 | /**
|
---|
48 | * Encodes a packet.
|
---|
49 | *
|
---|
50 | * <packet type id> [ <data> ]
|
---|
51 | *
|
---|
52 | * Example:
|
---|
53 | *
|
---|
54 | * 5hello world
|
---|
55 | * 3
|
---|
56 | * 4
|
---|
57 | *
|
---|
58 | * Binary is encoded in an identical principle
|
---|
59 | *
|
---|
60 | * @api private
|
---|
61 | */
|
---|
62 |
|
---|
63 | exports.encodePacket = function (packet, supportsBinary, utf8encode, callback) {
|
---|
64 | if (typeof supportsBinary === 'function') {
|
---|
65 | callback = supportsBinary;
|
---|
66 | supportsBinary = null;
|
---|
67 | }
|
---|
68 |
|
---|
69 | if (typeof utf8encode === 'function') {
|
---|
70 | callback = utf8encode;
|
---|
71 | utf8encode = null;
|
---|
72 | }
|
---|
73 |
|
---|
74 | if (Buffer.isBuffer(packet.data)) {
|
---|
75 | return encodeBuffer(packet, supportsBinary, callback);
|
---|
76 | } else if (packet.data && (packet.data.buffer || packet.data) instanceof ArrayBuffer) {
|
---|
77 | return encodeBuffer({ type: packet.type, data: arrayBufferToBuffer(packet.data) }, supportsBinary, callback);
|
---|
78 | }
|
---|
79 |
|
---|
80 | // Sending data as a utf-8 string
|
---|
81 | var encoded = packets[packet.type];
|
---|
82 |
|
---|
83 | // data fragment is optional
|
---|
84 | if (undefined !== packet.data) {
|
---|
85 | encoded += utf8encode ? utf8.encode(String(packet.data), { strict: false }) : String(packet.data);
|
---|
86 | }
|
---|
87 |
|
---|
88 | return callback('' + encoded);
|
---|
89 | };
|
---|
90 |
|
---|
91 | /**
|
---|
92 | * Encode Buffer data
|
---|
93 | */
|
---|
94 |
|
---|
95 | function encodeBuffer(packet, supportsBinary, callback) {
|
---|
96 | if (!supportsBinary) {
|
---|
97 | return exports.encodeBase64Packet(packet, callback);
|
---|
98 | }
|
---|
99 |
|
---|
100 | var data = packet.data;
|
---|
101 | var typeBuffer = Buffer.allocUnsafe(1);
|
---|
102 | typeBuffer[0] = packets[packet.type];
|
---|
103 | return callback(Buffer.concat([typeBuffer, data]));
|
---|
104 | }
|
---|
105 |
|
---|
106 | /**
|
---|
107 | * Encodes a packet with binary data in a base64 string
|
---|
108 | *
|
---|
109 | * @param {Object} packet, has `type` and `data`
|
---|
110 | * @return {String} base64 encoded message
|
---|
111 | */
|
---|
112 |
|
---|
113 | exports.encodeBase64Packet = function(packet, callback){
|
---|
114 | var data = Buffer.isBuffer(packet.data) ? packet.data : arrayBufferToBuffer(packet.data);
|
---|
115 | var message = 'b' + packets[packet.type];
|
---|
116 | message += data.toString('base64');
|
---|
117 | return callback(message);
|
---|
118 | };
|
---|
119 |
|
---|
120 | /**
|
---|
121 | * Decodes a packet. Data also available as an ArrayBuffer if requested.
|
---|
122 | *
|
---|
123 | * @return {Object} with `type` and `data` (if any)
|
---|
124 | * @api private
|
---|
125 | */
|
---|
126 |
|
---|
127 | exports.decodePacket = function (data, binaryType, utf8decode) {
|
---|
128 | if (data === undefined) {
|
---|
129 | return err;
|
---|
130 | }
|
---|
131 |
|
---|
132 | var type;
|
---|
133 |
|
---|
134 | // String data
|
---|
135 | if (typeof data === 'string') {
|
---|
136 |
|
---|
137 | type = data.charAt(0);
|
---|
138 |
|
---|
139 | if (type === 'b') {
|
---|
140 | return exports.decodeBase64Packet(data.substr(1), binaryType);
|
---|
141 | }
|
---|
142 |
|
---|
143 | if (utf8decode) {
|
---|
144 | data = tryDecode(data);
|
---|
145 | if (data === false) {
|
---|
146 | return err;
|
---|
147 | }
|
---|
148 | }
|
---|
149 |
|
---|
150 | if (Number(type) != type || !packetslist[type]) {
|
---|
151 | return err;
|
---|
152 | }
|
---|
153 |
|
---|
154 | if (data.length > 1) {
|
---|
155 | return { type: packetslist[type], data: data.substring(1) };
|
---|
156 | } else {
|
---|
157 | return { type: packetslist[type] };
|
---|
158 | }
|
---|
159 | }
|
---|
160 |
|
---|
161 | // Binary data
|
---|
162 | if (binaryType === 'arraybuffer') {
|
---|
163 | // wrap Buffer/ArrayBuffer data into an Uint8Array
|
---|
164 | var intArray = new Uint8Array(data);
|
---|
165 | type = intArray[0];
|
---|
166 | return { type: packetslist[type], data: intArray.buffer.slice(1) };
|
---|
167 | }
|
---|
168 |
|
---|
169 | if (data instanceof ArrayBuffer) {
|
---|
170 | data = arrayBufferToBuffer(data);
|
---|
171 | }
|
---|
172 | type = data[0];
|
---|
173 | return { type: packetslist[type], data: data.slice(1) };
|
---|
174 | };
|
---|
175 |
|
---|
176 | function tryDecode(data) {
|
---|
177 | try {
|
---|
178 | data = utf8.decode(data, { strict: false });
|
---|
179 | } catch (e) {
|
---|
180 | return false;
|
---|
181 | }
|
---|
182 | return data;
|
---|
183 | }
|
---|
184 |
|
---|
185 | /**
|
---|
186 | * Decodes a packet encoded in a base64 string.
|
---|
187 | *
|
---|
188 | * @param {String} base64 encoded message
|
---|
189 | * @return {Object} with `type` and `data` (if any)
|
---|
190 | */
|
---|
191 |
|
---|
192 | exports.decodeBase64Packet = function(msg, binaryType) {
|
---|
193 | var type = packetslist[msg.charAt(0)];
|
---|
194 | var data = Buffer.from(msg.substr(1), 'base64');
|
---|
195 | if (binaryType === 'arraybuffer') {
|
---|
196 | var abv = new Uint8Array(data.length);
|
---|
197 | for (var i = 0; i < abv.length; i++){
|
---|
198 | abv[i] = data[i];
|
---|
199 | }
|
---|
200 | data = abv.buffer;
|
---|
201 | }
|
---|
202 | return { type: type, data: data };
|
---|
203 | };
|
---|
204 |
|
---|
205 | /**
|
---|
206 | * Encodes multiple messages (payload).
|
---|
207 | *
|
---|
208 | * <length>:data
|
---|
209 | *
|
---|
210 | * Example:
|
---|
211 | *
|
---|
212 | * 11:hello world2:hi
|
---|
213 | *
|
---|
214 | * If any contents are binary, they will be encoded as base64 strings. Base64
|
---|
215 | * encoded strings are marked with a b before the length specifier
|
---|
216 | *
|
---|
217 | * @param {Array} packets
|
---|
218 | * @api private
|
---|
219 | */
|
---|
220 |
|
---|
221 | exports.encodePayload = function (packets, supportsBinary, callback) {
|
---|
222 | if (typeof supportsBinary === 'function') {
|
---|
223 | callback = supportsBinary;
|
---|
224 | supportsBinary = null;
|
---|
225 | }
|
---|
226 |
|
---|
227 | if (supportsBinary && hasBinary(packets)) {
|
---|
228 | return exports.encodePayloadAsBinary(packets, callback);
|
---|
229 | }
|
---|
230 |
|
---|
231 | if (!packets.length) {
|
---|
232 | return callback('0:');
|
---|
233 | }
|
---|
234 |
|
---|
235 | function encodeOne(packet, doneCallback) {
|
---|
236 | exports.encodePacket(packet, supportsBinary, false, function(message) {
|
---|
237 | doneCallback(null, setLengthHeader(message));
|
---|
238 | });
|
---|
239 | }
|
---|
240 |
|
---|
241 | map(packets, encodeOne, function(err, results) {
|
---|
242 | return callback(results.join(''));
|
---|
243 | });
|
---|
244 | };
|
---|
245 |
|
---|
246 | function setLengthHeader(message) {
|
---|
247 | return message.length + ':' + message;
|
---|
248 | }
|
---|
249 |
|
---|
250 | /**
|
---|
251 | * Async array map using after
|
---|
252 | */
|
---|
253 |
|
---|
254 | function map(ary, each, done) {
|
---|
255 | const results = new Array(ary.length);
|
---|
256 | let count = 0;
|
---|
257 |
|
---|
258 | for (let i = 0; i < ary.length; i++) {
|
---|
259 | each(ary[i], (error, msg) => {
|
---|
260 | results[i] = msg;
|
---|
261 | if (++count === ary.length) {
|
---|
262 | done(null, results);
|
---|
263 | }
|
---|
264 | });
|
---|
265 | }
|
---|
266 | }
|
---|
267 |
|
---|
268 | /*
|
---|
269 | * Decodes data when a payload is maybe expected. Possible binary contents are
|
---|
270 | * decoded from their base64 representation
|
---|
271 | *
|
---|
272 | * @param {String} data, callback method
|
---|
273 | * @api public
|
---|
274 | */
|
---|
275 |
|
---|
276 | exports.decodePayload = function (data, binaryType, callback) {
|
---|
277 | if (typeof data !== 'string') {
|
---|
278 | return exports.decodePayloadAsBinary(data, binaryType, callback);
|
---|
279 | }
|
---|
280 |
|
---|
281 | if (typeof binaryType === 'function') {
|
---|
282 | callback = binaryType;
|
---|
283 | binaryType = null;
|
---|
284 | }
|
---|
285 |
|
---|
286 | if (data === '') {
|
---|
287 | // parser error - ignoring payload
|
---|
288 | return callback(err, 0, 1);
|
---|
289 | }
|
---|
290 |
|
---|
291 | var length = '', n, msg, packet;
|
---|
292 |
|
---|
293 | for (var i = 0, l = data.length; i < l; i++) {
|
---|
294 | var chr = data.charAt(i);
|
---|
295 |
|
---|
296 | if (chr !== ':') {
|
---|
297 | length += chr;
|
---|
298 | continue;
|
---|
299 | }
|
---|
300 |
|
---|
301 | if (length === '' || (length != (n = Number(length)))) {
|
---|
302 | // parser error - ignoring payload
|
---|
303 | return callback(err, 0, 1);
|
---|
304 | }
|
---|
305 |
|
---|
306 | msg = data.substr(i + 1, n);
|
---|
307 |
|
---|
308 | if (length != msg.length) {
|
---|
309 | // parser error - ignoring payload
|
---|
310 | return callback(err, 0, 1);
|
---|
311 | }
|
---|
312 |
|
---|
313 | if (msg.length) {
|
---|
314 | packet = exports.decodePacket(msg, binaryType, false);
|
---|
315 |
|
---|
316 | if (err.type === packet.type && err.data === packet.data) {
|
---|
317 | // parser error in individual packet - ignoring payload
|
---|
318 | return callback(err, 0, 1);
|
---|
319 | }
|
---|
320 |
|
---|
321 | var more = callback(packet, i + n, l);
|
---|
322 | if (false === more) return;
|
---|
323 | }
|
---|
324 |
|
---|
325 | // advance cursor
|
---|
326 | i += n;
|
---|
327 | length = '';
|
---|
328 | }
|
---|
329 |
|
---|
330 | if (length !== '') {
|
---|
331 | // parser error - ignoring payload
|
---|
332 | return callback(err, 0, 1);
|
---|
333 | }
|
---|
334 |
|
---|
335 | };
|
---|
336 |
|
---|
337 | /**
|
---|
338 | *
|
---|
339 | * Converts a buffer to a utf8.js encoded string
|
---|
340 | *
|
---|
341 | * @api private
|
---|
342 | */
|
---|
343 |
|
---|
344 | function bufferToString(buffer) {
|
---|
345 | var str = '';
|
---|
346 | for (var i = 0, l = buffer.length; i < l; i++) {
|
---|
347 | str += String.fromCharCode(buffer[i]);
|
---|
348 | }
|
---|
349 | return str;
|
---|
350 | }
|
---|
351 |
|
---|
352 | /**
|
---|
353 | *
|
---|
354 | * Converts a utf8.js encoded string to a buffer
|
---|
355 | *
|
---|
356 | * @api private
|
---|
357 | */
|
---|
358 |
|
---|
359 | function stringToBuffer(string) {
|
---|
360 | var buf = Buffer.allocUnsafe(string.length);
|
---|
361 | for (var i = 0, l = string.length; i < l; i++) {
|
---|
362 | buf.writeUInt8(string.charCodeAt(i), i);
|
---|
363 | }
|
---|
364 | return buf;
|
---|
365 | }
|
---|
366 |
|
---|
367 | /**
|
---|
368 | *
|
---|
369 | * Converts an ArrayBuffer to a Buffer
|
---|
370 | *
|
---|
371 | * @api private
|
---|
372 | */
|
---|
373 |
|
---|
374 | function arrayBufferToBuffer(data) {
|
---|
375 | // data is either an ArrayBuffer or ArrayBufferView.
|
---|
376 | var length = data.byteLength || data.length;
|
---|
377 | var offset = data.byteOffset || 0;
|
---|
378 |
|
---|
379 | return Buffer.from(data.buffer || data, offset, length);
|
---|
380 | }
|
---|
381 |
|
---|
382 | /**
|
---|
383 | * Encodes multiple messages (payload) as binary.
|
---|
384 | *
|
---|
385 | * <1 = binary, 0 = string><number from 0-9><number from 0-9>[...]<number
|
---|
386 | * 255><data>
|
---|
387 | *
|
---|
388 | * Example:
|
---|
389 | * 1 3 255 1 2 3, if the binary contents are interpreted as 8 bit integers
|
---|
390 | *
|
---|
391 | * @param {Array} packets
|
---|
392 | * @return {Buffer} encoded payload
|
---|
393 | * @api private
|
---|
394 | */
|
---|
395 |
|
---|
396 | exports.encodePayloadAsBinary = function (packets, callback) {
|
---|
397 | if (!packets.length) {
|
---|
398 | return callback(EMPTY_BUFFER);
|
---|
399 | }
|
---|
400 |
|
---|
401 | map(packets, encodeOneBinaryPacket, function(err, results) {
|
---|
402 | return callback(Buffer.concat(results));
|
---|
403 | });
|
---|
404 | };
|
---|
405 |
|
---|
406 | function encodeOneBinaryPacket(p, doneCallback) {
|
---|
407 |
|
---|
408 | function onBinaryPacketEncode(packet) {
|
---|
409 |
|
---|
410 | var encodingLength = '' + packet.length;
|
---|
411 | var sizeBuffer;
|
---|
412 |
|
---|
413 | if (typeof packet === 'string') {
|
---|
414 | sizeBuffer = Buffer.allocUnsafe(encodingLength.length + 2);
|
---|
415 | sizeBuffer[0] = 0; // is a string (not true binary = 0)
|
---|
416 | for (var i = 0; i < encodingLength.length; i++) {
|
---|
417 | sizeBuffer[i + 1] = parseInt(encodingLength[i], 10);
|
---|
418 | }
|
---|
419 | sizeBuffer[sizeBuffer.length - 1] = 255;
|
---|
420 | return doneCallback(null, Buffer.concat([sizeBuffer, stringToBuffer(packet)]));
|
---|
421 | }
|
---|
422 |
|
---|
423 | sizeBuffer = Buffer.allocUnsafe(encodingLength.length + 2);
|
---|
424 | sizeBuffer[0] = 1; // is binary (true binary = 1)
|
---|
425 | for (var i = 0; i < encodingLength.length; i++) {
|
---|
426 | sizeBuffer[i + 1] = parseInt(encodingLength[i], 10);
|
---|
427 | }
|
---|
428 | sizeBuffer[sizeBuffer.length - 1] = 255;
|
---|
429 |
|
---|
430 | doneCallback(null, Buffer.concat([sizeBuffer, packet]));
|
---|
431 | }
|
---|
432 |
|
---|
433 | exports.encodePacket(p, true, true, onBinaryPacketEncode);
|
---|
434 |
|
---|
435 | }
|
---|
436 |
|
---|
437 |
|
---|
438 | /*
|
---|
439 | * Decodes data when a payload is maybe expected. Strings are decoded by
|
---|
440 | * interpreting each byte as a key code for entries marked to start with 0. See
|
---|
441 | * description of encodePayloadAsBinary
|
---|
442 |
|
---|
443 | * @param {Buffer} data, callback method
|
---|
444 | * @api public
|
---|
445 | */
|
---|
446 |
|
---|
447 | exports.decodePayloadAsBinary = function (data, binaryType, callback) {
|
---|
448 | if (typeof binaryType === 'function') {
|
---|
449 | callback = binaryType;
|
---|
450 | binaryType = null;
|
---|
451 | }
|
---|
452 |
|
---|
453 | var bufferTail = data;
|
---|
454 | var buffers = [];
|
---|
455 | var i;
|
---|
456 |
|
---|
457 | while (bufferTail.length > 0) {
|
---|
458 | var strLen = '';
|
---|
459 | var isString = bufferTail[0] === 0;
|
---|
460 | for (i = 1; ; i++) {
|
---|
461 | if (bufferTail[i] === 255) break;
|
---|
462 | // 310 = char length of Number.MAX_VALUE
|
---|
463 | if (strLen.length > 310) {
|
---|
464 | return callback(err, 0, 1);
|
---|
465 | }
|
---|
466 | strLen += '' + bufferTail[i];
|
---|
467 | }
|
---|
468 | bufferTail = bufferTail.slice(strLen.length + 1);
|
---|
469 |
|
---|
470 | var msgLength = parseInt(strLen, 10);
|
---|
471 |
|
---|
472 | var msg = bufferTail.slice(1, msgLength + 1);
|
---|
473 | if (isString) msg = bufferToString(msg);
|
---|
474 | buffers.push(msg);
|
---|
475 | bufferTail = bufferTail.slice(msgLength + 1);
|
---|
476 | }
|
---|
477 |
|
---|
478 | var total = buffers.length;
|
---|
479 | for (i = 0; i < total; i++) {
|
---|
480 | var buffer = buffers[i];
|
---|
481 | callback(exports.decodePacket(buffer, binaryType, true), i, total);
|
---|
482 | }
|
---|
483 | };
|
---|