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