index.js (8041B)
1 2 /** 3 * Module dependencies. 4 */ 5 6 var debug = require('debug')('socket.io-parser'); 7 var Emitter = require('component-emitter'); 8 var binary = require('./binary'); 9 var isArray = require('isarray'); 10 var isBuf = require('./is-buffer'); 11 12 /** 13 * Protocol version. 14 * 15 * @api public 16 */ 17 18 exports.protocol = 4; 19 20 /** 21 * Packet types. 22 * 23 * @api public 24 */ 25 26 exports.types = [ 27 'CONNECT', 28 'DISCONNECT', 29 'EVENT', 30 'ACK', 31 'ERROR', 32 'BINARY_EVENT', 33 'BINARY_ACK' 34 ]; 35 36 /** 37 * Packet type `connect`. 38 * 39 * @api public 40 */ 41 42 exports.CONNECT = 0; 43 44 /** 45 * Packet type `disconnect`. 46 * 47 * @api public 48 */ 49 50 exports.DISCONNECT = 1; 51 52 /** 53 * Packet type `event`. 54 * 55 * @api public 56 */ 57 58 exports.EVENT = 2; 59 60 /** 61 * Packet type `ack`. 62 * 63 * @api public 64 */ 65 66 exports.ACK = 3; 67 68 /** 69 * Packet type `error`. 70 * 71 * @api public 72 */ 73 74 exports.ERROR = 4; 75 76 /** 77 * Packet type 'binary event' 78 * 79 * @api public 80 */ 81 82 exports.BINARY_EVENT = 5; 83 84 /** 85 * Packet type `binary ack`. For acks with binary arguments. 86 * 87 * @api public 88 */ 89 90 exports.BINARY_ACK = 6; 91 92 /** 93 * Encoder constructor. 94 * 95 * @api public 96 */ 97 98 exports.Encoder = Encoder; 99 100 /** 101 * Decoder constructor. 102 * 103 * @api public 104 */ 105 106 exports.Decoder = Decoder; 107 108 /** 109 * A socket.io Encoder instance 110 * 111 * @api public 112 */ 113 114 function Encoder() {} 115 116 var ERROR_PACKET = exports.ERROR + '"encode error"'; 117 118 /** 119 * Encode a packet as a single string if non-binary, or as a 120 * buffer sequence, depending on packet type. 121 * 122 * @param {Object} obj - packet object 123 * @param {Function} callback - function to handle encodings (likely engine.write) 124 * @return Calls callback with Array of encodings 125 * @api public 126 */ 127 128 Encoder.prototype.encode = function(obj, callback){ 129 debug('encoding packet %j', obj); 130 131 if (exports.BINARY_EVENT === obj.type || exports.BINARY_ACK === obj.type) { 132 encodeAsBinary(obj, callback); 133 } else { 134 var encoding = encodeAsString(obj); 135 callback([encoding]); 136 } 137 }; 138 139 /** 140 * Encode packet as string. 141 * 142 * @param {Object} packet 143 * @return {String} encoded 144 * @api private 145 */ 146 147 function encodeAsString(obj) { 148 149 // first is type 150 var str = '' + obj.type; 151 152 // attachments if we have them 153 if (exports.BINARY_EVENT === obj.type || exports.BINARY_ACK === obj.type) { 154 str += obj.attachments + '-'; 155 } 156 157 // if we have a namespace other than `/` 158 // we append it followed by a comma `,` 159 if (obj.nsp && '/' !== obj.nsp) { 160 str += obj.nsp + ','; 161 } 162 163 // immediately followed by the id 164 if (null != obj.id) { 165 str += obj.id; 166 } 167 168 // json data 169 if (null != obj.data) { 170 var payload = tryStringify(obj.data); 171 if (payload !== false) { 172 str += payload; 173 } else { 174 return ERROR_PACKET; 175 } 176 } 177 178 debug('encoded %j as %s', obj, str); 179 return str; 180 } 181 182 function tryStringify(str) { 183 try { 184 return JSON.stringify(str); 185 } catch(e){ 186 return false; 187 } 188 } 189 190 /** 191 * Encode packet as 'buffer sequence' by removing blobs, and 192 * deconstructing packet into object with placeholders and 193 * a list of buffers. 194 * 195 * @param {Object} packet 196 * @return {Buffer} encoded 197 * @api private 198 */ 199 200 function encodeAsBinary(obj, callback) { 201 202 function writeEncoding(bloblessData) { 203 var deconstruction = binary.deconstructPacket(bloblessData); 204 var pack = encodeAsString(deconstruction.packet); 205 var buffers = deconstruction.buffers; 206 207 buffers.unshift(pack); // add packet info to beginning of data list 208 callback(buffers); // write all the buffers 209 } 210 211 binary.removeBlobs(obj, writeEncoding); 212 } 213 214 /** 215 * A socket.io Decoder instance 216 * 217 * @return {Object} decoder 218 * @api public 219 */ 220 221 function Decoder() { 222 this.reconstructor = null; 223 } 224 225 /** 226 * Mix in `Emitter` with Decoder. 227 */ 228 229 Emitter(Decoder.prototype); 230 231 /** 232 * Decodes an encoded packet string into packet JSON. 233 * 234 * @param {String} obj - encoded packet 235 * @return {Object} packet 236 * @api public 237 */ 238 239 Decoder.prototype.add = function(obj) { 240 var packet; 241 if (typeof obj === 'string') { 242 packet = decodeString(obj); 243 if (exports.BINARY_EVENT === packet.type || exports.BINARY_ACK === packet.type) { // binary packet's json 244 this.reconstructor = new BinaryReconstructor(packet); 245 246 // no attachments, labeled binary but no binary data to follow 247 if (this.reconstructor.reconPack.attachments === 0) { 248 this.emit('decoded', packet); 249 } 250 } else { // non-binary full packet 251 this.emit('decoded', packet); 252 } 253 } else if (isBuf(obj) || obj.base64) { // raw binary data 254 if (!this.reconstructor) { 255 throw new Error('got binary data when not reconstructing a packet'); 256 } else { 257 packet = this.reconstructor.takeBinaryData(obj); 258 if (packet) { // received final buffer 259 this.reconstructor = null; 260 this.emit('decoded', packet); 261 } 262 } 263 } else { 264 throw new Error('Unknown type: ' + obj); 265 } 266 }; 267 268 /** 269 * Decode a packet String (JSON data) 270 * 271 * @param {String} str 272 * @return {Object} packet 273 * @api private 274 */ 275 276 function decodeString(str) { 277 var i = 0; 278 // look up type 279 var p = { 280 type: Number(str.charAt(0)) 281 }; 282 283 if (null == exports.types[p.type]) { 284 return error('unknown packet type ' + p.type); 285 } 286 287 // look up attachments if type binary 288 if (exports.BINARY_EVENT === p.type || exports.BINARY_ACK === p.type) { 289 var buf = ''; 290 while (str.charAt(++i) !== '-') { 291 buf += str.charAt(i); 292 if (i == str.length) break; 293 } 294 if (buf != Number(buf) || str.charAt(i) !== '-') { 295 throw new Error('Illegal attachments'); 296 } 297 p.attachments = Number(buf); 298 } 299 300 // look up namespace (if any) 301 if ('/' === str.charAt(i + 1)) { 302 p.nsp = ''; 303 while (++i) { 304 var c = str.charAt(i); 305 if (',' === c) break; 306 p.nsp += c; 307 if (i === str.length) break; 308 } 309 } else { 310 p.nsp = '/'; 311 } 312 313 // look up id 314 var next = str.charAt(i + 1); 315 if ('' !== next && Number(next) == next) { 316 p.id = ''; 317 while (++i) { 318 var c = str.charAt(i); 319 if (null == c || Number(c) != c) { 320 --i; 321 break; 322 } 323 p.id += str.charAt(i); 324 if (i === str.length) break; 325 } 326 p.id = Number(p.id); 327 } 328 329 // look up json data 330 if (str.charAt(++i)) { 331 var payload = tryParse(str.substr(i)); 332 var isPayloadValid = payload !== false && (p.type === exports.ERROR || isArray(payload)); 333 if (isPayloadValid) { 334 p.data = payload; 335 } else { 336 return error('invalid payload'); 337 } 338 } 339 340 debug('decoded %s as %j', str, p); 341 return p; 342 } 343 344 function tryParse(str) { 345 try { 346 return JSON.parse(str); 347 } catch(e){ 348 return false; 349 } 350 } 351 352 /** 353 * Deallocates a parser's resources 354 * 355 * @api public 356 */ 357 358 Decoder.prototype.destroy = function() { 359 if (this.reconstructor) { 360 this.reconstructor.finishedReconstruction(); 361 } 362 }; 363 364 /** 365 * A manager of a binary event's 'buffer sequence'. Should 366 * be constructed whenever a packet of type BINARY_EVENT is 367 * decoded. 368 * 369 * @param {Object} packet 370 * @return {BinaryReconstructor} initialized reconstructor 371 * @api private 372 */ 373 374 function BinaryReconstructor(packet) { 375 this.reconPack = packet; 376 this.buffers = []; 377 } 378 379 /** 380 * Method to be called when binary data received from connection 381 * after a BINARY_EVENT packet. 382 * 383 * @param {Buffer | ArrayBuffer} binData - the raw binary data received 384 * @return {null | Object} returns null if more binary data is expected or 385 * a reconstructed packet object if all buffers have been received. 386 * @api private 387 */ 388 389 BinaryReconstructor.prototype.takeBinaryData = function(binData) { 390 this.buffers.push(binData); 391 if (this.buffers.length === this.reconPack.attachments) { // done with buffer list 392 var packet = binary.reconstructPacket(this.reconPack, this.buffers); 393 this.finishedReconstruction(); 394 return packet; 395 } 396 return null; 397 }; 398 399 /** 400 * Cleans up binary packet reconstruction variables. 401 * 402 * @api private 403 */ 404 405 BinaryReconstructor.prototype.finishedReconstruction = function() { 406 this.reconPack = null; 407 this.buffers = []; 408 }; 409 410 function error(msg) { 411 return { 412 type: exports.ERROR, 413 data: 'parser error: ' + msg 414 }; 415 }