3 * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
11 var Transport = require('../../transport')
12 , EventEmitter = process.EventEmitter
13 , crypto = require('crypto')
14 , url = require('url')
15 , parser = require('../../parser')
16 , util = require('../../util');
19 * Export the constructor.
22 exports = module.exports = WebSocket;
23 exports.Parser = Parser;
26 * HTTP interface constructor. Interface compatible with all transports that
27 * depend on request-response cycles.
32 function WebSocket (mng, data, req) {
37 this.parser = new Parser();
38 this.parser.on('data', function (packet) {
39 self.onMessage(parser.decodePacket(packet));
41 this.parser.on('ping', function () {
42 // version 8 ping => pong
44 self.socket.write('\u008a\u0000');
51 this.parser.on('close', function () {
54 this.parser.on('error', function (reason) {
55 self.log.warn(self.name + ' parser error: ' + reason);
59 Transport.call(this, mng, data, req);
63 * Inherits from Transport.
66 WebSocket.prototype.__proto__ = Transport.prototype;
74 WebSocket.prototype.name = 'websocket';
77 * Websocket draft version
82 WebSocket.prototype.protocolVersion = '16';
85 * Called when the socket connects.
90 WebSocket.prototype.onSocketConnect = function () {
93 if (typeof this.req.headers.upgrade === 'undefined' ||
94 this.req.headers.upgrade.toLowerCase() !== 'websocket') {
95 this.log.warn(this.name + ' connection invalid');
100 var origin = this.req.headers['origin'] || ''
101 , location = ((this.manager.settings['match origin protocol'] ?
102 origin.match(/^https/) : this.socket.encrypted) ?
104 + '://' + this.req.headers.host + this.req.url;
106 if (!this.verifyOrigin(origin)) {
107 this.log.warn(this.name + ' connection invalid: origin mismatch');
112 if (!this.req.headers['sec-websocket-key']) {
113 this.log.warn(this.name + ' connection invalid: received no key');
119 var key = this.req.headers['sec-websocket-key'];
120 var shasum = crypto.createHash('sha1');
121 shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
122 key = shasum.digest('base64');
125 'HTTP/1.1 101 Switching Protocols'
126 , 'Upgrade: websocket'
127 , 'Connection: Upgrade'
128 , 'Sec-WebSocket-Accept: ' + key
132 this.socket.write(headers.concat('', '').join('\r\n'));
133 this.socket.setTimeout(0);
134 this.socket.setNoDelay(true);
140 this.socket.on('data', function (data) {
141 self.parser.add(data);
146 * Verifies the origin of a request.
151 WebSocket.prototype.verifyOrigin = function (origin) {
152 var origins = this.manager.get('origins');
154 if (origin === 'null') origin = '*';
156 if (origins.indexOf('*:*') !== -1) {
162 var parts = url.parse(origin);
163 parts.port = parts.port || 80;
165 ~origins.indexOf(parts.hostname + ':' + parts.port) ||
166 ~origins.indexOf(parts.hostname + ':*') ||
167 ~origins.indexOf('*:' + parts.port);
168 if (!ok) this.log.warn('illegal origin: ' + origin);
171 this.log.warn('error parsing origin');
175 this.log.warn('origin missing from websocket call, yet required by config');
181 * Writes to the socket.
186 WebSocket.prototype.write = function (data) {
188 var buf = this.frame(0x81, data);
190 this.socket.write(buf, 'binary');
196 this.log.debug(this.name + ' writing', data);
206 WebSocket.prototype.payload = function (msgs) {
207 for (var i = 0, l = msgs.length; i < l; i++) {
215 * Frame server-to-client output as a text packet.
220 WebSocket.prototype.frame = function (opcode, str) {
221 var dataBuffer = new Buffer(str)
222 , dataLength = dataBuffer.length
224 , secondByte = dataLength;
225 if (dataLength > 65536) {
229 else if (dataLength > 125) {
233 var outputBuffer = new Buffer(dataLength + startOffset);
234 outputBuffer[0] = opcode;
235 outputBuffer[1] = secondByte;
236 dataBuffer.copy(outputBuffer, startOffset);
237 switch (secondByte) {
239 outputBuffer[2] = dataLength >>> 8;
240 outputBuffer[3] = dataLength % 256;
244 for (var i = 1; i <= 8; ++i) {
245 outputBuffer[startOffset - i] = l & 0xff;
253 * Closes the connection.
258 WebSocket.prototype.doClose = function () {
270 activeFragmentedOperation: null,
275 this.overflow = null;
276 this.expectOffset = 0;
277 this.expectBuffer = null;
278 this.expectHandler = null;
279 this.currentMessage = '';
282 this.opcodeHandlers = {
284 '1': function(data) {
285 var finish = function(mask, data) {
286 self.currentMessage += self.unmask(mask, data);
287 if (self.state.lastFragment) {
288 self.emit('data', self.currentMessage);
289 self.currentMessage = '';
294 var expectData = function(length) {
295 if (self.state.masked) {
296 self.expect('Mask', 4, function(data) {
298 self.expect('Data', length, function(data) {
304 self.expect('Data', length, function(data) {
311 var firstLength = data[1] & 0x7f;
312 if (firstLength < 126) {
313 expectData(firstLength);
315 else if (firstLength == 126) {
316 self.expect('Length', 2, function(data) {
317 expectData(util.unpack(data));
320 else if (firstLength == 127) {
321 self.expect('Length', 8, function(data) {
322 if (util.unpack(data.slice(0, 4)) != 0) {
323 self.error('packets with length spanning more than 32 bit is currently not supported');
326 var lengthBytes = data.slice(4); // note: cap to 32 bit length
327 expectData(util.unpack(data));
332 '2': function(data) {
333 var finish = function(mask, data) {
334 if (typeof self.currentMessage == 'string') self.currentMessage = []; // build a buffer list
335 self.currentMessage.push(self.unmask(mask, data, true));
336 if (self.state.lastFragment) {
337 self.emit('binary', self.concatBuffers(self.currentMessage));
338 self.currentMessage = '';
343 var expectData = function(length) {
344 if (self.state.masked) {
345 self.expect('Mask', 4, function(data) {
347 self.expect('Data', length, function(data) {
353 self.expect('Data', length, function(data) {
360 var firstLength = data[1] & 0x7f;
361 if (firstLength < 126) {
362 expectData(firstLength);
364 else if (firstLength == 126) {
365 self.expect('Length', 2, function(data) {
366 expectData(util.unpack(data));
369 else if (firstLength == 127) {
370 self.expect('Length', 8, function(data) {
371 if (util.unpack(data.slice(0, 4)) != 0) {
372 self.error('packets with length spanning more than 32 bit is currently not supported');
375 var lengthBytes = data.slice(4); // note: cap to 32 bit length
376 expectData(util.unpack(data));
381 '8': function(data) {
386 '9': function(data) {
387 if (self.state.lastFragment == false) {
388 self.error('fragmented ping is not supported');
392 var finish = function(mask, data) {
393 self.emit('ping', self.unmask(mask, data));
397 var expectData = function(length) {
398 if (self.state.masked) {
399 self.expect('Mask', 4, function(data) {
401 self.expect('Data', length, function(data) {
407 self.expect('Data', length, function(data) {
414 var firstLength = data[1] & 0x7f;
415 if (firstLength == 0) {
418 else if (firstLength < 126) {
419 expectData(firstLength);
421 else if (firstLength == 126) {
422 self.expect('Length', 2, function(data) {
423 expectData(util.unpack(data));
426 else if (firstLength == 127) {
427 self.expect('Length', 8, function(data) {
428 expectData(util.unpack(data));
434 this.expect('Opcode', 2, this.processPacket);
438 * Inherits from EventEmitter.
441 Parser.prototype.__proto__ = EventEmitter.prototype;
444 * Add new data to the parser.
449 Parser.prototype.add = function(data) {
450 if (this.expectBuffer == null) {
451 this.addToOverflow(data);
454 var toRead = Math.min(data.length, this.expectBuffer.length - this.expectOffset);
455 data.copy(this.expectBuffer, this.expectOffset, 0, toRead);
456 this.expectOffset += toRead;
457 if (toRead < data.length) {
458 // at this point the overflow buffer shouldn't at all exist
459 this.overflow = new Buffer(data.length - toRead);
460 data.copy(this.overflow, 0, toRead, toRead + this.overflow.length);
462 if (this.expectOffset == this.expectBuffer.length) {
463 var bufferForHandler = this.expectBuffer;
464 this.expectBuffer = null;
465 this.expectOffset = 0;
466 this.expectHandler.call(this, bufferForHandler);
471 * Adds a piece of data to the overflow.
476 Parser.prototype.addToOverflow = function(data) {
477 if (this.overflow == null) this.overflow = data;
479 var prevOverflow = this.overflow;
480 this.overflow = new Buffer(this.overflow.length + data.length);
481 prevOverflow.copy(this.overflow, 0);
482 data.copy(this.overflow, prevOverflow.length);
487 * Waits for a certain amount of bytes to be available, then fires a callback.
492 Parser.prototype.expect = function(what, length, handler) {
493 this.expectBuffer = new Buffer(length);
494 this.expectOffset = 0;
495 this.expectHandler = handler;
496 if (this.overflow != null) {
497 var toOverflow = this.overflow;
498 this.overflow = null;
499 this.add(toOverflow);
504 * Start processing a new packet.
509 Parser.prototype.processPacket = function (data) {
510 if ((data[0] & 0x70) != 0) {
511 this.error('reserved fields must be empty');
514 this.state.lastFragment = (data[0] & 0x80) == 0x80;
515 this.state.masked = (data[1] & 0x80) == 0x80;
516 var opcode = data[0] & 0xf;
518 // continuation frame
519 this.state.opcode = this.state.activeFragmentedOperation;
520 if (!(this.state.opcode == 1 || this.state.opcode == 2)) {
521 this.error('continuation frame cannot follow current opcode')
526 this.state.opcode = opcode;
527 if (this.state.lastFragment === false) {
528 this.state.activeFragmentedOperation = opcode;
531 var handler = this.opcodeHandlers[this.state.opcode];
532 if (typeof handler == 'undefined') this.error('no handler for opcode ' + this.state.opcode);
537 * Endprocessing a packet.
542 Parser.prototype.endPacket = function() {
543 this.expectOffset = 0;
544 this.expectBuffer = null;
545 this.expectHandler = null;
546 if (this.state.lastFragment && this.state.opcode == this.state.activeFragmentedOperation) {
547 // end current fragmented operation
548 this.state.activeFragmentedOperation = null;
550 this.state.lastFragment = false;
551 this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0;
552 this.state.masked = false;
553 this.expect('Opcode', 2, this.processPacket);
557 * Reset the parser state.
562 Parser.prototype.reset = function() {
564 activeFragmentedOperation: null,
569 this.expectOffset = 0;
570 this.expectBuffer = null;
571 this.expectHandler = null;
572 this.overflow = null;
573 this.currentMessage = '';
577 * Unmask received data.
582 Parser.prototype.unmask = function (mask, buf, binary) {
584 for (var i = 0, ll = buf.length; i < ll; i++) {
585 buf[i] ^= mask[i % 4];
588 if (binary) return buf;
589 return buf != null ? buf.toString('utf8') : '';
593 * Concatenates a list of buffers.
598 Parser.prototype.concatBuffers = function(buffers) {
600 for (var i = 0, l = buffers.length; i < l; ++i) {
601 length += buffers[i].length;
603 var mergedBuffer = new Buffer(length);
605 for (var i = 0, l = buffers.length; i < l; ++i) {
606 buffers[i].copy(mergedBuffer, offset);
607 offset += buffers[i].length;
618 Parser.prototype.error = function (reason) {
620 this.emit('error', reason);