faye-websocket
Advanced tools
Comparing version 0.1.2 to 0.2.0
@@ -6,4 +6,3 @@ var WebSocket = require('../lib/faye/websocket'); | ||
cases = 0, | ||
skip = [247,248,249,250,251,252,253,254,255, | ||
256,257,258,259,260,261,262,263,264]; | ||
skip = []; | ||
@@ -10,0 +9,0 @@ var socket = new WebSocket.Client(host + '/getCaseCount'); |
@@ -28,4 +28,4 @@ var WebSocket = require('../lib/faye/websocket'), | ||
server.addListener('upgrade', function(request, socket, head) { | ||
var ws = new WebSocket(request, socket, head); | ||
console.log('open', ws.url, ws.version); | ||
var ws = new WebSocket(request, socket, head, ['irc', 'xmpp']); | ||
console.log('open', ws.url, ws.version, ws.protocol); | ||
@@ -32,0 +32,0 @@ ws.onmessage = function(event) { |
@@ -10,6 +10,6 @@ // API and protocol references: | ||
var Draft75Parser = require('./websocket/draft75_parser'), | ||
Draft76Parser = require('./websocket/draft76_parser'), | ||
Protocol8Parser = require('./websocket/protocol8_parser'), | ||
API = require('./websocket/api'); | ||
var Draft75Parser = require('./websocket/draft75_parser'), | ||
Draft76Parser = require('./websocket/draft76_parser'), | ||
HybiParser = require('./websocket/hybi_parser'), | ||
API = require('./websocket/api'); | ||
@@ -19,3 +19,3 @@ var getParser = function(request) { | ||
return headers['sec-websocket-version'] | ||
? Protocol8Parser | ||
? HybiParser | ||
: (headers['sec-websocket-key1'] && headers['sec-websocket-key2']) | ||
@@ -35,3 +35,3 @@ ? Draft76Parser | ||
var WebSocket = function(request, socket, head) { | ||
var WebSocket = function(request, socket, head, supportedProtos) { | ||
this.request = request; | ||
@@ -46,5 +46,8 @@ this._stream = request.socket; | ||
var Parser = getParser(request); | ||
this._parser = new Parser(this, this._stream); | ||
this._parser.handshakeResponse(head); | ||
this._parser = new Parser(this, {protocols: supportedProtos}); | ||
var handshake = this._parser.handshakeResponse(head); | ||
try { this._stream.write(handshake, 'binary') } catch (e) {} | ||
this.protocol = this._parser.protocol || ''; | ||
this.readyState = API.OPEN; | ||
@@ -60,3 +63,5 @@ this.version = this._parser.getVersion(); | ||
this._stream.addListener('data', function(data) { | ||
self._parser.parse(data); | ||
var response = self._parser.parse(data); | ||
if (!response) return; | ||
try { self._stream.write(response, 'binary') } catch (e) {} | ||
}); | ||
@@ -63,0 +68,0 @@ ['close', 'end', 'error'].forEach(function(event) { |
@@ -11,2 +11,3 @@ var API = { | ||
onclose: null, | ||
protocol: null, | ||
@@ -23,3 +24,9 @@ receive: function(data) { | ||
if (this.readyState === API.CLOSED) return false; | ||
return this._parser.frame(data, type, errorType); | ||
var frame = this._parser.frame(data, type, errorType); | ||
try { | ||
this._stream.write(frame, 'binary'); | ||
return true; | ||
} catch (e) { | ||
return false; | ||
} | ||
}, | ||
@@ -26,0 +33,0 @@ |
@@ -5,12 +5,13 @@ var API = require('./api'), | ||
var Protocol8Parser = require('./protocol8_parser'); | ||
var HybiParser = require('./hybi_parser'); | ||
var Client = function(url) { | ||
this.url = url; | ||
this.uri = require('url').parse(url); | ||
var Client = function(url, protocols) { | ||
this.url = url; | ||
this._uri = require('url').parse(url); | ||
this.protocol = ''; | ||
this.readyState = API.CONNECTING; | ||
this.bufferedAmount = 0; | ||
var secure = (this.uri.protocol === 'wss:'), | ||
var secure = (this._uri.protocol === 'wss:'), | ||
self = this, | ||
@@ -20,6 +21,6 @@ onConnect = function() { self._onConnect() }, | ||
connection = secure | ||
? tls.connect(this.uri.port || 443, this.uri.hostname, onConnect) | ||
: net.createConnection(this.uri.port || 80, this.uri.hostname); | ||
? tls.connect(this._uri.port || 443, this._uri.hostname, onConnect) | ||
: net.createConnection(this._uri.port || 80, this._uri.hostname); | ||
this._parser = new Protocol8Parser(this, connection, {masking: true}); | ||
this._parser = new HybiParser(this, {masking: true, protocols: protocols}); | ||
this._stream = connection; | ||
@@ -39,5 +40,7 @@ | ||
Client.prototype._onConnect = function() { | ||
this._handshake = this._parser.createHandshake(this.uri); | ||
this._handshake = this._parser.createHandshake(this._uri, this._stream); | ||
this._message = []; | ||
this._handshake.requestData(); | ||
try { | ||
this._stream.write(this._handshake.requestData(), 'binary'); | ||
} catch (e) {} | ||
}; | ||
@@ -55,2 +58,3 @@ | ||
if (this._handshake.isValid()) { | ||
this.protocol = this._handshake.protocol || ''; | ||
this.readyState = API.OPEN; | ||
@@ -65,3 +69,3 @@ var event = new API.Event('open'); | ||
this.readyState = API.CLOSED; | ||
var event = new API.Event('close'); | ||
var event = new API.Event('close', {code: 1006, reason: ''}); | ||
event.initEvent('close', false, false); | ||
@@ -68,0 +72,0 @@ this.dispatchEvent(event); |
@@ -1,60 +0,87 @@ | ||
var Draft75Parser = function(webSocket, stream) { | ||
this._socket = webSocket; | ||
this._stream = stream; | ||
this._buffer = []; | ||
this._buffering = false; | ||
var Draft75Parser = function(webSocket) { | ||
this._socket = webSocket; | ||
this._stage = 0; | ||
}; | ||
var instance = { | ||
FRAME_START : new Buffer([0x00]), | ||
FRAME_END : new Buffer([0xFF]), | ||
getVersion: function() { | ||
return 'draft-75'; | ||
return 'hixie-75'; | ||
}, | ||
handshakeResponse: function() { | ||
var stream = this._stream; | ||
try { | ||
stream.write( 'HTTP/1.1 101 Web Socket Protocol Handshake\r\n' + | ||
'Upgrade: WebSocket\r\n' + | ||
'Connection: Upgrade\r\n' + | ||
'WebSocket-Origin: ' + this._socket.request.headers.origin + '\r\n' + | ||
'WebSocket-Location: ' + this._socket.url + '\r\n\r\n'); | ||
} catch (e) {} | ||
return new Buffer('HTTP/1.1 101 Web Socket Protocol Handshake\r\n' + | ||
'Upgrade: WebSocket\r\n' + | ||
'Connection: Upgrade\r\n' + | ||
'WebSocket-Origin: ' + this._socket.request.headers.origin + '\r\n' + | ||
'WebSocket-Location: ' + this._socket.url + '\r\n\r\n', | ||
'utf8'); | ||
}, | ||
parse: function(data) { | ||
for (var i = 0, n = data.length; i < n; i++) | ||
this._handleChar(data[i]); | ||
parse: function(buffer) { | ||
var data, message, value; | ||
for (var i = 0, n = buffer.length; i < n; i++) { | ||
data = buffer[i]; | ||
switch (this._stage) { | ||
case 0: | ||
this._parseLeadingByte(data); | ||
break; | ||
case 1: | ||
value = (data & 0x7F); | ||
this._length = value + 128 * this._length; | ||
if (this._closing && this._length === 0) { | ||
this._socket.close(null, null, false); | ||
} | ||
else if ((0x80 & data) !== 0x80) { | ||
if (this._length === 0) { | ||
this._socket.receive(''); | ||
this._stage = 0; | ||
} | ||
else { | ||
this._buffer = []; | ||
this._stage = 2; | ||
} | ||
} | ||
break; | ||
case 2: | ||
if (data === 0xFF) { | ||
message = new Buffer(this._buffer); | ||
this._socket.receive(message.toString('utf8', 0, this._buffer.length)); | ||
this._stage = 0; | ||
} | ||
else { | ||
this._buffer.push(data); | ||
if (this._length && this._buffer.length === this._length) | ||
this._stage = 0; | ||
} | ||
break; | ||
} | ||
} | ||
}, | ||
frame: function(data) { | ||
var stream = this._stream; | ||
try { | ||
stream.write(this.FRAME_START, 'binary'); | ||
stream.write(new Buffer(data), 'utf8'); | ||
stream.write(this.FRAME_END, 'binary'); | ||
return true; | ||
} catch (e) { | ||
return false; | ||
_parseLeadingByte: function(data) { | ||
if ((0x80 & data) === 0x80) { | ||
this._length = 0; | ||
this._stage = 1; | ||
} else { | ||
delete this._length; | ||
this._buffer = []; | ||
this._stage = 2; | ||
} | ||
}, | ||
_handleChar: function(data) { | ||
switch (data) { | ||
case 0x00: | ||
this._buffering = true; | ||
break; | ||
case 0xFF: | ||
this._buffer = new Buffer(this._buffer); | ||
this._socket.receive(this._buffer.toString('utf8', 0, this._buffer.length)); | ||
this._buffer = []; | ||
this._buffering = false; | ||
break; | ||
default: | ||
if (this._buffering) this._buffer.push(data); | ||
} | ||
frame: function(data) { | ||
if (Buffer.isBuffer(data)) return data; | ||
var buffer = new Buffer(data, 'utf8'), | ||
frame = new Buffer(buffer.length + 2); | ||
frame[0] = 0x00; | ||
frame[buffer.length + 1] = 0xFF; | ||
buffer.copy(frame, 1); | ||
return frame; | ||
} | ||
@@ -61,0 +88,0 @@ }; |
@@ -26,27 +26,31 @@ var crypto = require('crypto'), | ||
Draft76Parser.prototype.getVersion = function() { | ||
return 'draft-76'; | ||
return 'hixie-76'; | ||
}; | ||
Draft76Parser.prototype.handshakeResponse = function(head) { | ||
var request = this._socket.request, | ||
stream = this._stream; | ||
var request = this._socket.request, tmp; | ||
try { | ||
stream.write( 'HTTP/1.1 101 Web Socket Protocol Handshake\r\n' + | ||
'Upgrade: WebSocket\r\n' + | ||
'Connection: Upgrade\r\n' + | ||
'Sec-WebSocket-Origin: ' + request.headers.origin + '\r\n' + | ||
'Sec-WebSocket-Location: ' + this._socket.url + '\r\n\r\n', | ||
'binary'); | ||
} catch (e) {} | ||
var response = new Buffer('HTTP/1.1 101 Web Socket Protocol Handshake\r\n' + | ||
'Upgrade: WebSocket\r\n' + | ||
'Connection: Upgrade\r\n' + | ||
'Sec-WebSocket-Origin: ' + request.headers.origin + '\r\n' + | ||
'Sec-WebSocket-Location: ' + this._socket.url + '\r\n\r\n', | ||
'binary'); | ||
this.handshakeSignature(head); | ||
var signature = this.handshakeSignature(head); | ||
if (signature) { | ||
tmp = new Buffer(response.length + signature.length); | ||
response.copy(tmp, 0); | ||
signature.copy(tmp, response.length); | ||
response = tmp; | ||
} | ||
return response; | ||
}; | ||
Draft76Parser.prototype.handshakeSignature = function(head) { | ||
if (head.length === 0) return; | ||
if (head.length === 0) return null; | ||
this._handshakeComplete = true; | ||
var request = this._socket.request, | ||
stream = this._stream, | ||
@@ -65,5 +69,3 @@ key1 = request.headers['sec-websocket-key1'], | ||
try { | ||
stream.write(MD5.digest('binary'), 'binary'); | ||
} catch (e) {}; | ||
return new Buffer(MD5.digest('binary'), 'binary'); | ||
}; | ||
@@ -75,6 +77,22 @@ | ||
this.handshakeSignature(data); | ||
return this.handshakeSignature(data); | ||
}; | ||
Draft76Parser.prototype._parseLeadingByte = function(data) { | ||
if (data !== 0xFF) | ||
return Draft75Parser.prototype._parseLeadingByte.call(this, data); | ||
this._closing = true; | ||
this._length = 0; | ||
this._stage = 1; | ||
}; | ||
Draft76Parser.prototype.close = function(code, reason, callback, context) { | ||
if (this._closed) return; | ||
if (this._closing) this._socket.send(new Buffer([0xFF, 0x00])); | ||
this._closed = true; | ||
if (callback) callback.call(context); | ||
}; | ||
module.exports = Draft76Parser; | ||
{ "name" : "faye-websocket" | ||
, "description" : "Robust general-purpose WebSocket server and client" | ||
, "description" : "Standards-compliant WebSocket server and client" | ||
, "homepage" : "http://github.com/jcoglan/faye-websocket-node" | ||
@@ -7,6 +7,6 @@ , "author" : "James Coglan <jcoglan@gmail.com> (http://jcoglan.com/)" | ||
, "version" : "0.1.2" | ||
, "version" : "0.2.0" | ||
, "engines" : {"node": ">=0.4.0"} | ||
, "main" : "./lib/faye/websocket" | ||
, "devDependencies" : {"jsclass": ">=3.0.4"} | ||
, "devDependencies" : {"jsclass": ""} | ||
@@ -13,0 +13,0 @@ , "bugs" : "http://github.com/jcoglan/faye-websocket-node/issues" |
@@ -16,3 +16,3 @@ var Client = require('../../../lib/faye/websocket/client') | ||
open_socket: function(url, callback) { | ||
open_socket: function(url, protocols, callback) { | ||
var done = false, | ||
@@ -28,3 +28,3 @@ self = this, | ||
this._ws = new Client(url) | ||
this._ws = new Client(url, protocols) | ||
@@ -54,2 +54,7 @@ this._ws.onopen = function() { resume(true) } | ||
check_protocol: function(protocol, callback) { | ||
this.assertEqual( protocol, this._ws.protocol ) | ||
callback() | ||
}, | ||
listen_for_message: function(callback) { | ||
@@ -82,2 +87,3 @@ var self = this | ||
before(function() { | ||
this.protocols = ["foo", "echo"] | ||
this.plain_text_url = "ws://localhost:8000/bayeux" | ||
@@ -89,13 +95,19 @@ this.secure_url = "wss://localhost:8000/bayeux" | ||
it("can open a connection", function() { with(this) { | ||
open_socket(socket_url) | ||
open_socket(socket_url, protocols) | ||
check_open() | ||
check_protocol("echo") | ||
}}) | ||
it("cannot open a connection to the wrong host", function() { with(this) { | ||
open_socket(blocked_url) | ||
open_socket(blocked_url, protocols) | ||
check_closed() | ||
}}) | ||
it("cannot open a connection with unacceptable protocols", function() { with(this) { | ||
open_socket(socket_url, ["foo"]) | ||
check_closed() | ||
}}) | ||
it("can close the connection", function() { with(this) { | ||
open_socket(socket_url) | ||
open_socket(socket_url, protocols) | ||
close_socket() | ||
@@ -107,3 +119,3 @@ check_closed() | ||
before(function() { with(this) { | ||
open_socket(socket_url) | ||
open_socket(socket_url, protocols) | ||
}}) | ||
@@ -120,3 +132,3 @@ | ||
before(function() { with(this) { | ||
open_socket(socket_url) | ||
open_socket(socket_url, protocols) | ||
close_socket() | ||
@@ -123,0 +135,0 @@ }}) |
@@ -6,21 +6,56 @@ var Draft75Parser = require('../../../lib/faye/websocket/draft75_parser') | ||
this.webSocket = {dispatchEvent: function() {}} | ||
this.socket = new FakeSocket | ||
this.parser = new Draft75Parser(webSocket, socket) | ||
this.parser = new Draft75Parser(webSocket) | ||
}}) | ||
describe("parse", function() { with(this) { | ||
it("parses text frames", function() { with(this) { | ||
expect(webSocket, "receive").given("Hello") | ||
parser.parse([0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]) | ||
sharedBehavior("draft-75 parser", function() { with(this) { | ||
it("parses text frames", function() { with(this) { | ||
expect(webSocket, "receive").given("Hello") | ||
parser.parse([0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]) | ||
}}) | ||
it("parses multiple frames from the same packet", function() { with(this) { | ||
expect(webSocket, "receive").given("Hello").exactly(2) | ||
parser.parse([0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]) | ||
}}) | ||
it("parses text frames beginning 0x00-0x7F", function() { with(this) { | ||
expect(webSocket, "receive").given("Hello") | ||
parser.parse([0x66, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]) | ||
}}) | ||
it("ignores frames with a length header", function() { with(this) { | ||
expect(webSocket, "receive").exactly(0) | ||
parser.parse([0x80, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]) | ||
}}) | ||
it("parses text following an ignored block", function() { with(this) { | ||
expect(webSocket, "receive").given("Hello") | ||
parser.parse([0x80, 0x02, 0x48, 0x65, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]) | ||
}}) | ||
it("parses multibyte text frames", function() { with(this) { | ||
expect(webSocket, "receive").given("Apple = ") | ||
parser.parse([0x00, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf, 0xff]) | ||
}}) | ||
it("parses frames received in several packets", function() { with(this) { | ||
expect(webSocket, "receive").given("Apple = ") | ||
parser.parse([0x00, 0x41, 0x70, 0x70, 0x6c, 0x65]) | ||
parser.parse([0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf, 0xff]) | ||
}}) | ||
it("parses fragmented frames", function() { with(this) { | ||
expect(webSocket, "receive").given("Hello") | ||
parser.parse([0x00, 0x48, 0x65, 0x6c]) | ||
parser.parse([0x6c, 0x6f, 0xff]) | ||
}}) | ||
}}) | ||
it("parses multibyte text frames", function() { with(this) { | ||
expect(webSocket, "receive").given("Apple = ") | ||
parser.parse([0x00, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf, 0xff]) | ||
}}) | ||
behavesLike("draft-75 parser") | ||
it("parses fragmented frames", function() { with(this) { | ||
expect(webSocket, "receive").given("Hello") | ||
parser.parse([0x00, 0x48, 0x65, 0x6c]) | ||
parser.parse([0x6c, 0x6f, 0xff]) | ||
it("does not close the socket if a 76 close frame is received", function() { with(this) { | ||
expect(webSocket, "close").exactly(0) | ||
expect(webSocket, "receive").given("") | ||
parser.parse([0xFF, 0x00]) | ||
}}) | ||
@@ -31,11 +66,9 @@ }}) | ||
it("returns the given string formatted as a WebSocket frame", function() { with(this) { | ||
parser.frame("Hello") | ||
assertEqual( [0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff], socket.read() ) | ||
assertBufferEqual( [0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff], parser.frame("Hello") ) | ||
}}) | ||
it("encodes multibyte characters correctly", function() { with(this) { | ||
parser.frame("Apple = ") | ||
assertEqual( [0x00, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf, 0xff], socket.read() ) | ||
assertBufferEqual( [0x00, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf, 0xff], parser.frame("Apple = ") ) | ||
}}) | ||
}}) | ||
}}) |
@@ -9,19 +9,2 @@ require('jsclass') | ||
JS.ENV.FakeSocket = function() { | ||
this._fragments = [] | ||
} | ||
FakeSocket.prototype.write = function(buffer, encoding) { | ||
this._fragments.push([buffer, encoding]) | ||
} | ||
FakeSocket.prototype.read = function() { | ||
var output = [] | ||
this._fragments.forEach(function(buffer, i) { | ||
for (var j = 0, n = buffer[0].length; j < n; j++) | ||
output.push(buffer[0][j]) | ||
}) | ||
return output | ||
} | ||
FakeSocket.prototype.addListener = function() {} | ||
JS.ENV.EchoServer = function() {} | ||
@@ -37,3 +20,3 @@ EchoServer.prototype.listen = function(port, ssl) { | ||
server.addListener('upgrade', function(request, socket, head) { | ||
var ws = new WebSocket(request, socket, head) | ||
var ws = new WebSocket(request, socket, head, ["echo"]) | ||
ws.onmessage = function(event) { | ||
@@ -60,7 +43,15 @@ ws.send(event.data) | ||
JS.require('JS.Test', function() { | ||
JS.Test.Unit.Assertions.define("assertBufferEqual", function(array, buffer) { | ||
this.assertEqual(array.length, buffer.length); | ||
var ary = [], n = buffer.length; | ||
while (n--) ary[n] = buffer[n]; | ||
this.assertEqual(array, ary); | ||
}) | ||
JS.require( 'ClientSpec', | ||
'Draft75ParserSpec', | ||
'Protocol8ParserSpec', | ||
'Draft76ParserSpec', | ||
'HybiParserSpec', | ||
JS.Test.method('autorun')) | ||
}) | ||
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 3 instances in 1 package
1224
145
7
59441
24