4 * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
12 var Transport = require('../../transport')
13 , EventEmitter = process.EventEmitter
14 , crypto = require('crypto')
15 , url = require('url')
16 , parser = require('../../parser')
17 , util = require('../../util');
20 * Export the constructor.
23 exports = module.exports = WebSocket;
24 exports.Parser = Parser;
27 * HTTP interface constructor. Interface compatible with all transports that
28 * depend on request-response cycles.
33 function WebSocket (mng, data, req) {
38 this.parser = new Parser();
39 this.parser.on('data', function (packet) {
40 self.onMessage(parser.decodePacket(packet));
42 this.parser.on('ping', function () {
43 // version 8 ping => pong
45 self.socket.write('\u008a\u0000');
52 this.parser.on('close', function () {
55 this.parser.on('error', function (reason) {
56 self.log.warn(self.name + ' parser error: ' + reason);
60 Transport.call(this, mng, data, req);
64 * Inherits from Transport.
67 WebSocket.prototype.__proto__ = Transport.prototype;
75 WebSocket.prototype.name = 'websocket';
78 * Websocket draft version
83 WebSocket.prototype.protocolVersion = '07-12';
86 * Called when the socket connects.
91 WebSocket.prototype.onSocketConnect = function () {
94 if (typeof this.req.headers.upgrade === 'undefined' ||
95 this.req.headers.upgrade.toLowerCase() !== 'websocket') {
96 this.log.warn(this.name + ' connection invalid');
101 var origin = this.req.headers['sec-websocket-origin']
102 , location = ((this.manager.settings['match origin protocol'] ?
103 origin.match(/^https/) : this.socket.encrypted) ?
105 + '://' + this.req.headers.host + this.req.url;
107 if (!this.verifyOrigin(origin)) {
108 this.log.warn(this.name + ' connection invalid: origin mismatch');
113 if (!this.req.headers['sec-websocket-key']) {
114 this.log.warn(this.name + ' connection invalid: received no key');
120 var key = this.req.headers['sec-websocket-key'];
121 var shasum = crypto.createHash('sha1');
122 shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
123 key = shasum.digest('base64');
126 'HTTP/1.1 101 Switching Protocols'
127 , 'Upgrade: websocket'
128 , 'Connection: Upgrade'
129 , 'Sec-WebSocket-Accept: ' + key
133 this.socket.write(headers.concat('', '').join('\r\n'));
134 this.socket.setTimeout(0);
135 this.socket.setNoDelay(true);
141 this.socket.on('data', function (data) {
142 self.parser.add(data);
147 * Verifies the origin of a request.
152 WebSocket.prototype.verifyOrigin = function (origin) {
153 var origins = this.manager.get('origins');
155 if (origin === 'null') origin = '*';
157 if (origins.indexOf('*:*') !== -1) {
163 var parts = url.parse(origin);
164 parts.port = parts.port || 80;
166 ~origins.indexOf(parts.hostname + ':' + parts.port) ||
167 ~origins.indexOf(parts.hostname + ':*') ||
168 ~origins.indexOf('*:' + parts.port);
169 if (!ok) this.log.warn('illegal origin: ' + origin);
172 this.log.warn('error parsing origin');
176 this.log.warn('origin missing from websocket call, yet required by config');
182 * Writes to the socket.
187 WebSocket.prototype.write = function (data) {
189 var buf = this.frame(0x81, data);
191 this.socket.write(buf, 'binary');
197 this.log.debug(this.name + ' writing', data);
207 WebSocket.prototype.payload = function (msgs) {
208 for (var i = 0, l = msgs.length; i < l; i++) {
216 * Frame server-to-client output as a text packet.
221 WebSocket.prototype.frame = function (opcode, str) {
222 var dataBuffer = new Buffer(str)
223 , dataLength = dataBuffer.length
225 , secondByte = dataLength;
226 if (dataLength > 65536) {
230 else if (dataLength > 125) {
234 var outputBuffer = new Buffer(dataLength + startOffset);
235 outputBuffer[0] = opcode;
236 outputBuffer[1] = secondByte;
237 dataBuffer.copy(outputBuffer, startOffset);
238 switch (secondByte) {
240 outputBuffer[2] = dataLength >>> 8;
241 outputBuffer[3] = dataLength % 256;
245 for (var i = 1; i <= 8; ++i) {
246 outputBuffer[startOffset - i] = l & 0xff;
254 * Closes the connection.
259 WebSocket.prototype.doClose = function () {
271 activeFragmentedOperation: null,
276 this.overflow = null;
277 this.expectOffset = 0;
278 this.expectBuffer = null;
279 this.expectHandler = null;
280 this.currentMessage = '';
283 this.opcodeHandlers = {
285 '1': function(data) {
286 var finish = function(mask, data) {
287 self.currentMessage += self.unmask(mask, data);
288 if (self.state.lastFragment) {
289 self.emit('data', self.currentMessage);
290 self.currentMessage = '';
295 var expectData = function(length) {
296 if (self.state.masked) {
297 self.expect('Mask', 4, function(data) {
299 self.expect('Data', length, function(data) {
305 self.expect('Data', length, function(data) {
312 var firstLength = data[1] & 0x7f;
313 if (firstLength < 126) {
314 expectData(firstLength);
316 else if (firstLength == 126) {
317 self.expect('Length', 2, function(data) {
318 expectData(util.unpack(data));
321 else if (firstLength == 127) {
322 self.expect('Length', 8, function(data) {
323 if (util.unpack(data.slice(0, 4)) != 0) {
324 self.error('packets with length spanning more than 32 bit is currently not supported');
327 var lengthBytes = data.slice(4); // note: cap to 32 bit length
328 expectData(util.unpack(data));
333 '2': function(data) {
334 var finish = function(mask, data) {
335 if (typeof self.currentMessage == 'string') self.currentMessage = []; // build a buffer list
336 self.currentMessage.push(self.unmask(mask, data, true));
337 if (self.state.lastFragment) {
338 self.emit('binary', self.concatBuffers(self.currentMessage));
339 self.currentMessage = '';
344 var expectData = function(length) {
345 if (self.state.masked) {
346 self.expect('Mask', 4, function(data) {
348 self.expect('Data', length, function(data) {
354 self.expect('Data', length, function(data) {
361 var firstLength = data[1] & 0x7f;
362 if (firstLength < 126) {
363 expectData(firstLength);
365 else if (firstLength == 126) {
366 self.expect('Length', 2, function(data) {
367 expectData(util.unpack(data));
370 else if (firstLength == 127) {
371 self.expect('Length', 8, function(data) {
372 if (util.unpack(data.slice(0, 4)) != 0) {
373 self.error('packets with length spanning more than 32 bit is currently not supported');
376 var lengthBytes = data.slice(4); // note: cap to 32 bit length
377 expectData(util.unpack(data));
382 '8': function(data) {
387 '9': function(data) {
388 if (self.state.lastFragment == false) {
389 self.error('fragmented ping is not supported');
393 var finish = function(mask, data) {
394 self.emit('ping', self.unmask(mask, data));
398 var expectData = function(length) {
399 if (self.state.masked) {
400 self.expect('Mask', 4, function(data) {
402 self.expect('Data', length, function(data) {
408 self.expect('Data', length, function(data) {
415 var firstLength = data[1] & 0x7f;
416 if (firstLength == 0) {
419 else if (firstLength < 126) {
420 expectData(firstLength);
422 else if (firstLength == 126) {
423 self.expect('Length', 2, function(data) {
424 expectData(util.unpack(data));
427 else if (firstLength == 127) {
428 self.expect('Length', 8, function(data) {
429 expectData(util.unpack(data));
435 this.expect('Opcode', 2, this.processPacket);
439 * Inherits from EventEmitter.
442 Parser.prototype.__proto__ = EventEmitter.prototype;
445 * Add new data to the parser.
450 Parser.prototype.add = function(data) {
451 if (this.expectBuffer == null) {
452 this.addToOverflow(data);
455 var toRead = Math.min(data.length, this.expectBuffer.length - this.expectOffset);
456 data.copy(this.expectBuffer, this.expectOffset, 0, toRead);
457 this.expectOffset += toRead;
458 if (toRead < data.length) {
459 // at this point the overflow buffer shouldn't at all exist
460 this.overflow = new Buffer(data.length - toRead);
461 data.copy(this.overflow, 0, toRead, toRead + this.overflow.length);
463 if (this.expectOffset == this.expectBuffer.length) {
464 var bufferForHandler = this.expectBuffer;
465 this.expectBuffer = null;
466 this.expectOffset = 0;
467 this.expectHandler.call(this, bufferForHandler);
472 * Adds a piece of data to the overflow.
477 Parser.prototype.addToOverflow = function(data) {
478 if (this.overflow == null) this.overflow = data;
480 var prevOverflow = this.overflow;
481 this.overflow = new Buffer(this.overflow.length + data.length);
482 prevOverflow.copy(this.overflow, 0);
483 data.copy(this.overflow, prevOverflow.length);
488 * Waits for a certain amount of bytes to be available, then fires a callback.
493 Parser.prototype.expect = function(what, length, handler) {
494 this.expectBuffer = new Buffer(length);
495 this.expectOffset = 0;
496 this.expectHandler = handler;
497 if (this.overflow != null) {
498 var toOverflow = this.overflow;
499 this.overflow = null;
500 this.add(toOverflow);
505 * Start processing a new packet.
510 Parser.prototype.processPacket = function (data) {
511 if ((data[0] & 0x70) != 0) {
512 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);