mirror of
https://github.com/socketio/socket.io.git
synced 2026-01-11 07:58:13 -05:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e6f58fe27 | ||
|
|
e3fb39da3d | ||
|
|
cc275813b5 | ||
|
|
9d57245d65 | ||
|
|
9a05b3597e | ||
|
|
41f38b60e8 | ||
|
|
a9bbc38919 | ||
|
|
e282ab0e63 | ||
|
|
546d5203d4 | ||
|
|
69941e602b | ||
|
|
7ac9c2e888 | ||
|
|
fa1f50d173 | ||
|
|
20ddd5f11a | ||
|
|
e1891fd615 | ||
|
|
4d66f78ca2 | ||
|
|
6db6db41a2 | ||
|
|
713baa40e1 | ||
|
|
7c196f5b32 | ||
|
|
bd360a15ef | ||
|
|
ae7f25332a | ||
|
|
63fc15d276 | ||
|
|
2c5fa40c0d | ||
|
|
0b61eda84c | ||
|
|
23ba929f3f | ||
|
|
13647075f2 | ||
|
|
4c9414c4c1 | ||
|
|
ec88f95722 | ||
|
|
f377cd631e | ||
|
|
e41aab84f8 | ||
|
|
0a6c78cbb8 | ||
|
|
004130cb11 | ||
|
|
a300223122 | ||
|
|
0769c40368 | ||
|
|
355203afdb | ||
|
|
46bfcd0d83 | ||
|
|
46b2f86372 | ||
|
|
e5c86178f5 | ||
|
|
0e08d67e48 | ||
|
|
8efb1bc6e2 | ||
|
|
8bdf221935 | ||
|
|
899fb7faa1 | ||
|
|
34622b74ef | ||
|
|
d327976064 | ||
|
|
07b9c4696d | ||
|
|
7a5913b8a6 | ||
|
|
00b75759f1 |
35
History.md
35
History.md
@@ -1,4 +1,39 @@
|
||||
|
||||
0.8.3 / 2011-09-03
|
||||
==================
|
||||
|
||||
* Fixed `\n` parsing for non-JSON packets (fixes #479).
|
||||
* Fixed parsing of certain unicode characters (fixes #451).
|
||||
* Fixed transport message packet logging.
|
||||
* Fixed emission of `error` event resulting in an uncaught exception if unhandled (fixes #476).
|
||||
* Fixed; allow for falsy values as the configuration value of `log level` (fixes #491).
|
||||
* Fixed repository URI in `package.json`. Fixes #504.
|
||||
* Added text/plain content-type to handshake responses [einaros]
|
||||
* Improved single byte writes [einaros]
|
||||
* Updated socket.io-flashsocket default port from 843 to 10843 [3rd-Eden]
|
||||
* Updated client.
|
||||
|
||||
0.8.2 / 2011-08-29
|
||||
==================
|
||||
|
||||
* Updated client.
|
||||
|
||||
0.8.1 / 2011-08-29
|
||||
==================
|
||||
|
||||
* Fixed utf8 bug in send framing in websocket [einaros]
|
||||
* Fixed typo in docs [Znarkus]
|
||||
* Fixed bug in send framing for over 64kB of data in websocket [einaros]
|
||||
* Corrected ping handling in websocket transport [einaros]
|
||||
|
||||
0.8.0 / 2011-08-28
|
||||
==================
|
||||
|
||||
* Updated to work with two-level websocket versioning. [einaros]
|
||||
* Added hybi07 support. [einaros]
|
||||
* Added hybi10 support. [einaros]
|
||||
* Added http referrer verification to manager.js verifyOrigin. [einaors]
|
||||
|
||||
0.7.11 / 2011-08-27
|
||||
===================
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ Besides `connect`, `message` and `disconnect`, you can emit custom events:
|
||||
var io = require('socket.io').listen(80);
|
||||
|
||||
io.sockets.on('connection', function (socket) {
|
||||
io.sockets.emit('this', { will: 'be received by everyone');
|
||||
io.sockets.emit('this', { will: 'be received by everyone' });
|
||||
|
||||
socket.on('private message', function (from, msg) {
|
||||
console.log('I received a private message by ', from, ' saying ', msg);
|
||||
|
||||
@@ -71,7 +71,7 @@ function Manager (server, options) {
|
||||
, 'heartbeat interval': 20
|
||||
, 'polling duration': 20
|
||||
, 'flash policy server': true
|
||||
, 'flash policy port': 843
|
||||
, 'flash policy port': 10843
|
||||
, 'destroy upgrade': true
|
||||
, 'browser client': true
|
||||
, 'browser client minification': false
|
||||
@@ -141,7 +141,7 @@ Manager.prototype.__defineGetter__('log', function () {
|
||||
if (this.disabled('log')) return;
|
||||
|
||||
var logger = this.get('logger');
|
||||
logger.level = this.get('log level');
|
||||
logger.level = this.get('log level') || -1;
|
||||
|
||||
return logger;
|
||||
});
|
||||
@@ -775,7 +775,7 @@ Manager.prototype.handleHandshake = function (data, req, res) {
|
||||
res.writeHead(200, { 'Content-Type': 'application/javascript' });
|
||||
res.end('io.j[' + data.query.jsonp + '](new Error("' + message + '"));');
|
||||
} else {
|
||||
res.writeHead(status);
|
||||
res.writeHead(status, { 'Content-Type': 'text/plain' });
|
||||
res.end(message);
|
||||
}
|
||||
};
|
||||
@@ -808,7 +808,7 @@ Manager.prototype.handleHandshake = function (data, req, res) {
|
||||
hs = 'io.j[' + data.query.jsonp + '](' + JSON.stringify(hs) + ');';
|
||||
res.writeHead(200, { 'Content-Type': 'application/javascript' });
|
||||
} else {
|
||||
res.writeHead(200);
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
}
|
||||
|
||||
res.end(hs);
|
||||
@@ -866,7 +866,7 @@ Manager.prototype.handshakeData = function (data) {
|
||||
*/
|
||||
|
||||
Manager.prototype.verifyOrigin = function (request) {
|
||||
var origin = request.headers.origin
|
||||
var origin = request.headers.origin || request.headers.referer
|
||||
, origins = this.get('origins');
|
||||
|
||||
if (origin === 'null') origin = '*';
|
||||
@@ -878,14 +878,19 @@ Manager.prototype.verifyOrigin = function (request) {
|
||||
if (origin) {
|
||||
try {
|
||||
var parts = url.parse(origin);
|
||||
|
||||
return
|
||||
~origins.indexOf(parts.host + ':' + parts.port) ||
|
||||
~origins.indexOf(parts.host + ':*') ||
|
||||
var ok =
|
||||
~origins.indexOf(parts.hostname + ':' + parts.port) ||
|
||||
~origins.indexOf(parts.hostname + ':*') ||
|
||||
~origins.indexOf('*:' + parts.port);
|
||||
} catch (ex) {}
|
||||
if (!ok) this.log.warn('illegal origin: ' + origin);
|
||||
return ok;
|
||||
} catch (ex) {
|
||||
this.log.warn('error parsing origin');
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.log.warn('origin missing from handshake, yet required by config');
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
@@ -324,8 +324,9 @@ SocketNamespace.prototype.handlePacket = function (sessid, packet) {
|
||||
case 'event':
|
||||
var params = [packet.name].concat(packet.args);
|
||||
|
||||
if (dataAck)
|
||||
if (dataAck) {
|
||||
params.push(ack);
|
||||
}
|
||||
|
||||
socket.$emit.apply(socket, params);
|
||||
break;
|
||||
|
||||
@@ -138,7 +138,7 @@ exports.encodePayload = function (packets) {
|
||||
* @api private
|
||||
*/
|
||||
|
||||
var regexp = /^([^:]+):([0-9]+)?(\+)?:([^:]+)?:?(.*)?$/;
|
||||
var regexp = /([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/;
|
||||
|
||||
exports.decodePacket = function (data) {
|
||||
var pieces = data.match(regexp);
|
||||
|
||||
@@ -15,7 +15,7 @@ var client = require('socket.io-client');
|
||||
* Version.
|
||||
*/
|
||||
|
||||
exports.version = '0.7.11';
|
||||
exports.version = '0.8.4';
|
||||
|
||||
/**
|
||||
* Supported protocol version.
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
var parser = require('./parser')
|
||||
, util = require('./util')
|
||||
, EventEmitter = process.EventEmitter;
|
||||
, EventEmitter = process.EventEmitter
|
||||
|
||||
/**
|
||||
* Export the constructor.
|
||||
@@ -19,6 +19,12 @@ var parser = require('./parser')
|
||||
|
||||
exports = module.exports = Socket;
|
||||
|
||||
/**
|
||||
* Default error event listener to prevent uncaught exceptions.
|
||||
*/
|
||||
|
||||
var defaultError = function () {};
|
||||
|
||||
/**
|
||||
* Socket constructor.
|
||||
*
|
||||
@@ -39,6 +45,7 @@ function Socket (manager, id, nsp, readable) {
|
||||
this.setFlags();
|
||||
this.readable = readable;
|
||||
this.store = this.manager.store.client(this.id);
|
||||
this.on('error', defaultError);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -160,7 +167,7 @@ Socket.prototype.join = function (name, fn) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Joins a user to a room.
|
||||
* Un-joins a user from a room.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
@@ -24,7 +24,7 @@ exports = module.exports = FlashSocket;
|
||||
*/
|
||||
|
||||
function FlashSocket (mng, data, req) {
|
||||
WebSocket.call(this, mng, data, req);
|
||||
return WebSocket.call(this, mng, data, req);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -56,7 +56,7 @@ HTTPTransport.prototype.handleRequest = function (req) {
|
||||
req.on('end', function () {
|
||||
res.writeHead(200, headers);
|
||||
res.end('1');
|
||||
|
||||
|
||||
self.onData(self.postEncoded ? qs.parse(buffer).d : buffer);
|
||||
});
|
||||
|
||||
@@ -83,9 +83,9 @@ HTTPTransport.prototype.handleRequest = function (req) {
|
||||
|
||||
HTTPTransport.prototype.onData = function (data) {
|
||||
var messages = parser.decodePayload(data);
|
||||
this.log.debug(this.name + ' received data packet', data);
|
||||
|
||||
for (var i = 0, l = messages.length; i < l; i++) {
|
||||
this.log.debug(this.name + ' received data packet', data);
|
||||
this.onMessage(messages[i]);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -9,10 +9,7 @@
|
||||
* Module requirements.
|
||||
*/
|
||||
|
||||
var Transport = require('../transport')
|
||||
, EventEmitter = process.EventEmitter
|
||||
, crypto = require('crypto')
|
||||
, parser = require('../parser');
|
||||
var protocolVersions = require('./websocket/');
|
||||
|
||||
/**
|
||||
* Export the constructor.
|
||||
@@ -28,323 +25,9 @@ exports = module.exports = WebSocket;
|
||||
*/
|
||||
|
||||
function WebSocket (mng, data, req) {
|
||||
// parser
|
||||
var self = this;
|
||||
|
||||
this.parser = new Parser();
|
||||
this.parser.on('data', function (packet) {
|
||||
self.log.debug(self.name + ' received data packet', packet);
|
||||
self.onMessage(parser.decodePacket(packet));
|
||||
});
|
||||
this.parser.on('close', function () {
|
||||
self.end();
|
||||
});
|
||||
this.parser.on('error', function () {
|
||||
self.end();
|
||||
});
|
||||
|
||||
Transport.call(this, mng, data, req);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from Transport.
|
||||
*/
|
||||
|
||||
WebSocket.prototype.__proto__ = Transport.prototype;
|
||||
|
||||
/**
|
||||
* Transport name
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
WebSocket.prototype.name = 'websocket';
|
||||
|
||||
/**
|
||||
* Called when the socket connects.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.onSocketConnect = function () {
|
||||
var self = this;
|
||||
|
||||
this.socket.setNoDelay(true);
|
||||
|
||||
this.buffer = true;
|
||||
this.buffered = [];
|
||||
|
||||
if (this.req.headers.upgrade !== 'WebSocket') {
|
||||
this.log.warn(this.name + ' connection invalid');
|
||||
this.end();
|
||||
return;
|
||||
var version = req.headers['sec-websocket-version'];
|
||||
if (typeof version !== 'undefined' && typeof protocolVersions[version] !== 'undefined') {
|
||||
return new protocolVersions[version](mng, data, req);
|
||||
}
|
||||
|
||||
var origin = this.req.headers.origin
|
||||
, location = (this.socket.encrypted ? 'wss' : 'ws')
|
||||
+ '://' + this.req.headers.host + this.req.url
|
||||
, waitingForNonce = false;
|
||||
|
||||
if (this.req.headers['sec-websocket-key1']) {
|
||||
// If we don't have the nonce yet, wait for it (HAProxy compatibility).
|
||||
if (! (this.req.head && this.req.head.length >= 8)) {
|
||||
waitingForNonce = true;
|
||||
}
|
||||
|
||||
var headers = [
|
||||
'HTTP/1.1 101 WebSocket Protocol Handshake'
|
||||
, 'Upgrade: WebSocket'
|
||||
, 'Connection: Upgrade'
|
||||
, 'Sec-WebSocket-Origin: ' + origin
|
||||
, 'Sec-WebSocket-Location: ' + location
|
||||
];
|
||||
|
||||
if (this.req.headers['sec-websocket-protocol']){
|
||||
headers.push('Sec-WebSocket-Protocol: '
|
||||
+ this.req.headers['sec-websocket-protocol']);
|
||||
}
|
||||
} else {
|
||||
var headers = [
|
||||
'HTTP/1.1 101 Web Socket Protocol Handshake'
|
||||
, 'Upgrade: WebSocket'
|
||||
, 'Connection: Upgrade'
|
||||
, 'WebSocket-Origin: ' + origin
|
||||
, 'WebSocket-Location: ' + location
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
this.socket.write(headers.concat('', '').join('\r\n'));
|
||||
this.socket.setTimeout(0);
|
||||
this.socket.setNoDelay(true);
|
||||
this.socket.setEncoding('utf8');
|
||||
} catch (e) {
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (waitingForNonce) {
|
||||
this.socket.setEncoding('binary');
|
||||
} else if (this.proveReception(headers)) {
|
||||
self.flush();
|
||||
}
|
||||
|
||||
var headBuffer = '';
|
||||
|
||||
this.socket.on('data', function (data) {
|
||||
if (waitingForNonce) {
|
||||
headBuffer += data;
|
||||
|
||||
if (headBuffer.length < 8) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Restore the connection to utf8 encoding after receiving the nonce
|
||||
self.socket.setEncoding('utf8');
|
||||
waitingForNonce = false;
|
||||
|
||||
// Stuff the nonce into the location where it's expected to be
|
||||
self.req.head = headBuffer.substr(0, 8);
|
||||
headBuffer = '';
|
||||
|
||||
if (self.proveReception(headers)) {
|
||||
self.flush();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
self.parser.add(data);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes to the socket.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.write = function (data) {
|
||||
if (this.open) {
|
||||
this.drained = false;
|
||||
|
||||
if (this.buffer) {
|
||||
this.buffered.push(data);
|
||||
return this;
|
||||
}
|
||||
|
||||
var length = Buffer.byteLength(data)
|
||||
, buffer = new Buffer(2 + length);
|
||||
|
||||
buffer.write('\u0000', 'binary');
|
||||
buffer.write(data, 1, 'utf8');
|
||||
buffer.write('\uffff', 1 + length, 'binary');
|
||||
|
||||
try {
|
||||
if (this.socket.write(buffer)) {
|
||||
this.drained = true;
|
||||
}
|
||||
} catch (e) {
|
||||
this.end();
|
||||
}
|
||||
|
||||
this.log.debug(this.name + ' writing', data);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Flushes the internal buffer
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.flush = function () {
|
||||
this.buffer = false;
|
||||
|
||||
for (var i = 0, l = this.buffered.length; i < l; i++) {
|
||||
this.write(this.buffered.splice(0, 1)[0]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Finishes the handshake.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.proveReception = function (headers) {
|
||||
var self = this
|
||||
, k1 = this.req.headers['sec-websocket-key1']
|
||||
, k2 = this.req.headers['sec-websocket-key2'];
|
||||
|
||||
if (k1 && k2){
|
||||
var md5 = crypto.createHash('md5');
|
||||
|
||||
[k1, k2].forEach(function (k) {
|
||||
var n = parseInt(k.replace(/[^\d]/g, ''))
|
||||
, spaces = k.replace(/[^ ]/g, '').length;
|
||||
|
||||
if (spaces === 0 || n % spaces !== 0){
|
||||
self.log.warn('Invalid ' + self.name + ' key: "' + k + '".');
|
||||
self.end();
|
||||
return false;
|
||||
}
|
||||
|
||||
n /= spaces;
|
||||
|
||||
md5.update(String.fromCharCode(
|
||||
n >> 24 & 0xFF,
|
||||
n >> 16 & 0xFF,
|
||||
n >> 8 & 0xFF,
|
||||
n & 0xFF));
|
||||
});
|
||||
|
||||
md5.update(this.req.head.toString('binary'));
|
||||
|
||||
try {
|
||||
this.socket.write(md5.digest('binary'), 'binary');
|
||||
} catch (e) {
|
||||
this.end();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes a payload.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.payload = function (msgs) {
|
||||
for (var i = 0, l = msgs.length; i < l; i++) {
|
||||
this.write(msgs[i]);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes the connection.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.doClose = function () {
|
||||
this.socket.end();
|
||||
};
|
||||
|
||||
/**
|
||||
* WebSocket parser
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Parser () {
|
||||
this.buffer = '';
|
||||
this.i = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from EventEmitter.
|
||||
*/
|
||||
|
||||
Parser.prototype.__proto__ = EventEmitter.prototype;
|
||||
|
||||
/**
|
||||
* Adds data to the buffer.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Parser.prototype.add = function (data) {
|
||||
this.buffer += data;
|
||||
this.parse();
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses the buffer.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.parse = function () {
|
||||
for (var i = this.i, chr, l = this.buffer.length; i < l; i++){
|
||||
chr = this.buffer[i];
|
||||
|
||||
if (this.buffer.length == 2 && this.buffer[1] == '\u0000') {
|
||||
this.emit('close');
|
||||
this.buffer = '';
|
||||
this.i = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (i === 0){
|
||||
if (chr != '\u0000')
|
||||
this.error('Bad framing. Expected null byte as first frame');
|
||||
else
|
||||
continue;
|
||||
}
|
||||
|
||||
if (chr == '\ufffd'){
|
||||
this.emit('data', this.buffer.substr(1, i - 1));
|
||||
this.buffer = this.buffer.substr(i + 1);
|
||||
this.i = 0;
|
||||
return this.parse();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles an error
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.error = function (reason) {
|
||||
this.buffer = '';
|
||||
this.i = 0;
|
||||
this.emit('error', reason);
|
||||
return this;
|
||||
return new protocolVersions['default'](mng, data, req);
|
||||
};
|
||||
|
||||
350
lib/transports/websocket/default.js
Normal file
350
lib/transports/websocket/default.js
Normal file
@@ -0,0 +1,350 @@
|
||||
|
||||
/*!
|
||||
* socket.io-node
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module requirements.
|
||||
*/
|
||||
|
||||
var Transport = require('../../transport')
|
||||
, EventEmitter = process.EventEmitter
|
||||
, crypto = require('crypto')
|
||||
, parser = require('../../parser');
|
||||
|
||||
/**
|
||||
* Export the constructor.
|
||||
*/
|
||||
|
||||
exports = module.exports = WebSocket;
|
||||
|
||||
/**
|
||||
* HTTP interface constructor. Interface compatible with all transports that
|
||||
* depend on request-response cycles.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function WebSocket (mng, data, req) {
|
||||
// parser
|
||||
var self = this;
|
||||
|
||||
this.parser = new Parser();
|
||||
this.parser.on('data', function (packet) {
|
||||
self.log.debug(self.name + ' received data packet', packet);
|
||||
self.onMessage(parser.decodePacket(packet));
|
||||
});
|
||||
this.parser.on('close', function () {
|
||||
self.end();
|
||||
});
|
||||
this.parser.on('error', function () {
|
||||
self.end();
|
||||
});
|
||||
|
||||
Transport.call(this, mng, data, req);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from Transport.
|
||||
*/
|
||||
|
||||
WebSocket.prototype.__proto__ = Transport.prototype;
|
||||
|
||||
/**
|
||||
* Transport name
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
WebSocket.prototype.name = 'websocket';
|
||||
|
||||
/**
|
||||
* Called when the socket connects.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.onSocketConnect = function () {
|
||||
var self = this;
|
||||
|
||||
this.socket.setNoDelay(true);
|
||||
|
||||
this.buffer = true;
|
||||
this.buffered = [];
|
||||
|
||||
if (this.req.headers.upgrade !== 'WebSocket') {
|
||||
this.log.warn(this.name + ' connection invalid');
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var origin = this.req.headers.origin
|
||||
, location = (this.socket.encrypted ? 'wss' : 'ws')
|
||||
+ '://' + this.req.headers.host + this.req.url
|
||||
, waitingForNonce = false;
|
||||
|
||||
if (this.req.headers['sec-websocket-key1']) {
|
||||
// If we don't have the nonce yet, wait for it (HAProxy compatibility).
|
||||
if (! (this.req.head && this.req.head.length >= 8)) {
|
||||
waitingForNonce = true;
|
||||
}
|
||||
|
||||
var headers = [
|
||||
'HTTP/1.1 101 WebSocket Protocol Handshake'
|
||||
, 'Upgrade: WebSocket'
|
||||
, 'Connection: Upgrade'
|
||||
, 'Sec-WebSocket-Origin: ' + origin
|
||||
, 'Sec-WebSocket-Location: ' + location
|
||||
];
|
||||
|
||||
if (this.req.headers['sec-websocket-protocol']){
|
||||
headers.push('Sec-WebSocket-Protocol: '
|
||||
+ this.req.headers['sec-websocket-protocol']);
|
||||
}
|
||||
} else {
|
||||
var headers = [
|
||||
'HTTP/1.1 101 Web Socket Protocol Handshake'
|
||||
, 'Upgrade: WebSocket'
|
||||
, 'Connection: Upgrade'
|
||||
, 'WebSocket-Origin: ' + origin
|
||||
, 'WebSocket-Location: ' + location
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
this.socket.write(headers.concat('', '').join('\r\n'));
|
||||
this.socket.setTimeout(0);
|
||||
this.socket.setNoDelay(true);
|
||||
this.socket.setEncoding('utf8');
|
||||
} catch (e) {
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (waitingForNonce) {
|
||||
this.socket.setEncoding('binary');
|
||||
} else if (this.proveReception(headers)) {
|
||||
self.flush();
|
||||
}
|
||||
|
||||
var headBuffer = '';
|
||||
|
||||
this.socket.on('data', function (data) {
|
||||
if (waitingForNonce) {
|
||||
headBuffer += data;
|
||||
|
||||
if (headBuffer.length < 8) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Restore the connection to utf8 encoding after receiving the nonce
|
||||
self.socket.setEncoding('utf8');
|
||||
waitingForNonce = false;
|
||||
|
||||
// Stuff the nonce into the location where it's expected to be
|
||||
self.req.head = headBuffer.substr(0, 8);
|
||||
headBuffer = '';
|
||||
|
||||
if (self.proveReception(headers)) {
|
||||
self.flush();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
self.parser.add(data);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes to the socket.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.write = function (data) {
|
||||
if (this.open) {
|
||||
this.drained = false;
|
||||
|
||||
if (this.buffer) {
|
||||
this.buffered.push(data);
|
||||
return this;
|
||||
}
|
||||
|
||||
var length = Buffer.byteLength(data)
|
||||
, buffer = new Buffer(2 + length);
|
||||
|
||||
buffer.write('\x00', 'binary');
|
||||
buffer.write(data, 1, 'utf8');
|
||||
buffer.write('\xff', 1 + length, 'binary');
|
||||
|
||||
try {
|
||||
if (this.socket.write(buffer)) {
|
||||
this.drained = true;
|
||||
}
|
||||
} catch (e) {
|
||||
this.end();
|
||||
}
|
||||
|
||||
this.log.debug(this.name + ' writing', data);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Flushes the internal buffer
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.flush = function () {
|
||||
this.buffer = false;
|
||||
|
||||
for (var i = 0, l = this.buffered.length; i < l; i++) {
|
||||
this.write(this.buffered.splice(0, 1)[0]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Finishes the handshake.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.proveReception = function (headers) {
|
||||
var self = this
|
||||
, k1 = this.req.headers['sec-websocket-key1']
|
||||
, k2 = this.req.headers['sec-websocket-key2'];
|
||||
|
||||
if (k1 && k2){
|
||||
var md5 = crypto.createHash('md5');
|
||||
|
||||
[k1, k2].forEach(function (k) {
|
||||
var n = parseInt(k.replace(/[^\d]/g, ''))
|
||||
, spaces = k.replace(/[^ ]/g, '').length;
|
||||
|
||||
if (spaces === 0 || n % spaces !== 0){
|
||||
self.log.warn('Invalid ' + self.name + ' key: "' + k + '".');
|
||||
self.end();
|
||||
return false;
|
||||
}
|
||||
|
||||
n /= spaces;
|
||||
|
||||
md5.update(String.fromCharCode(
|
||||
n >> 24 & 0xFF,
|
||||
n >> 16 & 0xFF,
|
||||
n >> 8 & 0xFF,
|
||||
n & 0xFF));
|
||||
});
|
||||
|
||||
md5.update(this.req.head.toString('binary'));
|
||||
|
||||
try {
|
||||
this.socket.write(md5.digest('binary'), 'binary');
|
||||
} catch (e) {
|
||||
this.end();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes a payload.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.payload = function (msgs) {
|
||||
for (var i = 0, l = msgs.length; i < l; i++) {
|
||||
this.write(msgs[i]);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes the connection.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.doClose = function () {
|
||||
this.socket.end();
|
||||
};
|
||||
|
||||
/**
|
||||
* WebSocket parser
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Parser () {
|
||||
this.buffer = '';
|
||||
this.i = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from EventEmitter.
|
||||
*/
|
||||
|
||||
Parser.prototype.__proto__ = EventEmitter.prototype;
|
||||
|
||||
/**
|
||||
* Adds data to the buffer.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Parser.prototype.add = function (data) {
|
||||
this.buffer += data;
|
||||
this.parse();
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses the buffer.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.parse = function () {
|
||||
for (var i = this.i, chr, l = this.buffer.length; i < l; i++){
|
||||
chr = this.buffer[i];
|
||||
|
||||
if (this.buffer.length == 2 && this.buffer[1] == '\u0000') {
|
||||
this.emit('close');
|
||||
this.buffer = '';
|
||||
this.i = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (i === 0){
|
||||
if (chr != '\u0000')
|
||||
this.error('Bad framing. Expected null byte as first frame');
|
||||
else
|
||||
continue;
|
||||
}
|
||||
|
||||
if (chr == '\ufffd'){
|
||||
this.emit('data', this.buffer.substr(1, i - 1));
|
||||
this.buffer = this.buffer.substr(i + 1);
|
||||
this.i = 0;
|
||||
return this.parse();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles an error
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.error = function (reason) {
|
||||
this.buffer = '';
|
||||
this.i = 0;
|
||||
this.emit('error', reason);
|
||||
return this;
|
||||
};
|
||||
465
lib/transports/websocket/hybi-07-12.js
Normal file
465
lib/transports/websocket/hybi-07-12.js
Normal file
@@ -0,0 +1,465 @@
|
||||
|
||||
/*!
|
||||
* socket.io-node
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module requirements.
|
||||
*/
|
||||
|
||||
var Transport = require('../../transport')
|
||||
, EventEmitter = process.EventEmitter
|
||||
, crypto = require('crypto')
|
||||
, parser = require('../../parser')
|
||||
, util = require('../../util');
|
||||
|
||||
/**
|
||||
* Export the constructor.
|
||||
*/
|
||||
|
||||
exports = module.exports = WebSocket;
|
||||
exports.Parser = Parser;
|
||||
|
||||
/**
|
||||
* HTTP interface constructor. Interface compatible with all transports that
|
||||
* depend on request-response cycles.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function WebSocket (mng, data, req) {
|
||||
// parser
|
||||
var self = this;
|
||||
|
||||
this.parser = new Parser();
|
||||
this.parser.on('data', function (packet) {
|
||||
self.onMessage(parser.decodePacket(packet));
|
||||
});
|
||||
this.parser.on('ping', function () {
|
||||
// version 8 ping => pong
|
||||
self.socket.write('\u008a\u0000');
|
||||
});
|
||||
this.parser.on('close', function () {
|
||||
self.end();
|
||||
});
|
||||
this.parser.on('error', function (reason) {
|
||||
self.log.warn(self.name + ' parser error: ' + reason);
|
||||
self.end();
|
||||
});
|
||||
|
||||
Transport.call(this, mng, data, req);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from Transport.
|
||||
*/
|
||||
|
||||
WebSocket.prototype.__proto__ = Transport.prototype;
|
||||
|
||||
/**
|
||||
* Transport name
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
WebSocket.prototype.name = 'websocket';
|
||||
|
||||
/**
|
||||
* Called when the socket connects.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.onSocketConnect = function () {
|
||||
var self = this;
|
||||
|
||||
if (this.req.headers.upgrade !== 'websocket') {
|
||||
this.log.warn(this.name + ' connection invalid');
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var origin = this.req.headers.origin
|
||||
, location = (this.socket.encrypted ? 'wss' : 'ws')
|
||||
+ '://' + this.req.headers.host + this.req.url;
|
||||
|
||||
if (!this.req.headers['sec-websocket-key']) {
|
||||
this.log.warn(this.name + ' connection invalid: received no key');
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// calc key
|
||||
var key = this.req.headers['sec-websocket-key'];
|
||||
var shasum = crypto.createHash('sha1');
|
||||
shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
|
||||
key = shasum.digest('base64');
|
||||
|
||||
var headers = [
|
||||
'HTTP/1.1 101 Switching Protocols'
|
||||
, 'Upgrade: websocket'
|
||||
, 'Connection: Upgrade'
|
||||
, 'Sec-WebSocket-Accept: ' + key
|
||||
];
|
||||
|
||||
try {
|
||||
this.socket.write(headers.concat('', '').join('\r\n'));
|
||||
this.socket.setTimeout(0);
|
||||
this.socket.setNoDelay(true);
|
||||
} catch (e) {
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
this.socket.on('data', function (data) {
|
||||
self.parser.add(data);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes to the socket.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.write = function (data) {
|
||||
if (this.open) {
|
||||
var buf = this.frame(0x81, data);
|
||||
this.socket.write(buf, 'binary');
|
||||
this.log.debug(this.name + ' writing', data);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Frame server-to-client output as a text packet.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.frame = function (opcode, str) {
|
||||
var dataBuffer = new Buffer(str)
|
||||
, dataLength = dataBuffer.length
|
||||
, startOffset = 2
|
||||
, secondByte = dataLength;
|
||||
if (dataLength > 65536) {
|
||||
startOffset = 10;
|
||||
secondByte = 127;
|
||||
}
|
||||
else if (dataLength > 125) {
|
||||
startOffset = 4;
|
||||
secondByte = 126;
|
||||
}
|
||||
var outputBuffer = new Buffer(dataLength + startOffset);
|
||||
outputBuffer[0] = opcode;
|
||||
outputBuffer[1] = secondByte;
|
||||
dataBuffer.copy(outputBuffer, startOffset);
|
||||
switch (secondByte) {
|
||||
case 126:
|
||||
outputBuffer[2] = dataLength >>> 8;
|
||||
outputBuffer[3] = dataLength % 256;
|
||||
break;
|
||||
case 127:
|
||||
var l = dataLength;
|
||||
for (var i = 1; i <= 8; ++i) {
|
||||
outputBuffer[startOffset - i] = l & 0xff;
|
||||
l >>>= 8;
|
||||
}
|
||||
}
|
||||
return outputBuffer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes the connection.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.doClose = function () {
|
||||
this.socket.end();
|
||||
};
|
||||
|
||||
/**
|
||||
* WebSocket parser
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Parser () {
|
||||
this.state = {
|
||||
activeFragmentedOperation: null,
|
||||
lastFragment: false,
|
||||
masked: false,
|
||||
opcode: 0
|
||||
};
|
||||
this.overflow = null;
|
||||
this.expectOffset = 0;
|
||||
this.expectBuffer = null;
|
||||
this.expectHandler = null;
|
||||
this.currentMessage = '';
|
||||
|
||||
var self = this;
|
||||
this.opcodeHandlers = {
|
||||
// text
|
||||
'1': function(data) {
|
||||
var finish = function(mask, data) {
|
||||
self.currentMessage += self.unmask(mask, data);
|
||||
if (self.state.lastFragment) {
|
||||
self.emit('data', self.currentMessage);
|
||||
self.currentMessage = '';
|
||||
}
|
||||
self.endPacket();
|
||||
}
|
||||
|
||||
var expectData = function(length) {
|
||||
if (self.state.masked) {
|
||||
self.expect('Mask', 4, function(data) {
|
||||
var mask = data;
|
||||
self.expect('Data', length, function(data) {
|
||||
finish(mask, data);
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
self.expect('Data', length, function(data) {
|
||||
finish(null, data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// decode length
|
||||
var firstLength = data[1] & 0x7f;
|
||||
if (firstLength < 126) {
|
||||
expectData(firstLength);
|
||||
}
|
||||
else if (firstLength == 126) {
|
||||
self.expect('Length', 2, function(data) {
|
||||
expectData(util.unpack(data));
|
||||
});
|
||||
}
|
||||
else if (firstLength == 127) {
|
||||
self.expect('Length', 8, function(data) {
|
||||
if (util.unpack(data.slice(0, 4)) != 0) {
|
||||
self.error('packets with length spanning more than 32 bit is currently not supported');
|
||||
return;
|
||||
}
|
||||
var lengthBytes = data.slice(4); // note: cap to 32 bit length
|
||||
expectData(util.unpack(data));
|
||||
});
|
||||
}
|
||||
},
|
||||
// close
|
||||
'8': function(data) {
|
||||
self.emit('close');
|
||||
self.reset();
|
||||
},
|
||||
// ping
|
||||
'9': function(data) {
|
||||
if (self.state.lastFragment == false) {
|
||||
self.error('fragmented ping is not supported');
|
||||
return;
|
||||
}
|
||||
|
||||
var finish = function(mask, data) {
|
||||
self.emit('ping', self.unmask(mask, data));
|
||||
self.endPacket();
|
||||
}
|
||||
|
||||
var expectData = function(length) {
|
||||
if (self.state.masked) {
|
||||
self.expect('Mask', 4, function(data) {
|
||||
var mask = data;
|
||||
self.expect('Data', length, function(data) {
|
||||
finish(mask, data);
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
self.expect('Data', length, function(data) {
|
||||
finish(null, data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// decode length
|
||||
var firstLength = data[1] & 0x7f;
|
||||
if (firstLength == 0) {
|
||||
finish(null, null);
|
||||
}
|
||||
else if (firstLength < 126) {
|
||||
expectData(firstLength);
|
||||
}
|
||||
else if (firstLength == 126) {
|
||||
self.expect('Length', 2, function(data) {
|
||||
expectData(util.unpack(data));
|
||||
});
|
||||
}
|
||||
else if (firstLength == 127) {
|
||||
self.expect('Length', 8, function(data) {
|
||||
expectData(util.unpack(data));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.expect('Opcode', 2, this.processPacket);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from EventEmitter.
|
||||
*/
|
||||
|
||||
Parser.prototype.__proto__ = EventEmitter.prototype;
|
||||
|
||||
/**
|
||||
* Add new data to the parser.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Parser.prototype.add = function(data) {
|
||||
if (this.expectBuffer == null) {
|
||||
this.addToOverflow(data);
|
||||
return;
|
||||
}
|
||||
var toRead = Math.min(data.length, this.expectBuffer.length - this.expectOffset);
|
||||
data.copy(this.expectBuffer, this.expectOffset, 0, toRead);
|
||||
this.expectOffset += toRead;
|
||||
if (toRead < data.length) {
|
||||
// at this point the overflow buffer shouldn't at all exist
|
||||
this.overflow = new Buffer(data.length - toRead);
|
||||
data.copy(this.overflow, 0, toRead, toRead + this.overflow.length);
|
||||
}
|
||||
if (this.expectOffset == this.expectBuffer.length) {
|
||||
var bufferForHandler = this.expectBuffer;
|
||||
this.expectBuffer = null;
|
||||
this.expectOffset = 0;
|
||||
this.expectHandler.call(this, bufferForHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a piece of data to the overflow.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.addToOverflow = function(data) {
|
||||
if (this.overflow == null) this.overflow = data;
|
||||
else {
|
||||
var prevOverflow = this.overflow;
|
||||
this.overflow = new Buffer(this.overflow.length + data.length);
|
||||
prevOverflow.copy(this.overflow, 0);
|
||||
data.copy(this.overflow, prevOverflow.length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for a certain amount of bytes to be available, then fires a callback.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.expect = function(what, length, handler) {
|
||||
this.expectBuffer = new Buffer(length);
|
||||
this.expectOffset = 0;
|
||||
this.expectHandler = handler;
|
||||
if (this.overflow != null) {
|
||||
var toOverflow = this.overflow;
|
||||
this.overflow = null;
|
||||
this.add(toOverflow);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start processing a new packet.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.processPacket = function (data) {
|
||||
if ((data[0] & 0x70) != 0) this.error('reserved fields not empty');
|
||||
this.state.lastFragment = (data[0] & 0x80) == 0x80;
|
||||
this.state.masked = (data[1] & 0x80) == 0x80;
|
||||
var opcode = data[0] & 0xf;
|
||||
if (opcode == 0) {
|
||||
// continuation frame
|
||||
if (this.state.opcode != 1 || this.state.opcode != 2) {
|
||||
this.error('continuation frame cannot follow current opcode')
|
||||
return;
|
||||
}
|
||||
}
|
||||
else this.state.opcode = opcode;
|
||||
this.state.opcode = data[0] & 0xf;
|
||||
var handler = this.opcodeHandlers[this.state.opcode];
|
||||
if (typeof handler == 'undefined') this.error('no handler for opcode ' + this.state.opcode);
|
||||
else handler(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Endprocessing a packet.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.endPacket = function() {
|
||||
this.expectOffset = 0;
|
||||
this.expectBuffer = null;
|
||||
this.expectHandler = null;
|
||||
if (this.state.lastFragment && this.state.opcode == this.state.activeFragmentedOperation) {
|
||||
// end current fragmented operation
|
||||
this.state.activeFragmentedOperation = null;
|
||||
}
|
||||
this.state.lastFragment = false;
|
||||
this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0;
|
||||
this.state.masked = false;
|
||||
this.expect('Opcode', 2, this.processPacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the parser state.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.reset = function() {
|
||||
this.state = {
|
||||
activeFragmentedOperation: null,
|
||||
lastFragment: false,
|
||||
masked: false,
|
||||
opcode: 0
|
||||
};
|
||||
this.expectOffset = 0;
|
||||
this.expectBuffer = null;
|
||||
this.expectHandler = null;
|
||||
this.overflow = null;
|
||||
this.currentMessage = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmask received data.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.unmask = function (mask, buf) {
|
||||
if (mask != null) {
|
||||
for (var i = 0, ll = buf.length; i < ll; i++) {
|
||||
buf[i] ^= mask[i % 4];
|
||||
}
|
||||
}
|
||||
return buf != null ? buf.toString('utf8') : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an error
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.error = function (reason) {
|
||||
this.reset();
|
||||
this.emit('error', reason);
|
||||
return this;
|
||||
};
|
||||
10
lib/transports/websocket/index.js
Normal file
10
lib/transports/websocket/index.js
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
/**
|
||||
* Export websocket versions.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
7: require('./hybi-07-12'),
|
||||
8: require('./hybi-07-12'),
|
||||
default: require('./default')
|
||||
};
|
||||
25
lib/util.js
25
lib/util.js
@@ -23,3 +23,28 @@ exports.toArray = function (enu) {
|
||||
|
||||
return arr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unpacks a buffer to a number.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.unpack = function (buffer) {
|
||||
var n = 0;
|
||||
for (var i = 0; i < buffer.length; ++i) {
|
||||
n = (i == 0) ? buffer[i] : (n * 256) + buffer[i];
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Left pads a string.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.padl = function (s,n,c) {
|
||||
return new Array(1 + n - s.length).join(c) + s;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "socket.io"
|
||||
, "version": "0.7.11"
|
||||
, "version": "0.8.4"
|
||||
, "description": "Real-time apps made cross-browser & easy with a WebSocket-like API"
|
||||
, "homepage": "http://socket.io"
|
||||
, "keywords": ["websocket", "socket", "realtime", "socket.io", "comet", "ajax"]
|
||||
@@ -9,13 +9,14 @@
|
||||
{ "name": "Guillermo Rauch", "email": "rauchg@gmail.com" }
|
||||
, { "name": "Arnout Kazemier", "email": "info@3rd-eden.com" }
|
||||
, { "name": "Vladimir Dronnikov", "email": "dronnikov@gmail.com" }
|
||||
, { "name": "Einar Otto Stangvik", "email": "einaros@gmail.com" }
|
||||
]
|
||||
, "repository":{
|
||||
"type": "git"
|
||||
, "url": "https://github.com/LearnBoost/Socket.IO-node.git"
|
||||
, "url": "https://github.com/LearnBoost/socket.io.git"
|
||||
}
|
||||
, "dependencies": {
|
||||
"socket.io-client": "0.7.11"
|
||||
"socket.io-client": "0.8.4"
|
||||
, "policyfile": "0.0.4"
|
||||
, "redis": "0.6.6"
|
||||
}
|
||||
|
||||
@@ -472,6 +472,91 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
|
||||
'test that a referer is accepted for *:* origin': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
io.configure(function () {
|
||||
io.set('origins', '*:*');
|
||||
});
|
||||
|
||||
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com:82/something' } }, function (res, data) {
|
||||
res.statusCode.should.eql(200);
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test that valid referer is accepted for addr:* origin': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
io.configure(function () {
|
||||
io.set('origins', 'foo.bar.com:*');
|
||||
});
|
||||
|
||||
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com/something' } }, function (res, data) {
|
||||
res.statusCode.should.eql(200);
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test that erroneous referer is denied for addr:* origin': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
io.configure(function () {
|
||||
io.set('origins', 'foo.bar.com:*');
|
||||
});
|
||||
|
||||
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://baz.bar.com/something' } }, function (res, data) {
|
||||
res.statusCode.should.eql(403);
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test that valid referer port is accepted for addr:port origin': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
io.configure(function () {
|
||||
io.set('origins', 'foo.bar.com:81');
|
||||
});
|
||||
|
||||
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com:81/something' } }, function (res, data) {
|
||||
res.statusCode.should.eql(200);
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test that erroneous referer port is denied for addr:port origin': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
io.configure(function () {
|
||||
io.set('origins', 'foo.bar.com:81');
|
||||
});
|
||||
|
||||
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com/something' } }, function (res, data) {
|
||||
res.statusCode.should.eql(403);
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test limiting the supported transports for a manager': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
|
||||
@@ -343,6 +343,14 @@ module.exports = {
|
||||
parser.encodePacket({ type: 'message', data: '5', endpoint: '' })
|
||||
, parser.encodePacket({ type: 'message', data: '53d', endpoint: '' })
|
||||
]).should.eql('\ufffd5\ufffd3:::5\ufffd7\ufffd3:::53d')
|
||||
},
|
||||
|
||||
'test decoding newline': function () {
|
||||
parser.decodePacket('3:::\n').should.eql({
|
||||
type: 'message'
|
||||
, endpoint: ''
|
||||
, data: '\n'
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -77,7 +77,7 @@ module.exports = {
|
||||
var io = sio.listen(http.createServer())
|
||||
, port = ++ports;
|
||||
|
||||
io.get('flash policy port').should.eql(843);
|
||||
io.get('flash policy port').should.eql(10843);
|
||||
io.set('flash policy port', port);
|
||||
io.get('flash policy port').should.eql(port);
|
||||
|
||||
|
||||
271
test/transports.websocket.hybi07-12.parser.test.js
Normal file
271
test/transports.websocket.hybi07-12.parser.test.js
Normal file
@@ -0,0 +1,271 @@
|
||||
/**
|
||||
* Test dependencies.
|
||||
*/
|
||||
|
||||
var assert = require('assert');
|
||||
var Parser = require('../lib/transports/websocket/hybi-07-12.js').Parser;
|
||||
|
||||
/**
|
||||
* Returns a Buffer from a "ff 00 ff"-type hex string.
|
||||
*/
|
||||
|
||||
function makeBufferFromHexString(byteStr) {
|
||||
var bytes = byteStr.split(' ');
|
||||
var buf = new Buffer(bytes.length);
|
||||
for (var i = 0; i < bytes.length; ++i) {
|
||||
buf[i] = parseInt(bytes[i], 16);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a buffer in two parts.
|
||||
*/
|
||||
|
||||
function splitBuffer(buffer) {
|
||||
var b1 = new Buffer(Math.ceil(buffer.length / 2));
|
||||
buffer.copy(b1, 0, 0, b1.length);
|
||||
var b2 = new Buffer(Math.floor(buffer.length / 2));
|
||||
buffer.copy(b2, 0, b1.length, b1.length + b2.length);
|
||||
return [b1, b2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs hybi07+ type masking on a hex string.
|
||||
*/
|
||||
|
||||
function mask(str, maskString) {
|
||||
var buf = new Buffer(str);
|
||||
var mask = makeBufferFromHexString(maskString || '34 83 a8 68');
|
||||
for (var i = 0; i < buf.length; ++i) {
|
||||
buf[i] ^= mask[i % 4];
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpacks a Buffer into a number.
|
||||
*/
|
||||
|
||||
function unpack(buffer) {
|
||||
var n = 0;
|
||||
for (var i = 0; i < buffer.length; ++i) {
|
||||
n = (i == 0) ? buffer[i] : (n * 256) + buffer[i];
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hex string, representing a specific byte count 'length', from a number.
|
||||
*/
|
||||
|
||||
function pack(length, number) {
|
||||
return padl(number.toString(16), length, '0').replace(/(\d\d)/g, '$1 ').trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Left pads the string 's' to a total length of 'n' with char 'c'.
|
||||
*/
|
||||
|
||||
function padl(s, n, c) {
|
||||
return new Array(1 + n - s.length).join(c) + s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hex string from a Buffer.
|
||||
*/
|
||||
|
||||
function dump(data) {
|
||||
var s = '';
|
||||
for (var i = 0; i < data.length; ++i) {
|
||||
s += padl(data[i].toString(16), 2, '0') + ' ';
|
||||
}
|
||||
return s.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
'can parse unmasked text message': function() {
|
||||
var p = new Parser();
|
||||
var packet = '81 05 48 65 6c 6c 6f';
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal('Hello', data);
|
||||
});
|
||||
|
||||
p.add(makeBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse close message': function() {
|
||||
var p = new Parser();
|
||||
var packet = '88 00';
|
||||
|
||||
var gotClose = false;
|
||||
p.on('close', function(data) {
|
||||
gotClose = true;
|
||||
});
|
||||
|
||||
p.add(makeBufferFromHexString(packet));
|
||||
assert.ok(gotClose);
|
||||
},
|
||||
'can parse masked text message': function() {
|
||||
var p = new Parser();
|
||||
var packet = '81 93 34 83 a8 68 01 b9 92 52 4f a1 c6 09 59 e6 8a 52 16 e6 cb 00 5b a1 d5';
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal('5:::{"name":"echo"}', data);
|
||||
});
|
||||
|
||||
p.add(makeBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse a masked text message longer than 125 bytes': function() {
|
||||
var p = new Parser();
|
||||
var message = 'A';
|
||||
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
|
||||
var packet = '81 FE ' + pack(4, message.length) + ' 34 83 a8 68 ' + dump(mask(message, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
|
||||
p.add(makeBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse a really long masked text message': function() {
|
||||
var p = new Parser();
|
||||
var message = 'A';
|
||||
for (var i = 0; i < 64*1024; ++i) message += (i % 5).toString();
|
||||
var packet = '81 FF ' + pack(16, message.length) + ' 34 83 a8 68 ' + dump(mask(message, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
|
||||
p.add(makeBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse a fragmented masked text message of 300 bytes': function() {
|
||||
var p = new Parser();
|
||||
var message = 'A';
|
||||
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
|
||||
var msgpiece1 = message.substr(0, 150);
|
||||
var msgpiece2 = message.substr(150);
|
||||
var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece1, '34 83 a8 68'));
|
||||
var packet2 = '81 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece2, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
|
||||
p.add(makeBufferFromHexString(packet1));
|
||||
p.add(makeBufferFromHexString(packet2));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse a ping message': function() {
|
||||
var p = new Parser();
|
||||
var message = 'Hello';
|
||||
var packet = '89 FE ' + pack(4, message.length) + ' 34 83 a8 68 ' + dump(mask(message, '34 83 a8 68'));
|
||||
|
||||
var gotPing = false;
|
||||
p.on('ping', function(data) {
|
||||
gotPing = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
|
||||
p.add(makeBufferFromHexString(packet));
|
||||
assert.ok(gotPing);
|
||||
},
|
||||
'can parse a ping with no data': function() {
|
||||
var p = new Parser();
|
||||
var packet = '89 00';
|
||||
|
||||
var gotPing = false;
|
||||
p.on('ping', function(data) {
|
||||
gotPing = true;
|
||||
});
|
||||
|
||||
p.add(makeBufferFromHexString(packet));
|
||||
assert.ok(gotPing);
|
||||
},
|
||||
'can parse a fragmented masked text message of 300 bytes with a ping in the middle': function() {
|
||||
var p = new Parser();
|
||||
var message = 'A';
|
||||
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
|
||||
|
||||
var msgpiece1 = message.substr(0, 150);
|
||||
var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece1, '34 83 a8 68'));
|
||||
|
||||
var pingMessage = 'Hello';
|
||||
var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + dump(mask(pingMessage, '34 83 a8 68'));
|
||||
|
||||
var msgpiece2 = message.substr(150);
|
||||
var packet2 = '81 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece2, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
var gotPing = false;
|
||||
p.on('ping', function(data) {
|
||||
gotPing = true;
|
||||
assert.equal(pingMessage, data);
|
||||
});
|
||||
|
||||
p.add(makeBufferFromHexString(packet1));
|
||||
p.add(makeBufferFromHexString(pingPacket));
|
||||
p.add(makeBufferFromHexString(packet2));
|
||||
assert.ok(gotData);
|
||||
assert.ok(gotPing);
|
||||
},
|
||||
'can parse a fragmented masked text message of 300 bytes with a ping in the middle, which is delievered over sevaral tcp packets': function() {
|
||||
var p = new Parser();
|
||||
var message = 'A';
|
||||
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
|
||||
|
||||
var msgpiece1 = message.substr(0, 150);
|
||||
var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece1, '34 83 a8 68'));
|
||||
|
||||
var pingMessage = 'Hello';
|
||||
var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + dump(mask(pingMessage, '34 83 a8 68'));
|
||||
|
||||
var msgpiece2 = message.substr(150);
|
||||
var packet2 = '81 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece2, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
var gotPing = false;
|
||||
p.on('ping', function(data) {
|
||||
gotPing = true;
|
||||
assert.equal(pingMessage, data);
|
||||
});
|
||||
|
||||
var buffers = [];
|
||||
buffers = buffers.concat(splitBuffer(makeBufferFromHexString(packet1)));
|
||||
buffers = buffers.concat(splitBuffer(makeBufferFromHexString(pingPacket)));
|
||||
buffers = buffers.concat(splitBuffer(makeBufferFromHexString(packet2)));
|
||||
for (var i = 0; i < buffers.length; ++i) {
|
||||
p.add(buffers[i]);
|
||||
}
|
||||
assert.ok(gotData);
|
||||
assert.ok(gotPing);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -950,6 +950,44 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
|
||||
'test that emitting an error event doesnt throw': function (done) {
|
||||
var cl = client(++ports)
|
||||
, io = create(cl)
|
||||
|
||||
io.configure(function () {
|
||||
io.set('polling duration', .05);
|
||||
io.set('close timeout', .05);
|
||||
});
|
||||
|
||||
io.sockets.on('connection', function (socket) {
|
||||
socket.on('disconnect', function () {
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
cl.handshake(function (sid) {
|
||||
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
|
||||
res.statusCode.should.equal(200);
|
||||
msgs.should.have.length(1);
|
||||
msgs[0].should.eql({ type: 'noop', endpoint: '' });
|
||||
|
||||
cl.post(
|
||||
'/socket.io/{protocol}/xhr-polling/' + sid
|
||||
, parser.encodePacket({
|
||||
type: 'event'
|
||||
, name: 'error'
|
||||
})
|
||||
, function (res, data) {
|
||||
res.statusCode.should.eql(200);
|
||||
data.should.equal('1');
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
'test emitting an event to the server with data': function (done) {
|
||||
var cl = client(++ports)
|
||||
, io = create(cl)
|
||||
|
||||
Reference in New Issue
Block a user