socketioMEANseed/server/node_modules/engine.io-parser/lib/browser.js
server/node_modules/engine.io-parser/lib/browser.jsRaw
1/**
2 * Module dependencies.
3 */
4
5var keys = require('./keys');
6var hasBinary = require('has-binary');
7var sliceBuffer = require('arraybuffer.slice');
8var after = require('after');
9var utf8 = require('wtf-8');
10
11var base64encoder;
12if (global && global.ArrayBuffer) {
13 base64encoder = require('base64-arraybuffer');
14}
15
16/**
17 * Check if we are running an android browser. That requires us to use
18 * ArrayBuffer with polling transports...
19 *
20 * http://ghinda.net/jpeg-blob-ajax-android/
21 */
22
23var isAndroid = typeof navigator !== 'undefined' && /Android/i.test(navigator.userAgent);
24
25/**
26 * Check if we are running in PhantomJS.
27 * Uploading a Blob with PhantomJS does not work correctly, as reported here:
28 * https://github.com/ariya/phantomjs/issues/11395
29 * @type boolean
30 */
31var isPhantomJS = typeof navigator !== 'undefined' && /PhantomJS/i.test(navigator.userAgent);
32
33/**
34 * When true, avoids using Blobs to encode payloads.
35 * @type boolean
36 */
37var dontSendBlobs = isAndroid || isPhantomJS;
38
39/**
40 * Current protocol version.
41 */
42
43exports.protocol = 3;
44
45/**
46 * Packet types.
47 */
48
49var packets = exports.packets = {
50 open: 0 // non-ws
51 , close: 1 // non-ws
52 , ping: 2
53 , pong: 3
54 , message: 4
55 , upgrade: 5
56 , noop: 6
57};
58
59var packetslist = keys(packets);
60
61/**
62 * Premade error packet.
63 */
64
65var err = { type: 'error', data: 'parser error' };
66
67/**
68 * Create a blob api even for blob builder when vendor prefixes exist
69 */
70
71var Blob = require('blob');
72
73/**
74 * Encodes a packet.
75 *
76 * <packet type id> [ <data> ]
77 *
78 * Example:
79 *
80 * 5hello world
81 * 3
82 * 4
83 *
84 * Binary is encoded in an identical principle
85 *
86 * @api private
87 */
88
89exports.encodePacket = function (packet, supportsBinary, utf8encode, callback) {
90 if ('function' == typeof supportsBinary) {
91 callback = supportsBinary;
92 supportsBinary = false;
93 }
94
95 if ('function' == typeof utf8encode) {
96 callback = utf8encode;
97 utf8encode = null;
98 }
99
100 var data = (packet.data === undefined)
101 ? undefined
102 : packet.data.buffer || packet.data;
103
104 if (global.ArrayBuffer && data instanceof ArrayBuffer) {
105 return encodeArrayBuffer(packet, supportsBinary, callback);
106 } else if (Blob && data instanceof global.Blob) {
107 return encodeBlob(packet, supportsBinary, callback);
108 }
109
110 // might be an object with { base64: true, data: dataAsBase64String }
111 if (data && data.base64) {
112 return encodeBase64Object(packet, callback);
113 }
114
115 // Sending data as a utf-8 string
116 var encoded = packets[packet.type];
117
118 // data fragment is optional
119 if (undefined !== packet.data) {
120 encoded += utf8encode ? utf8.encode(String(packet.data)) : String(packet.data);
121 }
122
123 return callback('' + encoded);
124
125};
126
127function encodeBase64Object(packet, callback) {
128 // packet data is an object { base64: true, data: dataAsBase64String }
129 var message = 'b' + exports.packets[packet.type] + packet.data.data;
130 return callback(message);
131}
132
133/**
134 * Encode packet helpers for binary types
135 */
136
137function encodeArrayBuffer(packet, supportsBinary, callback) {
138 if (!supportsBinary) {
139 return exports.encodeBase64Packet(packet, callback);
140 }
141
142 var data = packet.data;
143 var contentArray = new Uint8Array(data);
144 var resultBuffer = new Uint8Array(1 + data.byteLength);
145
146 resultBuffer[0] = packets[packet.type];
147 for (var i = 0; i < contentArray.length; i++) {
148 resultBuffer[i+1] = contentArray[i];
149 }
150
151 return callback(resultBuffer.buffer);
152}
153
154function encodeBlobAsArrayBuffer(packet, supportsBinary, callback) {
155 if (!supportsBinary) {
156 return exports.encodeBase64Packet(packet, callback);
157 }
158
159 var fr = new FileReader();
160 fr.onload = function() {
161 packet.data = fr.result;
162 exports.encodePacket(packet, supportsBinary, true, callback);
163 };
164 return fr.readAsArrayBuffer(packet.data);
165}
166
167function encodeBlob(packet, supportsBinary, callback) {
168 if (!supportsBinary) {
169 return exports.encodeBase64Packet(packet, callback);
170 }
171
172 if (dontSendBlobs) {
173 return encodeBlobAsArrayBuffer(packet, supportsBinary, callback);
174 }
175
176 var length = new Uint8Array(1);
177 length[0] = packets[packet.type];
178 var blob = new Blob([length.buffer, packet.data]);
179
180 return callback(blob);
181}
182
183/**
184 * Encodes a packet with binary data in a base64 string
185 *
186 * @param {Object} packet, has `type` and `data`
187 * @return {String} base64 encoded message
188 */
189
190exports.encodeBase64Packet = function(packet, callback) {
191 var message = 'b' + exports.packets[packet.type];
192 if (Blob && packet.data instanceof global.Blob) {
193 var fr = new FileReader();
194 fr.onload = function() {
195 var b64 = fr.result.split(',')[1];
196 callback(message + b64);
197 };
198 return fr.readAsDataURL(packet.data);
199 }
200
201 var b64data;
202 try {
203 b64data = String.fromCharCode.apply(null, new Uint8Array(packet.data));
204 } catch (e) {
205 // iPhone Safari doesn't let you apply with typed arrays
206 var typed = new Uint8Array(packet.data);
207 var basic = new Array(typed.length);
208 for (var i = 0; i < typed.length; i++) {
209 basic[i] = typed[i];
210 }
211 b64data = String.fromCharCode.apply(null, basic);
212 }
213 message += global.btoa(b64data);
214 return callback(message);
215};
216
217/**
218 * Decodes a packet. Changes format to Blob if requested.
219 *
220 * @return {Object} with `type` and `data` (if any)
221 * @api private
222 */
223
224exports.decodePacket = function (data, binaryType, utf8decode) {
225 if (data === undefined) {
226 return err;
227 }
228 // String data
229 if (typeof data == 'string') {
230 if (data.charAt(0) == 'b') {
231 return exports.decodeBase64Packet(data.substr(1), binaryType);
232 }
233
234 if (utf8decode) {
235 data = tryDecode(data);
236 if (data === false) {
237 return err;
238 }
239 }
240 var type = data.charAt(0);
241
242 if (Number(type) != type || !packetslist[type]) {
243 return err;
244 }
245
246 if (data.length > 1) {
247 return { type: packetslist[type], data: data.substring(1) };
248 } else {
249 return { type: packetslist[type] };
250 }
251 }
252
253 var asArray = new Uint8Array(data);
254 var type = asArray[0];
255 var rest = sliceBuffer(data, 1);
256 if (Blob && binaryType === 'blob') {
257 rest = new Blob([rest]);
258 }
259 return { type: packetslist[type], data: rest };
260};
261
262function tryDecode(data) {
263 try {
264 data = utf8.decode(data);
265 } catch (e) {
266 return false;
267 }
268 return data;
269}
270
271/**
272 * Decodes a packet encoded in a base64 string
273 *
274 * @param {String} base64 encoded message
275 * @return {Object} with `type` and `data` (if any)
276 */
277
278exports.decodeBase64Packet = function(msg, binaryType) {
279 var type = packetslist[msg.charAt(0)];
280 if (!base64encoder) {
281 return { type: type, data: { base64: true, data: msg.substr(1) } };
282 }
283
284 var data = base64encoder.decode(msg.substr(1));
285
286 if (binaryType === 'blob' && Blob) {
287 data = new Blob([data]);
288 }
289
290 return { type: type, data: data };
291};
292
293/**
294 * Encodes multiple messages (payload).
295 *
296 * <length>:data
297 *
298 * Example:
299 *
300 * 11:hello world2:hi
301 *
302 * If any contents are binary, they will be encoded as base64 strings. Base64
303 * encoded strings are marked with a b before the length specifier
304 *
305 * @param {Array} packets
306 * @api private
307 */
308
309exports.encodePayload = function (packets, supportsBinary, callback) {
310 if (typeof supportsBinary == 'function') {
311 callback = supportsBinary;
312 supportsBinary = null;
313 }
314
315 var isBinary = hasBinary(packets);
316
317 if (supportsBinary && isBinary) {
318 if (Blob && !dontSendBlobs) {
319 return exports.encodePayloadAsBlob(packets, callback);
320 }
321
322 return exports.encodePayloadAsArrayBuffer(packets, callback);
323 }
324
325 if (!packets.length) {
326 return callback('0:');
327 }
328
329 function setLengthHeader(message) {
330 return message.length + ':' + message;
331 }
332
333 function encodeOne(packet, doneCallback) {
334 exports.encodePacket(packet, !isBinary ? false : supportsBinary, true, function(message) {
335 doneCallback(null, setLengthHeader(message));
336 });
337 }
338
339 map(packets, encodeOne, function(err, results) {
340 return callback(results.join(''));
341 });
342};
343
344/**
345 * Async array map using after
346 */
347
348function map(ary, each, done) {
349 var result = new Array(ary.length);
350 var next = after(ary.length, done);
351
352 var eachWithIndex = function(i, el, cb) {
353 each(el, function(error, msg) {
354 result[i] = msg;
355 cb(error, result);
356 });
357 };
358
359 for (var i = 0; i < ary.length; i++) {
360 eachWithIndex(i, ary[i], next);
361 }
362}
363
364/*
365 * Decodes data when a payload is maybe expected. Possible binary contents are
366 * decoded from their base64 representation
367 *
368 * @param {String} data, callback method
369 * @api public
370 */
371
372exports.decodePayload = function (data, binaryType, callback) {
373 if (typeof data != 'string') {
374 return exports.decodePayloadAsBinary(data, binaryType, callback);
375 }
376
377 if (typeof binaryType === 'function') {
378 callback = binaryType;
379 binaryType = null;
380 }
381
382 var packet;
383 if (data == '') {
384 // parser error - ignoring payload
385 return callback(err, 0, 1);
386 }
387
388 var length = ''
389 , n, msg;
390
391 for (var i = 0, l = data.length; i < l; i++) {
392 var chr = data.charAt(i);
393
394 if (':' != chr) {
395 length += chr;
396 } else {
397 if ('' == length || (length != (n = Number(length)))) {
398 // parser error - ignoring payload
399 return callback(err, 0, 1);
400 }
401
402 msg = data.substr(i + 1, n);
403
404 if (length != msg.length) {
405 // parser error - ignoring payload
406 return callback(err, 0, 1);
407 }
408
409 if (msg.length) {
410 packet = exports.decodePacket(msg, binaryType, true);
411
412 if (err.type == packet.type && err.data == packet.data) {
413 // parser error in individual packet - ignoring payload
414 return callback(err, 0, 1);
415 }
416
417 var ret = callback(packet, i + n, l);
418 if (false === ret) return;
419 }
420
421 // advance cursor
422 i += n;
423 length = '';
424 }
425 }
426
427 if (length != '') {
428 // parser error - ignoring payload
429 return callback(err, 0, 1);
430 }
431
432};
433
434/**
435 * Encodes multiple messages (payload) as binary.
436 *
437 * <1 = binary, 0 = string><number from 0-9><number from 0-9>[...]<number
438 * 255><data>
439 *
440 * Example:
441 * 1 3 255 1 2 3, if the binary contents are interpreted as 8 bit integers
442 *
443 * @param {Array} packets
444 * @return {ArrayBuffer} encoded payload
445 * @api private
446 */
447
448exports.encodePayloadAsArrayBuffer = function(packets, callback) {
449 if (!packets.length) {
450 return callback(new ArrayBuffer(0));
451 }
452
453 function encodeOne(packet, doneCallback) {
454 exports.encodePacket(packet, true, true, function(data) {
455 return doneCallback(null, data);
456 });
457 }
458
459 map(packets, encodeOne, function(err, encodedPackets) {
460 var totalLength = encodedPackets.reduce(function(acc, p) {
461 var len;
462 if (typeof p === 'string'){
463 len = p.length;
464 } else {
465 len = p.byteLength;
466 }
467 return acc + len.toString().length + len + 2; // string/binary identifier + separator = 2
468 }, 0);
469
470 var resultArray = new Uint8Array(totalLength);
471
472 var bufferIndex = 0;
473 encodedPackets.forEach(function(p) {
474 var isString = typeof p === 'string';
475 var ab = p;
476 if (isString) {
477 var view = new Uint8Array(p.length);
478 for (var i = 0; i < p.length; i++) {
479 view[i] = p.charCodeAt(i);
480 }
481 ab = view.buffer;
482 }
483
484 if (isString) { // not true binary
485 resultArray[bufferIndex++] = 0;
486 } else { // true binary
487 resultArray[bufferIndex++] = 1;
488 }
489
490 var lenStr = ab.byteLength.toString();
491 for (var i = 0; i < lenStr.length; i++) {
492 resultArray[bufferIndex++] = parseInt(lenStr[i]);
493 }
494 resultArray[bufferIndex++] = 255;
495
496 var view = new Uint8Array(ab);
497 for (var i = 0; i < view.length; i++) {
498 resultArray[bufferIndex++] = view[i];
499 }
500 });
501
502 return callback(resultArray.buffer);
503 });
504};
505
506/**
507 * Encode as Blob
508 */
509
510exports.encodePayloadAsBlob = function(packets, callback) {
511 function encodeOne(packet, doneCallback) {
512 exports.encodePacket(packet, true, true, function(encoded) {
513 var binaryIdentifier = new Uint8Array(1);
514 binaryIdentifier[0] = 1;
515 if (typeof encoded === 'string') {
516 var view = new Uint8Array(encoded.length);
517 for (var i = 0; i < encoded.length; i++) {
518 view[i] = encoded.charCodeAt(i);
519 }
520 encoded = view.buffer;
521 binaryIdentifier[0] = 0;
522 }
523
524 var len = (encoded instanceof ArrayBuffer)
525 ? encoded.byteLength
526 : encoded.size;
527
528 var lenStr = len.toString();
529 var lengthAry = new Uint8Array(lenStr.length + 1);
530 for (var i = 0; i < lenStr.length; i++) {
531 lengthAry[i] = parseInt(lenStr[i]);
532 }
533 lengthAry[lenStr.length] = 255;
534
535 if (Blob) {
536 var blob = new Blob([binaryIdentifier.buffer, lengthAry.buffer, encoded]);
537 doneCallback(null, blob);
538 }
539 });
540 }
541
542 map(packets, encodeOne, function(err, results) {
543 return callback(new Blob(results));
544 });
545};
546
547/*
548 * Decodes data when a payload is maybe expected. Strings are decoded by
549 * interpreting each byte as a key code for entries marked to start with 0. See
550 * description of encodePayloadAsBinary
551 *
552 * @param {ArrayBuffer} data, callback method
553 * @api public
554 */
555
556exports.decodePayloadAsBinary = function (data, binaryType, callback) {
557 if (typeof binaryType === 'function') {
558 callback = binaryType;
559 binaryType = null;
560 }
561
562 var bufferTail = data;
563 var buffers = [];
564
565 var numberTooLong = false;
566 while (bufferTail.byteLength > 0) {
567 var tailArray = new Uint8Array(bufferTail);
568 var isString = tailArray[0] === 0;
569 var msgLength = '';
570
571 for (var i = 1; ; i++) {
572 if (tailArray[i] == 255) break;
573
574 if (msgLength.length > 310) {
575 numberTooLong = true;
576 break;
577 }
578
579 msgLength += tailArray[i];
580 }
581
582 if(numberTooLong) return callback(err, 0, 1);
583
584 bufferTail = sliceBuffer(bufferTail, 2 + msgLength.length);
585 msgLength = parseInt(msgLength);
586
587 var msg = sliceBuffer(bufferTail, 0, msgLength);
588 if (isString) {
589 try {
590 msg = String.fromCharCode.apply(null, new Uint8Array(msg));
591 } catch (e) {
592 // iPhone Safari doesn't let you apply to typed arrays
593 var typed = new Uint8Array(msg);
594 msg = '';
595 for (var i = 0; i < typed.length; i++) {
596 msg += String.fromCharCode(typed[i]);
597 }
598 }
599 }
600
601 buffers.push(msg);
602 bufferTail = sliceBuffer(bufferTail, msgLength);
603 }
604
605 var total = buffers.length;
606 buffers.forEach(function(buffer, i) {
607 callback(exports.decodePacket(buffer, binaryType, true), i, total);
608 });
609};