From e2408b42db01fdbc93103afa04a678ebf35f016d Mon Sep 17 00:00:00 2001 From: libccy Date: Tue, 29 Mar 2016 21:43:15 +0800 Subject: [PATCH] fix --- audio/skill/reyingzi1_sunce.mp3 | 7 + audio/skill/reyingzi2_sunce.mp3 | 7 + audio/skill/reyingzi_cunce1.mp3 | Bin 0 -> 66519 bytes audio/skill/reyingzi_sunce2.mp3 | Bin 0 -> 55877 bytes character/refresh.js | 1 + game/asset.js | 36 +- game/game.js | 14 +- game/server.js | 30 + game/update.js | 6 +- image/skin/caocao/1.jpg | Bin 0 -> 27996 bytes node_modules/options/.npmignore | 7 + node_modules/options/Makefile | 12 + node_modules/options/README.md | 69 ++ node_modules/options/lib/options.js | 86 ++ node_modules/options/package.json | 51 ++ node_modules/ultron/.npmignore | 3 + node_modules/ultron/.travis.yml | 21 + node_modules/ultron/LICENSE | 22 + node_modules/ultron/README.md | 97 +++ node_modules/ultron/index.js | 129 +++ node_modules/ultron/package.json | 74 ++ node_modules/ultron/test.js | 327 +++++++ node_modules/ws/.npmignore | 11 + node_modules/ws/.travis.yml | 15 + node_modules/ws/Makefile | 40 + node_modules/ws/README.md | 242 ++++++ node_modules/ws/index.js | 49 ++ node_modules/ws/lib/BufferPool.js | 63 ++ node_modules/ws/lib/BufferUtil.fallback.js | 47 + node_modules/ws/lib/BufferUtil.js | 13 + node_modules/ws/lib/ErrorCodes.js | 24 + node_modules/ws/lib/Extensions.js | 70 ++ node_modules/ws/lib/PerMessageDeflate.js | 325 +++++++ node_modules/ws/lib/Receiver.hixie.js | 184 ++++ node_modules/ws/lib/Receiver.js | 702 +++++++++++++++ node_modules/ws/lib/Sender.hixie.js | 124 +++ node_modules/ws/lib/Sender.js | 324 +++++++ node_modules/ws/lib/Validation.fallback.js | 12 + node_modules/ws/lib/Validation.js | 13 + node_modules/ws/lib/WebSocket.js | 965 +++++++++++++++++++++ node_modules/ws/lib/WebSocketServer.js | 513 +++++++++++ node_modules/ws/package.json | 76 ++ 42 files changed, 4806 insertions(+), 5 deletions(-) create mode 100644 audio/skill/reyingzi1_sunce.mp3 create mode 100644 audio/skill/reyingzi2_sunce.mp3 create mode 100755 audio/skill/reyingzi_cunce1.mp3 create mode 100755 audio/skill/reyingzi_sunce2.mp3 create mode 100644 game/server.js create mode 100644 image/skin/caocao/1.jpg create mode 100644 node_modules/options/.npmignore create mode 100644 node_modules/options/Makefile create mode 100644 node_modules/options/README.md create mode 100644 node_modules/options/lib/options.js create mode 100644 node_modules/options/package.json create mode 100644 node_modules/ultron/.npmignore create mode 100644 node_modules/ultron/.travis.yml create mode 100644 node_modules/ultron/LICENSE create mode 100644 node_modules/ultron/README.md create mode 100644 node_modules/ultron/index.js create mode 100644 node_modules/ultron/package.json create mode 100644 node_modules/ultron/test.js create mode 100644 node_modules/ws/.npmignore create mode 100644 node_modules/ws/.travis.yml create mode 100644 node_modules/ws/Makefile create mode 100644 node_modules/ws/README.md create mode 100644 node_modules/ws/index.js create mode 100644 node_modules/ws/lib/BufferPool.js create mode 100644 node_modules/ws/lib/BufferUtil.fallback.js create mode 100644 node_modules/ws/lib/BufferUtil.js create mode 100644 node_modules/ws/lib/ErrorCodes.js create mode 100644 node_modules/ws/lib/Extensions.js create mode 100644 node_modules/ws/lib/PerMessageDeflate.js create mode 100644 node_modules/ws/lib/Receiver.hixie.js create mode 100644 node_modules/ws/lib/Receiver.js create mode 100644 node_modules/ws/lib/Sender.hixie.js create mode 100644 node_modules/ws/lib/Sender.js create mode 100644 node_modules/ws/lib/Validation.fallback.js create mode 100644 node_modules/ws/lib/Validation.js create mode 100644 node_modules/ws/lib/WebSocket.js create mode 100644 node_modules/ws/lib/WebSocketServer.js create mode 100644 node_modules/ws/package.json diff --git a/audio/skill/reyingzi1_sunce.mp3 b/audio/skill/reyingzi1_sunce.mp3 new file mode 100644 index 000000000..923f2b7ec --- /dev/null +++ b/audio/skill/reyingzi1_sunce.mp3 @@ -0,0 +1,7 @@ + + +404 Not Found + +

Not Found

+

The requested URL /audio/skill/reyingzi1_sunce.mp3 was not found on this server.

+ diff --git a/audio/skill/reyingzi2_sunce.mp3 b/audio/skill/reyingzi2_sunce.mp3 new file mode 100644 index 000000000..97f74d461 --- /dev/null +++ b/audio/skill/reyingzi2_sunce.mp3 @@ -0,0 +1,7 @@ + + +404 Not Found + +

Not Found

+

The requested URL /audio/skill/reyingzi2_sunce.mp3 was not found on this server.

+ diff --git a/audio/skill/reyingzi_cunce1.mp3 b/audio/skill/reyingzi_cunce1.mp3 new file mode 100755 index 0000000000000000000000000000000000000000..c6f10e3c31ba34bab283fe4bda346b079cbf66ab GIT binary patch literal 66519 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 literal 0 HcmV?d00001 diff --git a/character/refresh.js b/character/refresh.js index d4d011112..1f8330dc5 100755 --- a/character/refresh.js +++ b/character/refresh.js @@ -349,6 +349,7 @@ character.refresh={ }, reyingzi:{ audio:2, + audioname:['sunce'], trigger:{player:'phaseDrawBegin'}, forced:true, content:function(){ diff --git a/game/asset.js b/game/asset.js index 2fe45b9f9..e7533f8db 100644 --- a/game/asset.js +++ b/game/asset.js @@ -1,5 +1,5 @@ window.noname_asset_list=[ - '1.8.1', + '1.8.1.1', 'image/background/huangtian_bg.jpg', 'image/background/shengshi_bg.jpg', 'image/background/xueji_bg.jpg', @@ -1854,6 +1854,8 @@ window.noname_asset_list=[ 'audio/skill/reyiji2.mp3', 'audio/skill/reyingzi1.mp3', 'audio/skill/reyingzi2.mp3', + 'audio/skill/reyingzi1_sunce.mp3', + 'audio/skill/reyingzi2_sunce.mp3', 'audio/skill/roulin1.mp3', 'audio/skill/roulin2.mp3', 'audio/skill/ruoyu1.mp3', @@ -2160,4 +2162,36 @@ window.noname_asset_list=[ 'font/xiaozhuan.ttf', 'font/huangcao.ttf', 'font/xinwei.ttf', + 'theme/music/grid.png', + 'theme/music/style.css', + 'theme/music/wood.png', + 'theme/music/wood3.png', + 'theme/simple/card.png', + 'theme/simple/grid.png', + 'theme/simple/style.css', + 'theme/simple/unknown.png', + 'theme/simple/wood.png', + 'theme/simple/wood3.png', + 'theme/woodden/grid.png', + 'theme/woodden/style.css', + 'theme/woodden/wood.jpg', + 'theme/woodden/wood.png', + 'theme/woodden/wood2.jpg', + 'theme/woodden/wood2.png', + 'theme/style/card/default.css', + 'theme/style/card/music.css', + 'theme/style/card/simple.css', + 'theme/style/card/wood.css', + 'theme/style/cardback/default.css', + 'theme/style/cardback/music.css', + 'theme/style/cardback/official.css', + 'theme/style/cardback/wood.css', + 'theme/style/cardback/image/official.png', + 'theme/style/cardback/image/official2.png', + 'theme/style/hp/default.css', + 'theme/style/hp/official.css', + 'theme/style/hp/image/hp1.png', + 'theme/style/hp/image/hp2.png', + 'theme/style/hp/image/hp3.png', + 'theme/style/hp/image/hp4.png', ] diff --git a/game/game.js b/game/game.js index 064b095db..eb7cea84e 100755 --- a/game/game.js +++ b/game/game.js @@ -3291,6 +3291,9 @@ } }); } + if(lib.config.debug){ + require('remote').getCurrentWindow().openDevTools(); + } ui.background=ui.create.div('.background'); ui.background.style.backgroundSize="cover"; if(lib.config.image_background&&lib.config.image_background!='default'&&lib.config.image_background!='custom'){ @@ -3737,8 +3740,9 @@ if(path[path.length-1]=='/'){ path=path.slice(0,path.length-1); } - if(path=='mode'&&lib.config.all.stockmode.indexOf(file)==-1){ + if(path==lib.assetURL+'mode'&&lib.config.all.stockmode.indexOf(file)==-1){ lib.init['setMode_'+file](); + onload(); return; } if(Array.isArray(file)){ @@ -17600,6 +17604,7 @@ text.style.position='absolute'; text.style.left='30px'; text.style.top='12px'; + text.style.wordBreak='break-all'; page.appendChild(text); var caption=ui.create.div('','输入命令',page); @@ -17633,7 +17638,7 @@ else{ try{ var result=eval(text2.value); - if(result){ + if(result!==undefined){ game.print(result); } } @@ -17941,6 +17946,11 @@ script.remove(); var updates=window.noname_source_list; delete window.noname_source_list; + for(var i=0;in?iz;oz2Drs*8G~8 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 + * MIT Licensed + */ + +var fs = require('fs'); + +function Options(defaults) { + var internalValues = {}; + var values = this.value = {}; + Object.keys(defaults).forEach(function(key) { + internalValues[key] = defaults[key]; + Object.defineProperty(values, key, { + get: function() { return internalValues[key]; }, + configurable: false, + enumerable: true + }); + }); + this.reset = function() { + Object.keys(defaults).forEach(function(key) { + internalValues[key] = defaults[key]; + }); + return this; + }; + this.merge = function(options, required) { + options = options || {}; + if (Object.prototype.toString.call(required) === '[object Array]') { + var missing = []; + for (var i = 0, l = required.length; i < l; ++i) { + var key = required[i]; + if (!(key in options)) { + missing.push(key); + } + } + if (missing.length > 0) { + if (missing.length > 1) { + throw new Error('options ' + + missing.slice(0, missing.length - 1).join(', ') + ' and ' + + missing[missing.length - 1] + ' must be defined'); + } + else throw new Error('option ' + missing[0] + ' must be defined'); + } + } + Object.keys(options).forEach(function(key) { + if (key in internalValues) { + internalValues[key] = options[key]; + } + }); + return this; + }; + this.copy = function(keys) { + var obj = {}; + Object.keys(defaults).forEach(function(key) { + if (keys.indexOf(key) !== -1) { + obj[key] = values[key]; + } + }); + return obj; + }; + this.read = function(filename, cb) { + if (typeof cb == 'function') { + var self = this; + fs.readFile(filename, function(error, data) { + if (error) return cb(error); + var conf = JSON.parse(data); + self.merge(conf); + cb(); + }); + } + else { + var conf = JSON.parse(fs.readFileSync(filename)); + this.merge(conf); + } + return this; + }; + this.isDefined = function(key) { + return typeof values[key] != 'undefined'; + }; + this.isDefinedAndNonNull = function(key) { + return typeof values[key] != 'undefined' && values[key] !== null; + }; + Object.freeze(values); + Object.freeze(this); +} + +module.exports = Options; diff --git a/node_modules/options/package.json b/node_modules/options/package.json new file mode 100644 index 000000000..7a62d8e3c --- /dev/null +++ b/node_modules/options/package.json @@ -0,0 +1,51 @@ +{ + "author": { + "name": "Einar Otto Stangvik", + "email": "einaros@gmail.com", + "url": "http://2x.io" + }, + "name": "options", + "description": "A very light-weight in-code option parsers for node.js.", + "version": "0.0.6", + "repository": { + "type": "git", + "url": "git://github.com/einaros/options.js.git" + }, + "main": "lib/options", + "scripts": { + "test": "make test" + }, + "engines": { + "node": ">=0.4.0" + }, + "dependencies": {}, + "devDependencies": { + "mocha": "latest" + }, + "gitHead": "ff53d0a092c897cb95964232a96fe17da65c11af", + "bugs": { + "url": "https://github.com/einaros/options.js/issues" + }, + "homepage": "https://github.com/einaros/options.js", + "_id": "options@0.0.6", + "_shasum": "ec22d312806bb53e731773e7cdaefcf1c643128f", + "_from": "options@>=0.0.5", + "_npmVersion": "1.4.21", + "_npmUser": { + "name": "einaros", + "email": "einaros@gmail.com" + }, + "maintainers": [ + { + "name": "einaros", + "email": "einaros@gmail.com" + } + ], + "dist": { + "shasum": "ec22d312806bb53e731773e7cdaefcf1c643128f", + "tarball": "http://registry.npmjs.org/options/-/options-0.0.6.tgz" + }, + "directories": {}, + "_resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", + "readme": "ERROR: No README data found!" +} diff --git a/node_modules/ultron/.npmignore b/node_modules/ultron/.npmignore new file mode 100644 index 000000000..66210a2a6 --- /dev/null +++ b/node_modules/ultron/.npmignore @@ -0,0 +1,3 @@ +node_modules +coverage +.tern-port diff --git a/node_modules/ultron/.travis.yml b/node_modules/ultron/.travis.yml new file mode 100644 index 000000000..a505004be --- /dev/null +++ b/node_modules/ultron/.travis.yml @@ -0,0 +1,21 @@ +sudo: false +language: node_js +node_js: + - "0.12" + - "0.10" + - "0.8" + - "iojs" +before_install: + - 'if [ "${TRAVIS_NODE_VERSION}" == "0.8" ]; then npm install -g npm@2.11.1; fi' +script: + - "npm run test-travis" +after_script: + - "npm install coveralls@2.11.x && cat coverage/lcov.info | coveralls" +matrix: + fast_finish: true +notifications: + irc: + channels: + - "irc.freenode.org#unshift" + on_success: change + on_failure: change diff --git a/node_modules/ultron/LICENSE b/node_modules/ultron/LICENSE new file mode 100644 index 000000000..6dc9316a6 --- /dev/null +++ b/node_modules/ultron/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Unshift.io, Arnout Kazemier, the Contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/node_modules/ultron/README.md b/node_modules/ultron/README.md new file mode 100644 index 000000000..84fa3f238 --- /dev/null +++ b/node_modules/ultron/README.md @@ -0,0 +1,97 @@ +# Ultron + +[![Made by unshift](https://img.shields.io/badge/made%20by-unshift-00ffcc.svg?style=flat-square)](http://unshift.io)[![Version npm](http://img.shields.io/npm/v/ultron.svg?style=flat-square)](http://browsenpm.org/package/ultron)[![Build Status](http://img.shields.io/travis/unshiftio/ultron/master.svg?style=flat-square)](https://travis-ci.org/unshiftio/ultron)[![Dependencies](https://img.shields.io/david/unshiftio/ultron.svg?style=flat-square)](https://david-dm.org/unshiftio/ultron)[![Coverage Status](http://img.shields.io/coveralls/unshiftio/ultron/master.svg?style=flat-square)](https://coveralls.io/r/unshiftio/ultron?branch=master)[![IRC channel](http://img.shields.io/badge/IRC-irc.freenode.net%23unshift-00a8ff.svg?style=flat-square)](http://webchat.freenode.net/?channels=unshift) + +Ultron is a high-intelligence robot. It gathers intelligence so it can start +improving upon his rudimentary design. It will learn your event emitting +patterns and find ways to exterminate them. Allowing you to remove only the +event emitters that **you** assigned and not the ones that your users or +developers assigned. This can prevent race conditions, memory leaks and even file +descriptor leaks from ever happening as you won't remove clean up processes. + +## Installation + +The module is designed to be used in browsers using browserify and in Node.js. +You can install the module through the public npm registry by running the +following command in CLI: + +``` +npm install --save ultron +``` + +## Usage + +In all examples we assume that you've required the library as following: + +```js +'use strict'; + +var Ultron = require('ultron'); +``` + +Now that we've required the library we can construct our first `Ultron` instance. +The constructor requires one argument which should be the `EventEmitter` +instance that we need to operate upon. This can be the `EventEmitter` module +that ships with Node.js or `EventEmitter3` or anything else as long as it +follow the same API and internal structure as these 2. So with that in mind we +can create the instance: + +```js +// +// For the sake of this example we're going to construct an empty EventEmitter +// +var EventEmitter = require('events').EventEmitter; // or require('eventmitter3'); +var events = new EventEmitter(); + +var ultron = new Ultron(events); +``` + +You can now use the following API's from the Ultron instance: + +### Ultron.on + +Register a new event listener for the given event. It follows the exact same API +as `EventEmitter.on` but it will return itself instead of returning the +EventEmitter instance. If you are using EventEmitter3 it also supports the +context param: + +```js +ultron.on('event-name', handler, { custom: 'function context' }); +``` + +### Ultron.once + +Exactly the same as the [Ultron.on](#ultronon) but it only allows the execution +once. + +### Ultron.remove + +This is where all the magic happens and the safe removal starts. This function +accepts different argument styles: + +- No arguments, assume that all events need to be removed so it will work as + `removeAllListeners()` API. +- 1 argument, when it's a string it will be split on ` ` and `,` to create a + list of events that need to be cleared. +- Multiple arguments, we assume that they are all names of events that need to + be cleared. + +```js +ultron.remove('foo, bar baz'); // Removes foo, bar and baz. +ultron.remove('foo', 'bar', 'baz'); // Removes foo, bar and baz. +ultron.remove(); // Removes everything. +``` + +If you just want to remove a single event listener using a function reference +you can still use the EventEmitter's `removeListener(event, fn)` API: + +```js +function foo() {} + +ulton.on('foo', foo); +events.removeListener('foo', foo); +``` + +## License + +MIT diff --git a/node_modules/ultron/index.js b/node_modules/ultron/index.js new file mode 100644 index 000000000..af17ab7cc --- /dev/null +++ b/node_modules/ultron/index.js @@ -0,0 +1,129 @@ +'use strict'; + +var has = Object.prototype.hasOwnProperty; + +/** + * An auto incrementing id which we can use to create "unique" Ultron instances + * so we can track the event emitters that are added through the Ultron + * interface. + * + * @type {Number} + * @private + */ +var id = 0; + +/** + * Ultron is high-intelligence robot. It gathers intelligence so it can start improving + * upon his rudimentary design. It will learn from your EventEmitting patterns + * and exterminate them. + * + * @constructor + * @param {EventEmitter} ee EventEmitter instance we need to wrap. + * @api public + */ +function Ultron(ee) { + if (!(this instanceof Ultron)) return new Ultron(ee); + + this.id = id++; + this.ee = ee; +} + +/** + * Register a new EventListener for the given event. + * + * @param {String} event Name of the event. + * @param {Functon} fn Callback function. + * @param {Mixed} context The context of the function. + * @returns {Ultron} + * @api public + */ +Ultron.prototype.on = function on(event, fn, context) { + fn.__ultron = this.id; + this.ee.on(event, fn, context); + + return this; +}; +/** + * Add an EventListener that's only called once. + * + * @param {String} event Name of the event. + * @param {Function} fn Callback function. + * @param {Mixed} context The context of the function. + * @returns {Ultron} + * @api public + */ +Ultron.prototype.once = function once(event, fn, context) { + fn.__ultron = this.id; + this.ee.once(event, fn, context); + + return this; +}; + +/** + * Remove the listeners we assigned for the given event. + * + * @returns {Ultron} + * @api public + */ +Ultron.prototype.remove = function remove() { + var args = arguments + , event; + + // + // When no event names are provided we assume that we need to clear all the + // events that were assigned through us. + // + if (args.length === 1 && 'string' === typeof args[0]) { + args = args[0].split(/[, ]+/); + } else if (!args.length) { + args = []; + + for (event in this.ee._events) { + if (has.call(this.ee._events, event)) args.push(event); + } + } + + for (var i = 0; i < args.length; i++) { + var listeners = this.ee.listeners(args[i]); + + for (var j = 0; j < listeners.length; j++) { + event = listeners[j]; + + // + // Once listeners have a `listener` property that stores the real listener + // in the EventEmitter that ships with Node.js. + // + if (event.listener) { + if (event.listener.__ultron !== this.id) continue; + delete event.listener.__ultron; + } else { + if (event.__ultron !== this.id) continue; + delete event.__ultron; + } + + this.ee.removeListener(args[i], event); + } + } + + return this; +}; + +/** + * Destroy the Ultron instance, remove all listeners and release all references. + * + * @returns {Boolean} + * @api public + */ +Ultron.prototype.destroy = function destroy() { + if (!this.ee) return false; + + this.remove(); + this.ee = null; + + return true; +}; + +// +// Expose the module. +// +module.exports = Ultron; diff --git a/node_modules/ultron/package.json b/node_modules/ultron/package.json new file mode 100644 index 000000000..d68bc5882 --- /dev/null +++ b/node_modules/ultron/package.json @@ -0,0 +1,74 @@ +{ + "name": "ultron", + "version": "1.0.2", + "description": "Ultron is high-intelligence robot. It gathers intel so it can start improving upon his rudimentary design", + "main": "index.js", + "scripts": { + "100%": "istanbul check-coverage --statements 100 --functions 100 --lines 100 --branches 100", + "test": "mocha test.js", + "watch": "mocha --watch test.js", + "coverage": "istanbul cover ./node_modules/.bin/_mocha -- test.js", + "test-travis": "istanbul cover node_modules/.bin/_mocha --report lcovonly -- test.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/unshiftio/ultron.git" + }, + "keywords": [ + "Ultron", + "robot", + "gather", + "intelligence", + "event", + "events", + "eventemitter", + "emitter", + "cleanup" + ], + "author": { + "name": "Arnout Kazemier" + }, + "license": "MIT", + "devDependencies": { + "assume": "1.2.x", + "eventemitter3": "1.1.x", + "istanbul": "0.3.x", + "mocha": "2.2.x", + "pre-commit": "1.0.x" + }, + "bugs": { + "url": "https://github.com/unshiftio/ultron/issues" + }, + "homepage": "https://github.com/unshiftio/ultron", + "gitHead": "a10482ae98a09120821545456c90c6d60d540f7c", + "_id": "ultron@1.0.2", + "_shasum": "ace116ab557cd197386a4e88f4685378c8b2e4fa", + "_from": "ultron@>=1.0.0 <1.1.0", + "_npmVersion": "2.9.1", + "_nodeVersion": "0.12.3", + "_npmUser": { + "name": "3rdeden", + "email": "npm@3rd-Eden.com" + }, + "maintainers": [ + { + "name": "unshift", + "email": "npm@unshift.io" + }, + { + "name": "v1", + "email": "info@3rd-Eden.com" + }, + { + "name": "3rdeden", + "email": "npm@3rd-Eden.com" + } + ], + "dist": { + "shasum": "ace116ab557cd197386a4e88f4685378c8b2e4fa", + "tarball": "http://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz" + }, + "directories": {}, + "_resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", + "readme": "ERROR: No README data found!" +} diff --git a/node_modules/ultron/test.js b/node_modules/ultron/test.js new file mode 100644 index 000000000..1fd4f1bb5 --- /dev/null +++ b/node_modules/ultron/test.js @@ -0,0 +1,327 @@ +/* istanbul ignore next */ +describe('Ultron', function () { + 'use strict'; + + var EventEmitter = require('eventemitter3') + , EE = require('events').EventEmitter + , assume = require('assume') + , Ultron = require('./') + , ultron + , ee; + + beforeEach(function () { + ee = new EventEmitter(); + ultron = new Ultron(ee); + }); + + afterEach(function () { + ultron.destroy(); + ee.removeAllListeners(); + }); + + it('is exposed as a function', function () { + assume(Ultron).is.a('function'); + }); + + it('can be initialized without the new keyword', function () { + assume(Ultron(ee)).is.instanceOf(Ultron); + }); + + it('assigns a unique id to every instance', function () { + for (var i = 0; i < 100; i++) { + assume(ultron.id).does.not.equal((new Ultron()).id); + } + }); + + it('allows removal through the event emitter', function () { + function foo() {} + function bar() {} + + ultron.on('foo', foo); + ultron.once('foo', bar); + + assume(foo.__ultron).equals(ultron.id); + assume(bar.__ultron).equals(ultron.id); + assume(ee.listeners('foo').length).equals(2); + + ee.removeListener('foo', foo); + assume(ee.listeners('foo').length).equals(1); + + ee.removeListener('foo', bar); + assume(ee.listeners('foo').length).equals(0); + }); + + describe('#on', function () { + it('assigns a listener', function () { + assume(ee.listeners('foo').length).equals(0); + + function foo() {} + + ultron.on('foo', foo); + assume(ee.listeners('foo').length).equals(1); + assume(ee.listeners('foo')[0]).equals(foo); + }); + + it('tags the assigned function', function () { + assume(ee.listeners('foo').length).equals(0); + + ultron.on('foo', function () {}); + assume(ee.listeners('foo')[0].__ultron).equals(ultron.id); + }); + + it('also passes in the context', function (next) { + var context = 1313; + + ultron.on('foo', function (a, b, c) { + assume(a).equals('a'); + assume(b).equals('b'); + assume(c).equals('c'); + + assume(this).equals(context); + + next(); + }, context); + + ee.emit('foo', 'a', 'b', 'c'); + }); + + it('works with regular eventemitters as well', function (next) { + var ee = new EE() + , ultron = new Ultron(ee); + + ultron.on('foo', function (a, b, c) { + assume(a).equals('a'); + assume(b).equals('b'); + assume(c).equals('c'); + + next(); + }); + + ee.emit('foo', 'a', 'b', 'c'); + }); + }); + + describe('#once', function () { + it('assigns a listener', function () { + assume(ee.listeners('foo').length).equals(0); + + function foo() {} + ultron.once('foo', foo); + assume(ee.listeners('foo').length).equals(1); + assume(ee.listeners('foo')[0]).equals(foo); + }); + + it('tags the assigned function', function () { + assume(ee.listeners('foo').length).equals(0); + + ultron.once('foo', function () {}); + assume(ee.listeners('foo')[0].__ultron).equals(ultron.id); + }); + + it('also passes in the context', function (next) { + var context = 1313; + + ultron.once('foo', function (a, b, c) { + assume(a).equals('a'); + assume(b).equals('b'); + assume(c).equals('c'); + + assume(this).equals(context); + + next(); + }, context); + + ee.emit('foo', 'a', 'b', 'c'); + ee.emit('foo', 'a', 'b', 'c'); // Ensure that we don't double execute + }); + + it('works with regular eventemitters as well', function (next) { + var ee = new EE() + , ultron = new Ultron(ee); + + ultron.once('foo', function (a, b, c) { + assume(a).equals('a'); + assume(b).equals('b'); + assume(c).equals('c'); + + next(); + }); + + ee.emit('foo', 'a', 'b', 'c'); + ee.emit('foo', 'a', 'b', 'c'); // Ensure that we don't double execute + }); + }); + + describe('#remove', function () { + it('removes only our assigned `on` listeners', function () { + function foo() {} + function bar() {} + + ee.on('foo', foo); + ultron.on('foo', bar); + assume(ee.listeners('foo').length).equals(2); + + ultron.remove('foo'); + assume(ee.listeners('foo').length).equals(1); + assume(ee.listeners('foo')[0]).equals(foo); + }); + + it('removes our private __ultron references', function () { + function once() {} + function on() {} + + assume('__ultron' in once).is.false(); + assume('__ultron' in on).is.false(); + + ultron.on('foo', on); + ultron.once('bar', once); + + assume('__ultron' in once).is.true(); + assume('__ultron' in on).is.true(); + + ultron.remove('foo, bar'); + + assume('__ultron' in once).is.false(); + assume('__ultron' in on).is.false(); + + ultron.destroy(); + + ee = new EE(); + ultron = new Ultron(ee); + + assume('__ultron' in once).is.false(); + assume('__ultron' in on).is.false(); + + ultron.on('foo', on); + ultron.once('bar', once); + + assume('__ultron' in once).is.true(); + assume('__ultron' in on).is.true(); + + ultron.remove('foo, bar'); + + assume('__ultron' in once).is.false(); + assume('__ultron' in on).is.false(); + }); + + it('removes only our assigned `once` listeners', function () { + function foo() {} + function bar() {} + + ee.once('foo', foo); + ultron.once('foo', bar); + assume(ee.listeners('foo').length).equals(2); + + ultron.remove('foo'); + assume(ee.listeners('foo').length).equals(1); + assume(ee.listeners('foo')[0]).equals(foo); + }); + + it('removes only our assigned `once` listeners from regular EE', function () { + var ee = new EE() + , ultron = new Ultron(ee); + + function foo() {} + function bar() {} + + ee.once('foo', foo); + ultron.once('foo', bar); + assume(ee.listeners('foo').length).equals(2); + + ultron.remove('foo'); + assume(ee.listeners('foo').length).equals(1); + assume(ee.listeners('foo')[0].listener).equals(foo); + }); + + it('removes all assigned events if called without args', function () { + function foo() {} + function bar() {} + + ultron.on('foo', foo); + ultron.on('bar', bar); + + assume(ee.listeners('foo').length).equals(1); + assume(ee.listeners('bar').length).equals(1); + + ultron.remove(); + + assume(ee.listeners('foo').length).equals(0); + assume(ee.listeners('bar').length).equals(0); + }); + + it('removes multiple listeners based on args', function () { + function foo() {} + function bar() {} + function baz() {} + + ultron.on('foo', foo); + ultron.on('bar', bar); + ultron.on('baz', baz); + + assume(ee.listeners('foo').length).equals(1); + assume(ee.listeners('bar').length).equals(1); + assume(ee.listeners('baz').length).equals(1); + + ultron.remove('foo', 'bar'); + + assume(ee.listeners('foo').length).equals(0); + assume(ee.listeners('bar').length).equals(0); + assume(ee.listeners('baz').length).equals(1); + }); + + it('removes multiple listeners if first arg is seperated string', function () { + function foo() {} + function bar() {} + function baz() {} + + ultron.on('foo', foo); + ultron.on('bar', bar); + ultron.on('baz', baz); + + assume(ee.listeners('foo').length).equals(1); + assume(ee.listeners('bar').length).equals(1); + assume(ee.listeners('baz').length).equals(1); + + ultron.remove('foo, bar'); + + assume(ee.listeners('foo').length).equals(0); + assume(ee.listeners('bar').length).equals(0); + assume(ee.listeners('baz').length).equals(1); + }); + }); + + describe('#destroy', function () { + it('removes all listeners', function () { + function foo() {} + function bar() {} + function baz() {} + + ultron.on('foo', foo); + ultron.on('bar', bar); + ultron.on('baz', baz); + + assume(ee.listeners('foo').length).equals(1); + assume(ee.listeners('bar').length).equals(1); + assume(ee.listeners('baz').length).equals(1); + + ultron.destroy(); + + assume(ee.listeners('foo').length).equals(0); + assume(ee.listeners('bar').length).equals(0); + assume(ee.listeners('baz').length).equals(0); + }); + + it('removes the .ee reference', function () { + assume(ultron.ee).equals(ee); + ultron.destroy(); + assume(ultron.ee).equals(null); + }); + + it('returns booleans for state indication', function () { + assume(ultron.destroy()).is.true(); + assume(ultron.destroy()).is.false(); + assume(ultron.destroy()).is.false(); + assume(ultron.destroy()).is.false(); + }); + }); +}); diff --git a/node_modules/ws/.npmignore b/node_modules/ws/.npmignore new file mode 100644 index 000000000..1eba800f8 --- /dev/null +++ b/node_modules/ws/.npmignore @@ -0,0 +1,11 @@ +npm-debug.log +node_modules +.*.swp +.lock-* +build + +bench +doc +examples +test + diff --git a/node_modules/ws/.travis.yml b/node_modules/ws/.travis.yml new file mode 100644 index 000000000..5002b4984 --- /dev/null +++ b/node_modules/ws/.travis.yml @@ -0,0 +1,15 @@ +language: node_js +sudo: false +node_js: + - "5" + - "4" + - "0.12" +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - gcc-4.9 + - g++-4.9 +before_install: + - export CC="gcc-4.9" CXX="g++-4.9" diff --git a/node_modules/ws/Makefile b/node_modules/ws/Makefile new file mode 100644 index 000000000..00f19fa01 --- /dev/null +++ b/node_modules/ws/Makefile @@ -0,0 +1,40 @@ +ALL_TESTS = $(shell find test/ -name '*.test.js') +ALL_INTEGRATION = $(shell find test/ -name '*.integration.js') + +all: + node-gyp configure build + +clean: + node-gyp clean + +run-tests: + @./node_modules/.bin/mocha \ + -t 5000 \ + -s 2400 \ + $(TESTFLAGS) \ + $(TESTS) + +run-integrationtests: + @./node_modules/.bin/mocha \ + -t 5000 \ + -s 6000 \ + $(TESTFLAGS) \ + $(TESTS) + +test: + @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 NODE_PATH=lib TESTS="$(ALL_TESTS)" run-tests + +integrationtest: + @$(MAKE) NODE_TLS_REJECT_UNAUTHORIZED=0 NODE_PATH=lib TESTS="$(ALL_INTEGRATION)" run-integrationtests + +benchmark: + @node bench/sender.benchmark.js + @node bench/parser.benchmark.js + +autobahn: + @NODE_PATH=lib node test/autobahn.js + +autobahn-server: + @NODE_PATH=lib node test/autobahn-server.js + +.PHONY: test diff --git a/node_modules/ws/README.md b/node_modules/ws/README.md new file mode 100644 index 000000000..9be2e51d9 --- /dev/null +++ b/node_modules/ws/README.md @@ -0,0 +1,242 @@ +# ws: a node.js websocket library + +[![Build Status](https://travis-ci.org/websockets/ws.svg?branch=master)](https://travis-ci.org/websockets/ws) + +`ws` is a simple to use WebSocket implementation, up-to-date against RFC-6455, +and [probably the fastest WebSocket library for node.js][archive]. + +Passes the quite extensive Autobahn test suite. See http://websockets.github.com/ws +for the full reports. + +## Protocol support + +* **Hixie draft 76** (Old and deprecated, but still in use by Safari and Opera. + Added to ws version 0.4.2, but server only. Can be disabled by setting the + `disableHixie` option to true.) +* **HyBi drafts 07-12** (Use the option `protocolVersion: 8`) +* **HyBi drafts 13-17** (Current default, alternatively option `protocolVersion: 13`) + +### Installing + +``` +npm install --save ws +``` + +### Opt-in for performance + +There are 2 optional modules that can be installed along side with the `ws` +module. These modules are binary addons which improve certain operations, but as +they are binary addons they require compilation which can fail if no c++ +compiler is installed on the host system. + +- `npm install --save bufferutil`: Improves internal buffer operations which + allows for faster processing of masked WebSocket frames and general buffer + operations. +- `npm install --save utf-8-validate`: The specification requires validation of + invalid UTF-8 chars, some of these validations could not be done in JavaScript + hence the need for a binary addon. In most cases you will already be + validating the input that you receive for security purposes leading to double + validation. But if you want to be 100% spec conform and fast validation of UTF-8 + then this module is a must. + +### Sending and receiving text data + +```js +var WebSocket = require('ws'); +var ws = new WebSocket('ws://www.host.com/path'); + +ws.on('open', function open() { + ws.send('something'); +}); + +ws.on('message', function(data, flags) { + // flags.binary will be set if a binary data is received. + // flags.masked will be set if the data was masked. +}); +``` + +### Sending binary data + +```js +var WebSocket = require('ws'); +var ws = new WebSocket('ws://www.host.com/path'); + +ws.on('open', function open() { + var array = new Float32Array(5); + + for (var i = 0; i < array.length; ++i) { + array[i] = i / 2; + } + + ws.send(array, { binary: true, mask: true }); +}); +``` + +Setting `mask`, as done for the send options above, will cause the data to be +masked according to the WebSocket protocol. The same option applies for text +data. + +### Server example + +```js +var WebSocketServer = require('ws').Server + , wss = new WebSocketServer({ port: 8080 }); + +wss.on('connection', function connection(ws) { + ws.on('message', function incoming(message) { + console.log('received: %s', message); + }); + + ws.send('something'); +}); +``` + +### ExpressJS example + +```js +var server = require('http').createServer() + , url = require('url') + , WebSocketServer = require('ws').Server + , wss = new WebSocketServer({ server: server }) + , express = require('express') + , app = express() + , port = 4080; + +app.use(function (req, res) { + res.send({ msg: "hello" }); +}); + +wss.on('connection', function connection(ws) { + var location = url.parse(ws.upgradeReq.url, true); + // you might use location.query.access_token to authenticate or share sessions + // or ws.upgradeReq.headers.cookie (see http://stackoverflow.com/a/16395220/151312) + + ws.on('message', function incoming(message) { + console.log('received: %s', message); + }); + + ws.send('something'); +}); + +server.on('request', app); +server.listen(port, function () { console.log('Listening on ' + server.address().port) }); +``` + +### Server sending broadcast data + +```js +var WebSocketServer = require('ws').Server + , wss = new WebSocketServer({ port: 8080 }); + +wss.broadcast = function broadcast(data) { + wss.clients.forEach(function each(client) { + client.send(data); + }); +}; +``` + +### Error handling best practices + +```js +// If the WebSocket is closed before the following send is attempted +ws.send('something'); + +// Errors (both immediate and async write errors) can be detected in an optional +// callback. The callback is also the only way of being notified that data has +// actually been sent. +ws.send('something', function ack(error) { + // if error is not defined, the send has been completed, + // otherwise the error object will indicate what failed. +}); + +// Immediate errors can also be handled with try/catch-blocks, but **note** that +// since sends are inherently asynchronous, socket write failures will *not* be +// captured when this technique is used. +try { ws.send('something'); } +catch (e) { /* handle error */ } +``` + +### echo.websocket.org demo + +```js +var WebSocket = require('ws'); +var ws = new WebSocket('ws://echo.websocket.org/', { + protocolVersion: 8, + origin: 'http://websocket.org' +}); + +ws.on('open', function open() { + console.log('connected'); + ws.send(Date.now().toString(), {mask: true}); +}); + +ws.on('close', function close() { + console.log('disconnected'); +}); + +ws.on('message', function message(data, flags) { + console.log('Roundtrip time: ' + (Date.now() - parseInt(data)) + 'ms', flags); + + setTimeout(function timeout() { + ws.send(Date.now().toString(), {mask: true}); + }, 500); +}); +``` + +### Browserify users +When including ws via a browserify bundle, ws returns global.WebSocket which has slightly different API. +You should use the standard WebSockets API instead. + +https://developer.mozilla.org/en-US/docs/WebSockets/Writing_WebSocket_client_applications#Availability_of_WebSockets + + +### Other examples + +For a full example with a browser client communicating with a ws server, see the +examples folder. + +Note that the usage together with Express 3.0 is quite different from Express +2.x. The difference is expressed in the two different serverstats-examples. + +Otherwise, see the test cases. + +### Running the tests + +``` +make test +``` + +## API Docs + +See [`/doc/ws.md`](https://github.com/websockets/ws/blob/master/doc/ws.md) for Node.js-like docs for the ws classes. + +## Changelog + +We're using the GitHub [`releases`](https://github.com/websockets/ws/releases) for changelog entries. + +## License + +(The MIT License) + +Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +[archive]: http://web.archive.org/web/20130314230536/http://hobbycoding.posterous.com/the-fastest-websocket-module-for-nodejs diff --git a/node_modules/ws/index.js b/node_modules/ws/index.js new file mode 100644 index 000000000..a7e8644b9 --- /dev/null +++ b/node_modules/ws/index.js @@ -0,0 +1,49 @@ +'use strict'; + +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +var WS = module.exports = require('./lib/WebSocket'); + +WS.Server = require('./lib/WebSocketServer'); +WS.Sender = require('./lib/Sender'); +WS.Receiver = require('./lib/Receiver'); + +/** + * Create a new WebSocket server. + * + * @param {Object} options Server options + * @param {Function} fn Optional connection listener. + * @returns {WS.Server} + * @api public + */ +WS.createServer = function createServer(options, fn) { + var server = new WS.Server(options); + + if (typeof fn === 'function') { + server.on('connection', fn); + } + + return server; +}; + +/** + * Create a new WebSocket connection. + * + * @param {String} address The URL/address we need to connect to. + * @param {Function} fn Open listener. + * @returns {WS} + * @api public + */ +WS.connect = WS.createConnection = function connect(address, fn) { + var client = new WS(address); + + if (typeof fn === 'function') { + client.on('open', fn); + } + + return client; +}; diff --git a/node_modules/ws/lib/BufferPool.js b/node_modules/ws/lib/BufferPool.js new file mode 100644 index 000000000..8ee599057 --- /dev/null +++ b/node_modules/ws/lib/BufferPool.js @@ -0,0 +1,63 @@ +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +var util = require('util'); + +function BufferPool(initialSize, growStrategy, shrinkStrategy) { + if (this instanceof BufferPool === false) { + throw new TypeError("Classes can't be function-called"); + } + + if (typeof initialSize === 'function') { + shrinkStrategy = growStrategy; + growStrategy = initialSize; + initialSize = 0; + } + else if (typeof initialSize === 'undefined') { + initialSize = 0; + } + this._growStrategy = (growStrategy || function(db, size) { + return db.used + size; + }).bind(null, this); + this._shrinkStrategy = (shrinkStrategy || function(db) { + return initialSize; + }).bind(null, this); + this._buffer = initialSize ? new Buffer(initialSize) : null; + this._offset = 0; + this._used = 0; + this._changeFactor = 0; + this.__defineGetter__('size', function(){ + return this._buffer == null ? 0 : this._buffer.length; + }); + this.__defineGetter__('used', function(){ + return this._used; + }); +} + +BufferPool.prototype.get = function(length) { + if (this._buffer == null || this._offset + length > this._buffer.length) { + var newBuf = new Buffer(this._growStrategy(length)); + this._buffer = newBuf; + this._offset = 0; + } + this._used += length; + var buf = this._buffer.slice(this._offset, this._offset + length); + this._offset += length; + return buf; +} + +BufferPool.prototype.reset = function(forceNewBuffer) { + var len = this._shrinkStrategy(); + if (len < this.size) this._changeFactor -= 1; + if (forceNewBuffer || this._changeFactor < -2) { + this._changeFactor = 0; + this._buffer = len ? new Buffer(len) : null; + } + this._offset = 0; + this._used = 0; +} + +module.exports = BufferPool; diff --git a/node_modules/ws/lib/BufferUtil.fallback.js b/node_modules/ws/lib/BufferUtil.fallback.js new file mode 100644 index 000000000..508542c9e --- /dev/null +++ b/node_modules/ws/lib/BufferUtil.fallback.js @@ -0,0 +1,47 @@ +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +module.exports.BufferUtil = { + merge: function(mergedBuffer, buffers) { + var offset = 0; + for (var i = 0, l = buffers.length; i < l; ++i) { + var buf = buffers[i]; + buf.copy(mergedBuffer, offset); + offset += buf.length; + } + }, + mask: function(source, mask, output, offset, length) { + var maskNum = mask.readUInt32LE(0, true); + var i = 0; + for (; i < length - 3; i += 4) { + var num = maskNum ^ source.readUInt32LE(i, true); + if (num < 0) num = 4294967296 + num; + output.writeUInt32LE(num, offset + i, true); + } + switch (length % 4) { + case 3: output[offset + i + 2] = source[i + 2] ^ mask[2]; + case 2: output[offset + i + 1] = source[i + 1] ^ mask[1]; + case 1: output[offset + i] = source[i] ^ mask[0]; + case 0:; + } + }, + unmask: function(data, mask) { + var maskNum = mask.readUInt32LE(0, true); + var length = data.length; + var i = 0; + for (; i < length - 3; i += 4) { + var num = maskNum ^ data.readUInt32LE(i, true); + if (num < 0) num = 4294967296 + num; + data.writeUInt32LE(num, i, true); + } + switch (length % 4) { + case 3: data[i + 2] = data[i + 2] ^ mask[2]; + case 2: data[i + 1] = data[i + 1] ^ mask[1]; + case 1: data[i] = data[i] ^ mask[0]; + case 0:; + } + } +} diff --git a/node_modules/ws/lib/BufferUtil.js b/node_modules/ws/lib/BufferUtil.js new file mode 100644 index 000000000..18c699894 --- /dev/null +++ b/node_modules/ws/lib/BufferUtil.js @@ -0,0 +1,13 @@ +'use strict'; + +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +try { + module.exports = require('bufferutil'); +} catch (e) { + module.exports = require('./BufferUtil.fallback'); +} diff --git a/node_modules/ws/lib/ErrorCodes.js b/node_modules/ws/lib/ErrorCodes.js new file mode 100644 index 000000000..55ebd529b --- /dev/null +++ b/node_modules/ws/lib/ErrorCodes.js @@ -0,0 +1,24 @@ +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +module.exports = { + isValidErrorCode: function(code) { + return (code >= 1000 && code <= 1011 && code != 1004 && code != 1005 && code != 1006) || + (code >= 3000 && code <= 4999); + }, + 1000: 'normal', + 1001: 'going away', + 1002: 'protocol error', + 1003: 'unsupported data', + 1004: 'reserved', + 1005: 'reserved for extensions', + 1006: 'reserved for extensions', + 1007: 'inconsistent or invalid data', + 1008: 'policy violation', + 1009: 'message too big', + 1010: 'extension handshake missing', + 1011: 'an unexpected condition prevented the request from being fulfilled', +}; \ No newline at end of file diff --git a/node_modules/ws/lib/Extensions.js b/node_modules/ws/lib/Extensions.js new file mode 100644 index 000000000..a465ace2b --- /dev/null +++ b/node_modules/ws/lib/Extensions.js @@ -0,0 +1,70 @@ + +var util = require('util'); + +/** + * Module exports. + */ + +exports.parse = parse; +exports.format = format; + +/** + * Parse extensions header value + */ + +function parse(value) { + value = value || ''; + + var extensions = {}; + + value.split(',').forEach(function(v) { + var params = v.split(';'); + var token = params.shift().trim(); + var paramsList = extensions[token] = extensions[token] || []; + var parsedParams = {}; + + params.forEach(function(param) { + var parts = param.trim().split('='); + var key = parts[0]; + var value = parts[1]; + if (typeof value === 'undefined') { + value = true; + } else { + // unquote value + if (value[0] === '"') { + value = value.slice(1); + } + if (value[value.length - 1] === '"') { + value = value.slice(0, value.length - 1); + } + } + (parsedParams[key] = parsedParams[key] || []).push(value); + }); + + paramsList.push(parsedParams); + }); + + return extensions; +} + +/** + * Format extensions header value + */ + +function format(value) { + return Object.keys(value).map(function(token) { + var paramsList = value[token]; + if (!util.isArray(paramsList)) { + paramsList = [paramsList]; + } + return paramsList.map(function(params) { + return [token].concat(Object.keys(params).map(function(k) { + var p = params[k]; + if (!util.isArray(p)) p = [p]; + return p.map(function(v) { + return v === true ? k : k + '=' + v; + }).join('; '); + })).join('; '); + }).join(', '); + }).join(', '); +} diff --git a/node_modules/ws/lib/PerMessageDeflate.js b/node_modules/ws/lib/PerMessageDeflate.js new file mode 100644 index 000000000..5324bd8e6 --- /dev/null +++ b/node_modules/ws/lib/PerMessageDeflate.js @@ -0,0 +1,325 @@ + +var zlib = require('zlib'); + +var AVAILABLE_WINDOW_BITS = [8, 9, 10, 11, 12, 13, 14, 15]; +var DEFAULT_WINDOW_BITS = 15; +var DEFAULT_MEM_LEVEL = 8; + +PerMessageDeflate.extensionName = 'permessage-deflate'; + +/** + * Per-message Compression Extensions implementation + */ + +function PerMessageDeflate(options, isServer) { + if (this instanceof PerMessageDeflate === false) { + throw new TypeError("Classes can't be function-called"); + } + + this._options = options || {}; + this._isServer = !!isServer; + this._inflate = null; + this._deflate = null; + this.params = null; +} + +/** + * Create extension parameters offer + * + * @api public + */ + +PerMessageDeflate.prototype.offer = function() { + var params = {}; + if (this._options.serverNoContextTakeover) { + params.server_no_context_takeover = true; + } + if (this._options.clientNoContextTakeover) { + params.client_no_context_takeover = true; + } + if (this._options.serverMaxWindowBits) { + params.server_max_window_bits = this._options.serverMaxWindowBits; + } + if (this._options.clientMaxWindowBits) { + params.client_max_window_bits = this._options.clientMaxWindowBits; + } else if (this._options.clientMaxWindowBits == null) { + params.client_max_window_bits = true; + } + return params; +}; + +/** + * Accept extension offer + * + * @api public + */ + +PerMessageDeflate.prototype.accept = function(paramsList) { + paramsList = this.normalizeParams(paramsList); + + var params; + if (this._isServer) { + params = this.acceptAsServer(paramsList); + } else { + params = this.acceptAsClient(paramsList); + } + + this.params = params; + return params; +}; + +/** + * Releases all resources used by the extension + * + * @api public + */ + +PerMessageDeflate.prototype.cleanup = function() { + if (this._inflate) { + if (this._inflate.writeInProgress) { + this._inflate.pendingClose = true; + } else { + if (this._inflate.close) this._inflate.close(); + this._inflate = null; + } + } + if (this._deflate) { + if (this._deflate.writeInProgress) { + this._deflate.pendingClose = true; + } else { + if (this._deflate.close) this._deflate.close(); + this._deflate = null; + } + } +}; + +/** + * Accept extension offer from client + * + * @api private + */ + +PerMessageDeflate.prototype.acceptAsServer = function(paramsList) { + var accepted = {}; + var result = paramsList.some(function(params) { + accepted = {}; + if (this._options.serverNoContextTakeover === false && params.server_no_context_takeover) { + return; + } + if (this._options.serverMaxWindowBits === false && params.server_max_window_bits) { + return; + } + if (typeof this._options.serverMaxWindowBits === 'number' && + typeof params.server_max_window_bits === 'number' && + this._options.serverMaxWindowBits > params.server_max_window_bits) { + return; + } + if (typeof this._options.clientMaxWindowBits === 'number' && !params.client_max_window_bits) { + return; + } + + if (this._options.serverNoContextTakeover || params.server_no_context_takeover) { + accepted.server_no_context_takeover = true; + } + if (this._options.clientNoContextTakeover) { + accepted.client_no_context_takeover = true; + } + if (this._options.clientNoContextTakeover !== false && params.client_no_context_takeover) { + accepted.client_no_context_takeover = true; + } + if (typeof this._options.serverMaxWindowBits === 'number') { + accepted.server_max_window_bits = this._options.serverMaxWindowBits; + } else if (typeof params.server_max_window_bits === 'number') { + accepted.server_max_window_bits = params.server_max_window_bits; + } + if (typeof this._options.clientMaxWindowBits === 'number') { + accepted.client_max_window_bits = this._options.clientMaxWindowBits; + } else if (this._options.clientMaxWindowBits !== false && typeof params.client_max_window_bits === 'number') { + accepted.client_max_window_bits = params.client_max_window_bits; + } + return true; + }, this); + + if (!result) { + throw new Error('Doesn\'t support the offered configuration'); + } + + return accepted; +}; + +/** + * Accept extension response from server + * + * @api privaye + */ + +PerMessageDeflate.prototype.acceptAsClient = function(paramsList) { + var params = paramsList[0]; + if (this._options.clientNoContextTakeover != null) { + if (this._options.clientNoContextTakeover === false && params.client_no_context_takeover) { + throw new Error('Invalid value for "client_no_context_takeover"'); + } + } + if (this._options.clientMaxWindowBits != null) { + if (this._options.clientMaxWindowBits === false && params.client_max_window_bits) { + throw new Error('Invalid value for "client_max_window_bits"'); + } + if (typeof this._options.clientMaxWindowBits === 'number' && + (!params.client_max_window_bits || params.client_max_window_bits > this._options.clientMaxWindowBits)) { + throw new Error('Invalid value for "client_max_window_bits"'); + } + } + return params; +}; + +/** + * Normalize extensions parameters + * + * @api private + */ + +PerMessageDeflate.prototype.normalizeParams = function(paramsList) { + return paramsList.map(function(params) { + Object.keys(params).forEach(function(key) { + var value = params[key]; + if (value.length > 1) { + throw new Error('Multiple extension parameters for ' + key); + } + + value = value[0]; + + switch (key) { + case 'server_no_context_takeover': + case 'client_no_context_takeover': + if (value !== true) { + throw new Error('invalid extension parameter value for ' + key + ' (' + value + ')'); + } + params[key] = true; + break; + case 'server_max_window_bits': + case 'client_max_window_bits': + if (typeof value === 'string') { + value = parseInt(value, 10); + if (!~AVAILABLE_WINDOW_BITS.indexOf(value)) { + throw new Error('invalid extension parameter value for ' + key + ' (' + value + ')'); + } + } + if (!this._isServer && value === true) { + throw new Error('Missing extension parameter value for ' + key); + } + params[key] = value; + break; + default: + throw new Error('Not defined extension parameter (' + key + ')'); + } + }, this); + return params; + }, this); +}; + +/** + * Decompress message + * + * @api public + */ + +PerMessageDeflate.prototype.decompress = function (data, fin, callback) { + var endpoint = this._isServer ? 'client' : 'server'; + + if (!this._inflate) { + var maxWindowBits = this.params[endpoint + '_max_window_bits']; + this._inflate = zlib.createInflateRaw({ + windowBits: 'number' === typeof maxWindowBits ? maxWindowBits : DEFAULT_WINDOW_BITS + }); + } + this._inflate.writeInProgress = true; + + var self = this; + var buffers = []; + + this._inflate.on('error', onError).on('data', onData); + this._inflate.write(data); + if (fin) { + this._inflate.write(new Buffer([0x00, 0x00, 0xff, 0xff])); + } + this._inflate.flush(function() { + cleanup(); + callback(null, Buffer.concat(buffers)); + }); + + function onError(err) { + cleanup(); + callback(err); + } + + function onData(data) { + buffers.push(data); + } + + function cleanup() { + if (!self._inflate) return; + self._inflate.removeListener('error', onError); + self._inflate.removeListener('data', onData); + self._inflate.writeInProgress = false; + if ((fin && self.params[endpoint + '_no_context_takeover']) || self._inflate.pendingClose) { + if (self._inflate.close) self._inflate.close(); + self._inflate = null; + } + } +}; + +/** + * Compress message + * + * @api public + */ + +PerMessageDeflate.prototype.compress = function (data, fin, callback) { + var endpoint = this._isServer ? 'server' : 'client'; + + if (!this._deflate) { + var maxWindowBits = this.params[endpoint + '_max_window_bits']; + this._deflate = zlib.createDeflateRaw({ + flush: zlib.Z_SYNC_FLUSH, + windowBits: 'number' === typeof maxWindowBits ? maxWindowBits : DEFAULT_WINDOW_BITS, + memLevel: this._options.memLevel || DEFAULT_MEM_LEVEL + }); + } + this._deflate.writeInProgress = true; + + var self = this; + var buffers = []; + + this._deflate.on('error', onError).on('data', onData); + this._deflate.write(data); + this._deflate.flush(function() { + cleanup(); + var data = Buffer.concat(buffers); + if (fin) { + data = data.slice(0, data.length - 4); + } + callback(null, data); + }); + + function onError(err) { + cleanup(); + callback(err); + } + + function onData(data) { + buffers.push(data); + } + + function cleanup() { + if (!self._deflate) return; + self._deflate.removeListener('error', onError); + self._deflate.removeListener('data', onData); + self._deflate.writeInProgress = false; + if ((fin && self.params[endpoint + '_no_context_takeover']) || self._deflate.pendingClose) { + if (self._deflate.close) self._deflate.close(); + self._deflate = null; + } + } +}; + +module.exports = PerMessageDeflate; diff --git a/node_modules/ws/lib/Receiver.hixie.js b/node_modules/ws/lib/Receiver.hixie.js new file mode 100644 index 000000000..66bc561b7 --- /dev/null +++ b/node_modules/ws/lib/Receiver.hixie.js @@ -0,0 +1,184 @@ +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +var util = require('util'); + +/** + * State constants + */ + +var EMPTY = 0 + , BODY = 1; +var BINARYLENGTH = 2 + , BINARYBODY = 3; + +/** + * Hixie Receiver implementation + */ + +function Receiver () { + if (this instanceof Receiver === false) { + throw new TypeError("Classes can't be function-called"); + } + + this.state = EMPTY; + this.buffers = []; + this.messageEnd = -1; + this.spanLength = 0; + this.dead = false; + + this.onerror = function() {}; + this.ontext = function() {}; + this.onbinary = function() {}; + this.onclose = function() {}; + this.onping = function() {}; + this.onpong = function() {}; +} + +module.exports = Receiver; + +/** + * Add new data to the parser. + * + * @api public + */ + +Receiver.prototype.add = function(data) { + var self = this; + function doAdd() { + if (self.state === EMPTY) { + if (data.length == 2 && data[0] == 0xFF && data[1] == 0x00) { + self.reset(); + self.onclose(); + return; + } + if (data[0] === 0x80) { + self.messageEnd = 0; + self.state = BINARYLENGTH; + data = data.slice(1); + } else { + + if (data[0] !== 0x00) { + self.error('payload must start with 0x00 byte', true); + return; + } + data = data.slice(1); + self.state = BODY; + + } + } + if (self.state === BINARYLENGTH) { + var i = 0; + while ((i < data.length) && (data[i] & 0x80)) { + self.messageEnd = 128 * self.messageEnd + (data[i] & 0x7f); + ++i; + } + if (i < data.length) { + self.messageEnd = 128 * self.messageEnd + (data[i] & 0x7f); + self.state = BINARYBODY; + ++i; + } + if (i > 0) + data = data.slice(i); + } + if (self.state === BINARYBODY) { + var dataleft = self.messageEnd - self.spanLength; + if (data.length >= dataleft) { + // consume the whole buffer to finish the frame + self.buffers.push(data); + self.spanLength += dataleft; + self.messageEnd = dataleft; + return self.parse(); + } + // frame's not done even if we consume it all + self.buffers.push(data); + self.spanLength += data.length; + return; + } + self.buffers.push(data); + if ((self.messageEnd = bufferIndex(data, 0xFF)) != -1) { + self.spanLength += self.messageEnd; + return self.parse(); + } + else self.spanLength += data.length; + } + while(data) data = doAdd(); +}; + +/** + * Releases all resources used by the receiver. + * + * @api public + */ + +Receiver.prototype.cleanup = function() { + this.dead = true; + this.state = EMPTY; + this.buffers = []; +}; + +/** + * Process buffered data. + * + * @api public + */ + +Receiver.prototype.parse = function() { + var output = new Buffer(this.spanLength); + var outputIndex = 0; + for (var bi = 0, bl = this.buffers.length; bi < bl - 1; ++bi) { + var buffer = this.buffers[bi]; + buffer.copy(output, outputIndex); + outputIndex += buffer.length; + } + var lastBuffer = this.buffers[this.buffers.length - 1]; + if (this.messageEnd > 0) lastBuffer.copy(output, outputIndex, 0, this.messageEnd); + if (this.state !== BODY) --this.messageEnd; + var tail = null; + if (this.messageEnd < lastBuffer.length - 1) { + tail = lastBuffer.slice(this.messageEnd + 1); + } + this.reset(); + this.ontext(output.toString('utf8')); + return tail; +}; + +/** + * Handles an error + * + * @api private + */ + +Receiver.prototype.error = function (reason, terminate) { + this.reset(); + this.onerror(reason, terminate); + return this; +}; + +/** + * Reset parser state + * + * @api private + */ + +Receiver.prototype.reset = function (reason) { + if (this.dead) return; + this.state = EMPTY; + this.buffers = []; + this.messageEnd = -1; + this.spanLength = 0; +}; + +/** + * Internal api + */ + +function bufferIndex(buffer, byte) { + for (var i = 0, l = buffer.length; i < l; ++i) { + if (buffer[i] === byte) return i; + } + return -1; +} diff --git a/node_modules/ws/lib/Receiver.js b/node_modules/ws/lib/Receiver.js new file mode 100644 index 000000000..b3183bfb4 --- /dev/null +++ b/node_modules/ws/lib/Receiver.js @@ -0,0 +1,702 @@ +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +var util = require('util') + , Validation = require('./Validation').Validation + , ErrorCodes = require('./ErrorCodes') + , BufferPool = require('./BufferPool') + , bufferUtil = require('./BufferUtil').BufferUtil + , PerMessageDeflate = require('./PerMessageDeflate'); + +/** + * HyBi Receiver implementation + */ + +function Receiver (extensions) { + if (this instanceof Receiver === false) { + throw new TypeError("Classes can't be function-called"); + } + + // memory pool for fragmented messages + var fragmentedPoolPrevUsed = -1; + this.fragmentedBufferPool = new BufferPool(1024, function(db, length) { + return db.used + length; + }, function(db) { + return fragmentedPoolPrevUsed = fragmentedPoolPrevUsed >= 0 ? + Math.ceil((fragmentedPoolPrevUsed + db.used) / 2) : + db.used; + }); + + // memory pool for unfragmented messages + var unfragmentedPoolPrevUsed = -1; + this.unfragmentedBufferPool = new BufferPool(1024, function(db, length) { + return db.used + length; + }, function(db) { + return unfragmentedPoolPrevUsed = unfragmentedPoolPrevUsed >= 0 ? + Math.ceil((unfragmentedPoolPrevUsed + db.used) / 2) : + db.used; + }); + + this.extensions = extensions || {}; + this.state = { + activeFragmentedOperation: null, + lastFragment: false, + masked: false, + opcode: 0, + fragmentedOperation: false + }; + this.overflow = []; + this.headerBuffer = new Buffer(10); + this.expectOffset = 0; + this.expectBuffer = null; + this.expectHandler = null; + this.currentMessage = []; + this.messageHandlers = []; + this.expectHeader(2, this.processPacket); + this.dead = false; + this.processing = false; + + this.onerror = function() {}; + this.ontext = function() {}; + this.onbinary = function() {}; + this.onclose = function() {}; + this.onping = function() {}; + this.onpong = function() {}; +} + +module.exports = Receiver; + +/** + * Add new data to the parser. + * + * @api public + */ + +Receiver.prototype.add = function(data) { + var dataLength = data.length; + if (dataLength == 0) return; + if (this.expectBuffer == null) { + this.overflow.push(data); + return; + } + var toRead = Math.min(dataLength, this.expectBuffer.length - this.expectOffset); + fastCopy(toRead, data, this.expectBuffer, this.expectOffset); + this.expectOffset += toRead; + if (toRead < dataLength) { + this.overflow.push(data.slice(toRead)); + } + while (this.expectBuffer && this.expectOffset == this.expectBuffer.length) { + var bufferForHandler = this.expectBuffer; + this.expectBuffer = null; + this.expectOffset = 0; + this.expectHandler.call(this, bufferForHandler); + } +}; + +/** + * Releases all resources used by the receiver. + * + * @api public + */ + +Receiver.prototype.cleanup = function() { + this.dead = true; + this.overflow = null; + this.headerBuffer = null; + this.expectBuffer = null; + this.expectHandler = null; + this.unfragmentedBufferPool = null; + this.fragmentedBufferPool = null; + this.state = null; + this.currentMessage = null; + this.onerror = null; + this.ontext = null; + this.onbinary = null; + this.onclose = null; + this.onping = null; + this.onpong = null; +}; + +/** + * Waits for a certain amount of header bytes to be available, then fires a callback. + * + * @api private + */ + +Receiver.prototype.expectHeader = function(length, handler) { + if (length == 0) { + handler(null); + return; + } + this.expectBuffer = this.headerBuffer.slice(this.expectOffset, this.expectOffset + length); + this.expectHandler = handler; + var toRead = length; + while (toRead > 0 && this.overflow.length > 0) { + var fromOverflow = this.overflow.pop(); + if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead)); + var read = Math.min(fromOverflow.length, toRead); + fastCopy(read, fromOverflow, this.expectBuffer, this.expectOffset); + this.expectOffset += read; + toRead -= read; + } +}; + +/** + * Waits for a certain amount of data bytes to be available, then fires a callback. + * + * @api private + */ + +Receiver.prototype.expectData = function(length, handler) { + if (length == 0) { + handler(null); + return; + } + this.expectBuffer = this.allocateFromPool(length, this.state.fragmentedOperation); + this.expectHandler = handler; + var toRead = length; + while (toRead > 0 && this.overflow.length > 0) { + var fromOverflow = this.overflow.pop(); + if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead)); + var read = Math.min(fromOverflow.length, toRead); + fastCopy(read, fromOverflow, this.expectBuffer, this.expectOffset); + this.expectOffset += read; + toRead -= read; + } +}; + +/** + * Allocates memory from the buffer pool. + * + * @api private + */ + +Receiver.prototype.allocateFromPool = function(length, isFragmented) { + return (isFragmented ? this.fragmentedBufferPool : this.unfragmentedBufferPool).get(length); +}; + +/** + * Start processing a new packet. + * + * @api private + */ + +Receiver.prototype.processPacket = function (data) { + if (this.extensions[PerMessageDeflate.extensionName]) { + if ((data[0] & 0x30) != 0) { + this.error('reserved fields (2, 3) must be empty', 1002); + return; + } + } else { + if ((data[0] & 0x70) != 0) { + this.error('reserved fields must be empty', 1002); + return; + } + } + this.state.lastFragment = (data[0] & 0x80) == 0x80; + this.state.masked = (data[1] & 0x80) == 0x80; + var compressed = (data[0] & 0x40) == 0x40; + var opcode = data[0] & 0xf; + if (opcode === 0) { + if (compressed) { + this.error('continuation frame cannot have the Per-message Compressed bits', 1002); + return; + } + // continuation frame + this.state.fragmentedOperation = true; + this.state.opcode = this.state.activeFragmentedOperation; + if (!(this.state.opcode == 1 || this.state.opcode == 2)) { + this.error('continuation frame cannot follow current opcode', 1002); + return; + } + } + else { + if (opcode < 3 && this.state.activeFragmentedOperation != null) { + this.error('data frames after the initial data frame must have opcode 0', 1002); + return; + } + if (opcode >= 8 && compressed) { + this.error('control frames cannot have the Per-message Compressed bits', 1002); + return; + } + this.state.compressed = compressed; + this.state.opcode = opcode; + if (this.state.lastFragment === false) { + this.state.fragmentedOperation = true; + this.state.activeFragmentedOperation = opcode; + } + else this.state.fragmentedOperation = false; + } + var handler = opcodes[this.state.opcode]; + if (typeof handler == 'undefined') this.error('no handler for opcode ' + this.state.opcode, 1002); + else { + handler.start.call(this, data); + } +}; + +/** + * Endprocessing a packet. + * + * @api private + */ + +Receiver.prototype.endPacket = function() { + if (!this.state.fragmentedOperation) this.unfragmentedBufferPool.reset(true); + else if (this.state.lastFragment) this.fragmentedBufferPool.reset(true); + 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.expectHeader(2, this.processPacket); +}; + +/** + * Reset the parser state. + * + * @api private + */ + +Receiver.prototype.reset = function() { + if (this.dead) return; + this.state = { + activeFragmentedOperation: null, + lastFragment: false, + masked: false, + opcode: 0, + fragmentedOperation: false + }; + this.fragmentedBufferPool.reset(true); + this.unfragmentedBufferPool.reset(true); + this.expectOffset = 0; + this.expectBuffer = null; + this.expectHandler = null; + this.overflow = []; + this.currentMessage = []; + this.messageHandlers = []; +}; + +/** + * Unmask received data. + * + * @api private + */ + +Receiver.prototype.unmask = function (mask, buf, binary) { + if (mask != null && buf != null) bufferUtil.unmask(buf, mask); + if (binary) return buf; + return buf != null ? buf.toString('utf8') : ''; +}; + +/** + * Concatenates a list of buffers. + * + * @api private + */ + +Receiver.prototype.concatBuffers = function(buffers) { + var length = 0; + for (var i = 0, l = buffers.length; i < l; ++i) length += buffers[i].length; + var mergedBuffer = new Buffer(length); + bufferUtil.merge(mergedBuffer, buffers); + return mergedBuffer; +}; + +/** + * Handles an error + * + * @api private + */ + +Receiver.prototype.error = function (reason, protocolErrorCode) { + this.reset(); + this.onerror(reason, protocolErrorCode); + return this; +}; + +/** + * Execute message handler buffers + * + * @api private + */ + +Receiver.prototype.flush = function() { + if (this.processing || this.dead) return; + + var handler = this.messageHandlers.shift(); + if (!handler) return; + + this.processing = true; + var self = this; + + handler(function() { + self.processing = false; + self.flush(); + }); +}; + +/** + * Apply extensions to message + * + * @api private + */ + +Receiver.prototype.applyExtensions = function(messageBuffer, fin, compressed, callback) { + var self = this; + if (compressed) { + this.extensions[PerMessageDeflate.extensionName].decompress(messageBuffer, fin, function(err, buffer) { + if (self.dead) return; + if (err) { + callback(new Error('invalid compressed data')); + return; + } + callback(null, buffer); + }); + } else { + callback(null, messageBuffer); + } +}; + +/** + * Buffer utilities + */ + +function readUInt16BE(start) { + return (this[start]<<8) + + this[start+1]; +} + +function readUInt32BE(start) { + return (this[start]<<24) + + (this[start+1]<<16) + + (this[start+2]<<8) + + this[start+3]; +} + +function fastCopy(length, srcBuffer, dstBuffer, dstOffset) { + switch (length) { + default: srcBuffer.copy(dstBuffer, dstOffset, 0, length); break; + case 16: dstBuffer[dstOffset+15] = srcBuffer[15]; + case 15: dstBuffer[dstOffset+14] = srcBuffer[14]; + case 14: dstBuffer[dstOffset+13] = srcBuffer[13]; + case 13: dstBuffer[dstOffset+12] = srcBuffer[12]; + case 12: dstBuffer[dstOffset+11] = srcBuffer[11]; + case 11: dstBuffer[dstOffset+10] = srcBuffer[10]; + case 10: dstBuffer[dstOffset+9] = srcBuffer[9]; + case 9: dstBuffer[dstOffset+8] = srcBuffer[8]; + case 8: dstBuffer[dstOffset+7] = srcBuffer[7]; + case 7: dstBuffer[dstOffset+6] = srcBuffer[6]; + case 6: dstBuffer[dstOffset+5] = srcBuffer[5]; + case 5: dstBuffer[dstOffset+4] = srcBuffer[4]; + case 4: dstBuffer[dstOffset+3] = srcBuffer[3]; + case 3: dstBuffer[dstOffset+2] = srcBuffer[2]; + case 2: dstBuffer[dstOffset+1] = srcBuffer[1]; + case 1: dstBuffer[dstOffset] = srcBuffer[0]; + } +} + +function clone(obj) { + var cloned = {}; + for (var k in obj) { + if (obj.hasOwnProperty(k)) { + cloned[k] = obj[k]; + } + } + return cloned; +} + +/** + * Opcode handlers + */ + +var opcodes = { + // text + '1': { + start: function(data) { + var self = this; + // decode length + var firstLength = data[1] & 0x7f; + if (firstLength < 126) { + opcodes['1'].getData.call(self, firstLength); + } + else if (firstLength == 126) { + self.expectHeader(2, function(data) { + opcodes['1'].getData.call(self, readUInt16BE.call(data, 0)); + }); + } + else if (firstLength == 127) { + self.expectHeader(8, function(data) { + if (readUInt32BE.call(data, 0) != 0) { + self.error('packets with length spanning more than 32 bit is currently not supported', 1008); + return; + } + opcodes['1'].getData.call(self, readUInt32BE.call(data, 4)); + }); + } + }, + getData: function(length) { + var self = this; + if (self.state.masked) { + self.expectHeader(4, function(data) { + var mask = data; + self.expectData(length, function(data) { + opcodes['1'].finish.call(self, mask, data); + }); + }); + } + else { + self.expectData(length, function(data) { + opcodes['1'].finish.call(self, null, data); + }); + } + }, + finish: function(mask, data) { + var self = this; + var packet = this.unmask(mask, data, true) || new Buffer(0); + var state = clone(this.state); + this.messageHandlers.push(function(callback) { + self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) { + if (err) return self.error(err.message, 1007); + if (buffer != null) self.currentMessage.push(buffer); + + if (state.lastFragment) { + var messageBuffer = self.concatBuffers(self.currentMessage); + self.currentMessage = []; + if (!Validation.isValidUTF8(messageBuffer)) { + self.error('invalid utf8 sequence', 1007); + return; + } + self.ontext(messageBuffer.toString('utf8'), {masked: state.masked, buffer: messageBuffer}); + } + callback(); + }); + }); + this.flush(); + this.endPacket(); + } + }, + // binary + '2': { + start: function(data) { + var self = this; + // decode length + var firstLength = data[1] & 0x7f; + if (firstLength < 126) { + opcodes['2'].getData.call(self, firstLength); + } + else if (firstLength == 126) { + self.expectHeader(2, function(data) { + opcodes['2'].getData.call(self, readUInt16BE.call(data, 0)); + }); + } + else if (firstLength == 127) { + self.expectHeader(8, function(data) { + if (readUInt32BE.call(data, 0) != 0) { + self.error('packets with length spanning more than 32 bit is currently not supported', 1008); + return; + } + opcodes['2'].getData.call(self, readUInt32BE.call(data, 4, true)); + }); + } + }, + getData: function(length) { + var self = this; + if (self.state.masked) { + self.expectHeader(4, function(data) { + var mask = data; + self.expectData(length, function(data) { + opcodes['2'].finish.call(self, mask, data); + }); + }); + } + else { + self.expectData(length, function(data) { + opcodes['2'].finish.call(self, null, data); + }); + } + }, + finish: function(mask, data) { + var self = this; + var packet = this.unmask(mask, data, true) || new Buffer(0); + var state = clone(this.state); + this.messageHandlers.push(function(callback) { + self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) { + if (err) return self.error(err.message, 1007); + if (buffer != null) self.currentMessage.push(buffer); + if (state.lastFragment) { + var messageBuffer = self.concatBuffers(self.currentMessage); + self.currentMessage = []; + self.onbinary(messageBuffer, {masked: state.masked, buffer: messageBuffer}); + } + callback(); + }); + }); + this.flush(); + this.endPacket(); + } + }, + // close + '8': { + start: function(data) { + var self = this; + if (self.state.lastFragment == false) { + self.error('fragmented close is not supported', 1002); + return; + } + + // decode length + var firstLength = data[1] & 0x7f; + if (firstLength < 126) { + opcodes['8'].getData.call(self, firstLength); + } + else { + self.error('control frames cannot have more than 125 bytes of data', 1002); + } + }, + getData: function(length) { + var self = this; + if (self.state.masked) { + self.expectHeader(4, function(data) { + var mask = data; + self.expectData(length, function(data) { + opcodes['8'].finish.call(self, mask, data); + }); + }); + } + else { + self.expectData(length, function(data) { + opcodes['8'].finish.call(self, null, data); + }); + } + }, + finish: function(mask, data) { + var self = this; + data = self.unmask(mask, data, true); + + var state = clone(this.state); + this.messageHandlers.push(function() { + if (data && data.length == 1) { + self.error('close packets with data must be at least two bytes long', 1002); + return; + } + var code = data && data.length > 1 ? readUInt16BE.call(data, 0) : 1000; + if (!ErrorCodes.isValidErrorCode(code)) { + self.error('invalid error code', 1002); + return; + } + var message = ''; + if (data && data.length > 2) { + var messageBuffer = data.slice(2); + if (!Validation.isValidUTF8(messageBuffer)) { + self.error('invalid utf8 sequence', 1007); + return; + } + message = messageBuffer.toString('utf8'); + } + self.onclose(code, message, {masked: state.masked}); + self.reset(); + }); + this.flush(); + }, + }, + // ping + '9': { + start: function(data) { + var self = this; + if (self.state.lastFragment == false) { + self.error('fragmented ping is not supported', 1002); + return; + } + + // decode length + var firstLength = data[1] & 0x7f; + if (firstLength < 126) { + opcodes['9'].getData.call(self, firstLength); + } + else { + self.error('control frames cannot have more than 125 bytes of data', 1002); + } + }, + getData: function(length) { + var self = this; + if (self.state.masked) { + self.expectHeader(4, function(data) { + var mask = data; + self.expectData(length, function(data) { + opcodes['9'].finish.call(self, mask, data); + }); + }); + } + else { + self.expectData(length, function(data) { + opcodes['9'].finish.call(self, null, data); + }); + } + }, + finish: function(mask, data) { + var self = this; + data = this.unmask(mask, data, true); + var state = clone(this.state); + this.messageHandlers.push(function(callback) { + self.onping(data, {masked: state.masked, binary: true}); + callback(); + }); + this.flush(); + this.endPacket(); + } + }, + // pong + '10': { + start: function(data) { + var self = this; + if (self.state.lastFragment == false) { + self.error('fragmented pong is not supported', 1002); + return; + } + + // decode length + var firstLength = data[1] & 0x7f; + if (firstLength < 126) { + opcodes['10'].getData.call(self, firstLength); + } + else { + self.error('control frames cannot have more than 125 bytes of data', 1002); + } + }, + getData: function(length) { + var self = this; + if (this.state.masked) { + this.expectHeader(4, function(data) { + var mask = data; + self.expectData(length, function(data) { + opcodes['10'].finish.call(self, mask, data); + }); + }); + } + else { + this.expectData(length, function(data) { + opcodes['10'].finish.call(self, null, data); + }); + } + }, + finish: function(mask, data) { + var self = this; + data = self.unmask(mask, data, true); + var state = clone(this.state); + this.messageHandlers.push(function(callback) { + self.onpong(data, {masked: state.masked, binary: true}); + callback(); + }); + this.flush(); + this.endPacket(); + } + } +} diff --git a/node_modules/ws/lib/Sender.hixie.js b/node_modules/ws/lib/Sender.hixie.js new file mode 100644 index 000000000..b87d9dd93 --- /dev/null +++ b/node_modules/ws/lib/Sender.hixie.js @@ -0,0 +1,124 @@ +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +var events = require('events') + , util = require('util') + , EventEmitter = events.EventEmitter; + +/** + * Hixie Sender implementation + */ + +function Sender(socket) { + if (this instanceof Sender === false) { + throw new TypeError("Classes can't be function-called"); + } + + events.EventEmitter.call(this); + + this.socket = socket; + this.continuationFrame = false; + this.isClosed = false; +} + +module.exports = Sender; + +/** + * Inherits from EventEmitter. + */ + +util.inherits(Sender, events.EventEmitter); + +/** + * Frames and writes data. + * + * @api public + */ + +Sender.prototype.send = function(data, options, cb) { + if (this.isClosed) return; + + var isString = typeof data == 'string' + , length = isString ? Buffer.byteLength(data) : data.length + , lengthbytes = (length > 127) ? 2 : 1 // assume less than 2**14 bytes + , writeStartMarker = this.continuationFrame == false + , writeEndMarker = !options || !(typeof options.fin != 'undefined' && !options.fin) + , buffer = new Buffer((writeStartMarker ? ((options && options.binary) ? (1 + lengthbytes) : 1) : 0) + length + ((writeEndMarker && !(options && options.binary)) ? 1 : 0)) + , offset = writeStartMarker ? 1 : 0; + + if (writeStartMarker) { + if (options && options.binary) { + buffer.write('\x80', 'binary'); + // assume length less than 2**14 bytes + if (lengthbytes > 1) + buffer.write(String.fromCharCode(128+length/128), offset++, 'binary'); + buffer.write(String.fromCharCode(length&0x7f), offset++, 'binary'); + } else + buffer.write('\x00', 'binary'); + } + + if (isString) buffer.write(data, offset, 'utf8'); + else data.copy(buffer, offset, 0); + + if (writeEndMarker) { + if (options && options.binary) { + // sending binary, not writing end marker + } else + buffer.write('\xff', offset + length, 'binary'); + this.continuationFrame = false; + } + else this.continuationFrame = true; + + try { + this.socket.write(buffer, 'binary', cb); + } catch (e) { + this.error(e.toString()); + } +}; + +/** + * Sends a close instruction to the remote party. + * + * @api public + */ + +Sender.prototype.close = function(code, data, mask, cb) { + if (this.isClosed) return; + this.isClosed = true; + try { + if (this.continuationFrame) this.socket.write(new Buffer([0xff], 'binary')); + this.socket.write(new Buffer([0xff, 0x00]), 'binary', cb); + } catch (e) { + this.error(e.toString()); + } +}; + +/** + * Sends a ping message to the remote party. Not available for hixie. + * + * @api public + */ + +Sender.prototype.ping = function(data, options) {}; + +/** + * Sends a pong message to the remote party. Not available for hixie. + * + * @api public + */ + +Sender.prototype.pong = function(data, options) {}; + +/** + * Handles an error + * + * @api private + */ + +Sender.prototype.error = function (reason) { + this.emit('error', reason); + return this; +}; diff --git a/node_modules/ws/lib/Sender.js b/node_modules/ws/lib/Sender.js new file mode 100644 index 000000000..d34061e07 --- /dev/null +++ b/node_modules/ws/lib/Sender.js @@ -0,0 +1,324 @@ +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +var events = require('events') + , util = require('util') + , EventEmitter = events.EventEmitter + , ErrorCodes = require('./ErrorCodes') + , bufferUtil = require('./BufferUtil').BufferUtil + , PerMessageDeflate = require('./PerMessageDeflate'); + +/** + * HyBi Sender implementation + */ + +function Sender(socket, extensions) { + if (this instanceof Sender === false) { + throw new TypeError("Classes can't be function-called"); + } + + events.EventEmitter.call(this); + + this._socket = socket; + this.extensions = extensions || {}; + this.firstFragment = true; + this.compress = false; + this.messageHandlers = []; + this.processing = false; +} + +/** + * Inherits from EventEmitter. + */ + +util.inherits(Sender, events.EventEmitter); + +/** + * Sends a close instruction to the remote party. + * + * @api public + */ + +Sender.prototype.close = function(code, data, mask, cb) { + if (typeof code !== 'undefined') { + if (typeof code !== 'number' || + !ErrorCodes.isValidErrorCode(code)) throw new Error('first argument must be a valid error code number'); + } + code = code || 1000; + var dataBuffer = new Buffer(2 + (data ? Buffer.byteLength(data) : 0)); + writeUInt16BE.call(dataBuffer, code, 0); + if (dataBuffer.length > 2) dataBuffer.write(data, 2); + + var self = this; + this.messageHandlers.push(function(callback) { + self.frameAndSend(0x8, dataBuffer, true, mask); + callback(); + if (typeof cb == 'function') cb(); + }); + this.flush(); +}; + +/** + * Sends a ping message to the remote party. + * + * @api public + */ + +Sender.prototype.ping = function(data, options) { + var mask = options && options.mask; + var self = this; + this.messageHandlers.push(function(callback) { + self.frameAndSend(0x9, data || '', true, mask); + callback(); + }); + this.flush(); +}; + +/** + * Sends a pong message to the remote party. + * + * @api public + */ + +Sender.prototype.pong = function(data, options) { + var mask = options && options.mask; + var self = this; + this.messageHandlers.push(function(callback) { + self.frameAndSend(0xa, data || '', true, mask); + callback(); + }); + this.flush(); +}; + +/** + * Sends text or binary data to the remote party. + * + * @api public + */ + +Sender.prototype.send = function(data, options, cb) { + var finalFragment = options && options.fin === false ? false : true; + var mask = options && options.mask; + var compress = options && options.compress; + var opcode = options && options.binary ? 2 : 1; + if (this.firstFragment === false) { + opcode = 0; + compress = false; + } else { + this.firstFragment = false; + this.compress = compress; + } + if (finalFragment) this.firstFragment = true + + var compressFragment = this.compress; + + var self = this; + this.messageHandlers.push(function(callback) { + self.applyExtensions(data, finalFragment, compressFragment, function(err, data) { + if (err) { + if (typeof cb == 'function') cb(err); + else self.emit('error', err); + return; + } + self.frameAndSend(opcode, data, finalFragment, mask, compress, cb); + callback(); + }); + }); + this.flush(); +}; + +/** + * Frames and sends a piece of data according to the HyBi WebSocket protocol. + * + * @api private + */ + +Sender.prototype.frameAndSend = function(opcode, data, finalFragment, maskData, compressed, cb) { + var canModifyData = false; + + if (!data) { + try { + this._socket.write(new Buffer([opcode | (finalFragment ? 0x80 : 0), 0 | (maskData ? 0x80 : 0)].concat(maskData ? [0, 0, 0, 0] : [])), 'binary', cb); + } + catch (e) { + if (typeof cb == 'function') cb(e); + else this.emit('error', e); + } + return; + } + + if (!Buffer.isBuffer(data)) { + canModifyData = true; + if (data && (typeof data.byteLength !== 'undefined' || typeof data.buffer !== 'undefined')) { + data = getArrayBuffer(data); + } else { + // + // If people want to send a number, this would allocate the number in + // bytes as memory size instead of storing the number as buffer value. So + // we need to transform it to string in order to prevent possible + // vulnerabilities / memory attacks. + // + if (typeof data === 'number') data = data.toString(); + + data = new Buffer(data); + } + } + + var dataLength = data.length + , dataOffset = maskData ? 6 : 2 + , secondByte = dataLength; + + if (dataLength >= 65536) { + dataOffset += 8; + secondByte = 127; + } + else if (dataLength > 125) { + dataOffset += 2; + secondByte = 126; + } + + var mergeBuffers = dataLength < 32768 || (maskData && !canModifyData); + var totalLength = mergeBuffers ? dataLength + dataOffset : dataOffset; + var outputBuffer = new Buffer(totalLength); + outputBuffer[0] = finalFragment ? opcode | 0x80 : opcode; + if (compressed) outputBuffer[0] |= 0x40; + + switch (secondByte) { + case 126: + writeUInt16BE.call(outputBuffer, dataLength, 2); + break; + case 127: + writeUInt32BE.call(outputBuffer, 0, 2); + writeUInt32BE.call(outputBuffer, dataLength, 6); + } + + if (maskData) { + outputBuffer[1] = secondByte | 0x80; + var mask = this._randomMask || (this._randomMask = getRandomMask()); + outputBuffer[dataOffset - 4] = mask[0]; + outputBuffer[dataOffset - 3] = mask[1]; + outputBuffer[dataOffset - 2] = mask[2]; + outputBuffer[dataOffset - 1] = mask[3]; + if (mergeBuffers) { + bufferUtil.mask(data, mask, outputBuffer, dataOffset, dataLength); + try { + this._socket.write(outputBuffer, 'binary', cb); + } + catch (e) { + if (typeof cb == 'function') cb(e); + else this.emit('error', e); + } + } + else { + bufferUtil.mask(data, mask, data, 0, dataLength); + try { + this._socket.write(outputBuffer, 'binary'); + this._socket.write(data, 'binary', cb); + } + catch (e) { + if (typeof cb == 'function') cb(e); + else this.emit('error', e); + } + } + } + else { + outputBuffer[1] = secondByte; + if (mergeBuffers) { + data.copy(outputBuffer, dataOffset); + try { + this._socket.write(outputBuffer, 'binary', cb); + } + catch (e) { + if (typeof cb == 'function') cb(e); + else this.emit('error', e); + } + } + else { + try { + this._socket.write(outputBuffer, 'binary'); + this._socket.write(data, 'binary', cb); + } + catch (e) { + if (typeof cb == 'function') cb(e); + else this.emit('error', e); + } + } + } +}; + +/** + * Execute message handler buffers + * + * @api private + */ + +Sender.prototype.flush = function() { + if (this.processing) return; + + var handler = this.messageHandlers.shift(); + if (!handler) return; + + this.processing = true; + + var self = this; + + handler(function() { + self.processing = false; + self.flush(); + }); +}; + +/** + * Apply extensions to message + * + * @api private + */ + +Sender.prototype.applyExtensions = function(data, fin, compress, callback) { + if (compress && data) { + if ((data.buffer || data) instanceof ArrayBuffer) { + data = getArrayBuffer(data); + } + this.extensions[PerMessageDeflate.extensionName].compress(data, fin, callback); + } else { + callback(null, data); + } +}; + +module.exports = Sender; + +function writeUInt16BE(value, offset) { + this[offset] = (value & 0xff00)>>8; + this[offset+1] = value & 0xff; +} + +function writeUInt32BE(value, offset) { + this[offset] = (value & 0xff000000)>>24; + this[offset+1] = (value & 0xff0000)>>16; + this[offset+2] = (value & 0xff00)>>8; + this[offset+3] = value & 0xff; +} + +function getArrayBuffer(data) { + // data is either an ArrayBuffer or ArrayBufferView. + var array = new Uint8Array(data.buffer || data) + , l = data.byteLength || data.length + , o = data.byteOffset || 0 + , buffer = new Buffer(l); + for (var i = 0; i < l; ++i) { + buffer[i] = array[o+i]; + } + return buffer; +} + +function getRandomMask() { + return new Buffer([ + ~~(Math.random() * 255), + ~~(Math.random() * 255), + ~~(Math.random() * 255), + ~~(Math.random() * 255) + ]); +} diff --git a/node_modules/ws/lib/Validation.fallback.js b/node_modules/ws/lib/Validation.fallback.js new file mode 100644 index 000000000..2c7c4fd48 --- /dev/null +++ b/node_modules/ws/lib/Validation.fallback.js @@ -0,0 +1,12 @@ +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +module.exports.Validation = { + isValidUTF8: function(buffer) { + return true; + } +}; + diff --git a/node_modules/ws/lib/Validation.js b/node_modules/ws/lib/Validation.js new file mode 100644 index 000000000..0795fb7f0 --- /dev/null +++ b/node_modules/ws/lib/Validation.js @@ -0,0 +1,13 @@ +'use strict'; + +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +try { + module.exports = require('utf-8-validate'); +} catch (e) { + module.exports = require('./Validation.fallback'); +} diff --git a/node_modules/ws/lib/WebSocket.js b/node_modules/ws/lib/WebSocket.js new file mode 100644 index 000000000..4e06c8071 --- /dev/null +++ b/node_modules/ws/lib/WebSocket.js @@ -0,0 +1,965 @@ +'use strict'; + +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +var url = require('url') + , util = require('util') + , http = require('http') + , https = require('https') + , crypto = require('crypto') + , stream = require('stream') + , Ultron = require('ultron') + , Options = require('options') + , Sender = require('./Sender') + , Receiver = require('./Receiver') + , SenderHixie = require('./Sender.hixie') + , ReceiverHixie = require('./Receiver.hixie') + , Extensions = require('./Extensions') + , PerMessageDeflate = require('./PerMessageDeflate') + , EventEmitter = require('events').EventEmitter; + +/** + * Constants + */ + +// Default protocol version + +var protocolVersion = 13; + +// Close timeout + +var closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly + +/** + * WebSocket implementation + * + * @constructor + * @param {String} address Connection address. + * @param {String|Array} protocols WebSocket protocols. + * @param {Object} options Additional connection options. + * @api public + */ +function WebSocket(address, protocols, options) { + if (this instanceof WebSocket === false) { + return new WebSocket(address, protocols, options); + } + + EventEmitter.call(this); + + if (protocols && !Array.isArray(protocols) && 'object' === typeof protocols) { + // accept the "options" Object as the 2nd argument + options = protocols; + protocols = null; + } + + if ('string' === typeof protocols) { + protocols = [ protocols ]; + } + + if (!Array.isArray(protocols)) { + protocols = []; + } + + this._socket = null; + this._ultron = null; + this._closeReceived = false; + this.bytesReceived = 0; + this.readyState = null; + this.supports = {}; + this.extensions = {}; + + if (Array.isArray(address)) { + initAsServerClient.apply(this, address.concat(options)); + } else { + initAsClient.apply(this, [address, protocols, options]); + } +} + +/** + * Inherits from EventEmitter. + */ +util.inherits(WebSocket, EventEmitter); + +/** + * Ready States + */ +["CONNECTING", "OPEN", "CLOSING", "CLOSED"].forEach(function each(state, index) { + WebSocket.prototype[state] = WebSocket[state] = index; +}); + +/** + * Gracefully closes the connection, after sending a description message to the server + * + * @param {Object} data to be sent to the server + * @api public + */ +WebSocket.prototype.close = function close(code, data) { + if (this.readyState === WebSocket.CLOSED) return; + + if (this.readyState === WebSocket.CONNECTING) { + this.readyState = WebSocket.CLOSED; + return; + } + + if (this.readyState === WebSocket.CLOSING) { + if (this._closeReceived && this._isServer) { + this.terminate(); + } + return; + } + + var self = this; + try { + this.readyState = WebSocket.CLOSING; + this._closeCode = code; + this._closeMessage = data; + var mask = !this._isServer; + this._sender.close(code, data, mask, function(err) { + if (err) self.emit('error', err); + + if (self._closeReceived && self._isServer) { + self.terminate(); + } else { + // ensure that the connection is cleaned up even when no response of closing handshake. + clearTimeout(self._closeTimer); + self._closeTimer = setTimeout(cleanupWebsocketResources.bind(self, true), closeTimeout); + } + }); + } catch (e) { + this.emit('error', e); + } +}; + +/** + * Pause the client stream + * + * @api public + */ +WebSocket.prototype.pause = function pauser() { + if (this.readyState !== WebSocket.OPEN) throw new Error('not opened'); + + return this._socket.pause(); +}; + +/** + * Sends a ping + * + * @param {Object} data to be sent to the server + * @param {Object} Members - mask: boolean, binary: boolean + * @param {boolean} dontFailWhenClosed indicates whether or not to throw if the connection isnt open + * @api public + */ +WebSocket.prototype.ping = function ping(data, options, dontFailWhenClosed) { + if (this.readyState !== WebSocket.OPEN) { + if (dontFailWhenClosed === true) return; + throw new Error('not opened'); + } + + options = options || {}; + + if (typeof options.mask === 'undefined') options.mask = !this._isServer; + + this._sender.ping(data, options); +}; + +/** + * Sends a pong + * + * @param {Object} data to be sent to the server + * @param {Object} Members - mask: boolean, binary: boolean + * @param {boolean} dontFailWhenClosed indicates whether or not to throw if the connection isnt open + * @api public + */ +WebSocket.prototype.pong = function(data, options, dontFailWhenClosed) { + if (this.readyState !== WebSocket.OPEN) { + if (dontFailWhenClosed === true) return; + throw new Error('not opened'); + } + + options = options || {}; + + if (typeof options.mask === 'undefined') options.mask = !this._isServer; + + this._sender.pong(data, options); +}; + +/** + * Resume the client stream + * + * @api public + */ +WebSocket.prototype.resume = function resume() { + if (this.readyState !== WebSocket.OPEN) throw new Error('not opened'); + + return this._socket.resume(); +}; + +/** + * Sends a piece of data + * + * @param {Object} data to be sent to the server + * @param {Object} Members - mask: boolean, binary: boolean, compress: boolean + * @param {function} Optional callback which is executed after the send completes + * @api public + */ + +WebSocket.prototype.send = function send(data, options, cb) { + if (typeof options === 'function') { + cb = options; + options = {}; + } + + if (this.readyState !== WebSocket.OPEN) { + if (typeof cb === 'function') cb(new Error('not opened')); + else throw new Error('not opened'); + return; + } + + if (!data) data = ''; + if (this._queue) { + var self = this; + this._queue.push(function() { self.send(data, options, cb); }); + return; + } + + options = options || {}; + options.fin = true; + + if (typeof options.binary === 'undefined') { + options.binary = (data instanceof ArrayBuffer || data instanceof Buffer || + data instanceof Uint8Array || + data instanceof Uint16Array || + data instanceof Uint32Array || + data instanceof Int8Array || + data instanceof Int16Array || + data instanceof Int32Array || + data instanceof Float32Array || + data instanceof Float64Array); + } + + if (typeof options.mask === 'undefined') options.mask = !this._isServer; + if (typeof options.compress === 'undefined') options.compress = true; + if (!this.extensions[PerMessageDeflate.extensionName]) { + options.compress = false; + } + + var readable = typeof stream.Readable === 'function' + ? stream.Readable + : stream.Stream; + + if (data instanceof readable) { + startQueue(this); + var self = this; + + sendStream(this, data, options, function send(error) { + process.nextTick(function tock() { + executeQueueSends(self); + }); + + if (typeof cb === 'function') cb(error); + }); + } else { + this._sender.send(data, options, cb); + } +}; + +/** + * Streams data through calls to a user supplied function + * + * @param {Object} Members - mask: boolean, binary: boolean, compress: boolean + * @param {function} 'function (error, send)' which is executed on successive ticks of which send is 'function (data, final)'. + * @api public + */ +WebSocket.prototype.stream = function stream(options, cb) { + if (typeof options === 'function') { + cb = options; + options = {}; + } + + var self = this; + + if (typeof cb !== 'function') throw new Error('callback must be provided'); + + if (this.readyState !== WebSocket.OPEN) { + if (typeof cb === 'function') cb(new Error('not opened')); + else throw new Error('not opened'); + return; + } + + if (this._queue) { + this._queue.push(function () { self.stream(options, cb); }); + return; + } + + options = options || {}; + + if (typeof options.mask === 'undefined') options.mask = !this._isServer; + if (typeof options.compress === 'undefined') options.compress = true; + if (!this.extensions[PerMessageDeflate.extensionName]) { + options.compress = false; + } + + startQueue(this); + + function send(data, final) { + try { + if (self.readyState !== WebSocket.OPEN) throw new Error('not opened'); + options.fin = final === true; + self._sender.send(data, options); + if (!final) process.nextTick(cb.bind(null, null, send)); + else executeQueueSends(self); + } catch (e) { + if (typeof cb === 'function') cb(e); + else { + delete self._queue; + self.emit('error', e); + } + } + } + + process.nextTick(cb.bind(null, null, send)); +}; + +/** + * Immediately shuts down the connection + * + * @api public + */ +WebSocket.prototype.terminate = function terminate() { + if (this.readyState === WebSocket.CLOSED) return; + + if (this._socket) { + this.readyState = WebSocket.CLOSING; + + // End the connection + try { this._socket.end(); } + catch (e) { + // Socket error during end() call, so just destroy it right now + cleanupWebsocketResources.call(this, true); + return; + } + + // Add a timeout to ensure that the connection is completely + // cleaned up within 30 seconds, even if the clean close procedure + // fails for whatever reason + // First cleanup any pre-existing timeout from an earlier "terminate" call, + // if one exists. Otherwise terminate calls in quick succession will leak timeouts + // and hold the program open for `closeTimout` time. + if (this._closeTimer) { clearTimeout(this._closeTimer); } + this._closeTimer = setTimeout(cleanupWebsocketResources.bind(this, true), closeTimeout); + } else if (this.readyState === WebSocket.CONNECTING) { + cleanupWebsocketResources.call(this, true); + } +}; + +/** + * Expose bufferedAmount + * + * @api public + */ +Object.defineProperty(WebSocket.prototype, 'bufferedAmount', { + get: function get() { + var amount = 0; + if (this._socket) { + amount = this._socket.bufferSize || 0; + } + return amount; + } +}); + +/** + * Emulates the W3C Browser based WebSocket interface using function members. + * + * @see http://dev.w3.org/html5/websockets/#the-websocket-interface + * @api public + */ +['open', 'error', 'close', 'message'].forEach(function(method) { + Object.defineProperty(WebSocket.prototype, 'on' + method, { + /** + * Returns the current listener + * + * @returns {Mixed} the set function or undefined + * @api public + */ + get: function get() { + var listener = this.listeners(method)[0]; + return listener ? (listener._listener ? listener._listener : listener) : undefined; + }, + + /** + * Start listening for events + * + * @param {Function} listener the listener + * @returns {Mixed} the set function or undefined + * @api public + */ + set: function set(listener) { + this.removeAllListeners(method); + this.addEventListener(method, listener); + } + }); +}); + +/** + * Emulates the W3C Browser based WebSocket interface using addEventListener. + * + * @see https://developer.mozilla.org/en/DOM/element.addEventListener + * @see http://dev.w3.org/html5/websockets/#the-websocket-interface + * @api public + */ +WebSocket.prototype.addEventListener = function(method, listener) { + var target = this; + + function onMessage (data, flags) { + listener.call(target, new MessageEvent(data, !!flags.binary, target)); + } + + function onClose (code, message) { + listener.call(target, new CloseEvent(code, message, target)); + } + + function onError (event) { + event.type = 'error'; + event.target = target; + listener.call(target, event); + } + + function onOpen () { + listener.call(target, new OpenEvent(target)); + } + + if (typeof listener === 'function') { + if (method === 'message') { + // store a reference so we can return the original function from the + // addEventListener hook + onMessage._listener = listener; + this.on(method, onMessage); + } else if (method === 'close') { + // store a reference so we can return the original function from the + // addEventListener hook + onClose._listener = listener; + this.on(method, onClose); + } else if (method === 'error') { + // store a reference so we can return the original function from the + // addEventListener hook + onError._listener = listener; + this.on(method, onError); + } else if (method === 'open') { + // store a reference so we can return the original function from the + // addEventListener hook + onOpen._listener = listener; + this.on(method, onOpen); + } else { + this.on(method, listener); + } + } +}; + +module.exports = WebSocket; +module.exports.buildHostHeader = buildHostHeader + +/** + * W3C MessageEvent + * + * @see http://www.w3.org/TR/html5/comms.html + * @constructor + * @api private + */ +function MessageEvent(dataArg, isBinary, target) { + this.type = 'message'; + this.data = dataArg; + this.target = target; + this.binary = isBinary; // non-standard. +} + +/** + * W3C CloseEvent + * + * @see http://www.w3.org/TR/html5/comms.html + * @constructor + * @api private + */ +function CloseEvent(code, reason, target) { + this.type = 'close'; + this.wasClean = (typeof code === 'undefined' || code === 1000); + this.code = code; + this.reason = reason; + this.target = target; +} + +/** + * W3C OpenEvent + * + * @see http://www.w3.org/TR/html5/comms.html + * @constructor + * @api private + */ +function OpenEvent(target) { + this.type = 'open'; + this.target = target; +} + +// Append port number to Host header, only if specified in the url +// and non-default +function buildHostHeader(isSecure, hostname, port) { + var headerHost = hostname; + if (hostname) { + if ((isSecure && (port != 443)) || (!isSecure && (port != 80))){ + headerHost = headerHost + ':' + port; + } + } + return headerHost; +} + +/** + * Entirely private apis, + * which may or may not be bound to a sepcific WebSocket instance. + */ +function initAsServerClient(req, socket, upgradeHead, options) { + options = new Options({ + protocolVersion: protocolVersion, + protocol: null, + extensions: {} + }).merge(options); + + // expose state properties + this.protocol = options.value.protocol; + this.protocolVersion = options.value.protocolVersion; + this.extensions = options.value.extensions; + this.supports.binary = (this.protocolVersion !== 'hixie-76'); + this.upgradeReq = req; + this.readyState = WebSocket.CONNECTING; + this._isServer = true; + + // establish connection + if (options.value.protocolVersion === 'hixie-76') { + establishConnection.call(this, ReceiverHixie, SenderHixie, socket, upgradeHead); + } else { + establishConnection.call(this, Receiver, Sender, socket, upgradeHead); + } +} + +function initAsClient(address, protocols, options) { + options = new Options({ + origin: null, + protocolVersion: protocolVersion, + host: null, + headers: null, + protocol: protocols.join(','), + agent: null, + + // ssl-related options + pfx: null, + key: null, + passphrase: null, + cert: null, + ca: null, + ciphers: null, + rejectUnauthorized: null, + perMessageDeflate: true, + localAddress: null + }).merge(options); + + if (options.value.protocolVersion !== 8 && options.value.protocolVersion !== 13) { + throw new Error('unsupported protocol version'); + } + + // verify URL and establish http class + var serverUrl = url.parse(address); + var isUnixSocket = serverUrl.protocol === 'ws+unix:'; + if (!serverUrl.host && !isUnixSocket) throw new Error('invalid url'); + var isSecure = serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:'; + var httpObj = isSecure ? https : http; + var port = serverUrl.port || (isSecure ? 443 : 80); + var auth = serverUrl.auth; + + // prepare extensions + var extensionsOffer = {}; + var perMessageDeflate; + if (options.value.perMessageDeflate) { + perMessageDeflate = new PerMessageDeflate(typeof options.value.perMessageDeflate !== true ? options.value.perMessageDeflate : {}, false); + extensionsOffer[PerMessageDeflate.extensionName] = perMessageDeflate.offer(); + } + + // expose state properties + this._isServer = false; + this.url = address; + this.protocolVersion = options.value.protocolVersion; + this.supports.binary = (this.protocolVersion !== 'hixie-76'); + + // begin handshake + var key = new Buffer(options.value.protocolVersion + '-' + Date.now()).toString('base64'); + var shasum = crypto.createHash('sha1'); + shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'); + var expectedServerKey = shasum.digest('base64'); + + var agent = options.value.agent; + + var headerHost = buildHostHeader(isSecure, serverUrl.hostname, port) + + var requestOptions = { + port: port, + host: serverUrl.hostname, + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Host': headerHost, + 'Sec-WebSocket-Version': options.value.protocolVersion, + 'Sec-WebSocket-Key': key + } + }; + + // If we have basic auth. + if (auth) { + requestOptions.headers.Authorization = 'Basic ' + new Buffer(auth).toString('base64'); + } + + if (options.value.protocol) { + requestOptions.headers['Sec-WebSocket-Protocol'] = options.value.protocol; + } + + if (options.value.host) { + requestOptions.headers.Host = options.value.host; + } + + if (options.value.headers) { + for (var header in options.value.headers) { + if (options.value.headers.hasOwnProperty(header)) { + requestOptions.headers[header] = options.value.headers[header]; + } + } + } + + if (Object.keys(extensionsOffer).length) { + requestOptions.headers['Sec-WebSocket-Extensions'] = Extensions.format(extensionsOffer); + } + + if (options.isDefinedAndNonNull('pfx') + || options.isDefinedAndNonNull('key') + || options.isDefinedAndNonNull('passphrase') + || options.isDefinedAndNonNull('cert') + || options.isDefinedAndNonNull('ca') + || options.isDefinedAndNonNull('ciphers') + || options.isDefinedAndNonNull('rejectUnauthorized')) { + + if (options.isDefinedAndNonNull('pfx')) requestOptions.pfx = options.value.pfx; + if (options.isDefinedAndNonNull('key')) requestOptions.key = options.value.key; + if (options.isDefinedAndNonNull('passphrase')) requestOptions.passphrase = options.value.passphrase; + if (options.isDefinedAndNonNull('cert')) requestOptions.cert = options.value.cert; + if (options.isDefinedAndNonNull('ca')) requestOptions.ca = options.value.ca; + if (options.isDefinedAndNonNull('ciphers')) requestOptions.ciphers = options.value.ciphers; + if (options.isDefinedAndNonNull('rejectUnauthorized')) requestOptions.rejectUnauthorized = options.value.rejectUnauthorized; + + if (!agent) { + // global agent ignores client side certificates + agent = new httpObj.Agent(requestOptions); + } + } + + requestOptions.path = serverUrl.path || '/'; + + if (agent) { + requestOptions.agent = agent; + } + + if (isUnixSocket) { + requestOptions.socketPath = serverUrl.pathname; + } + + if (options.value.localAddress) { + requestOptions.localAddress = options.value.localAddress; + } + + if (options.value.origin) { + if (options.value.protocolVersion < 13) requestOptions.headers['Sec-WebSocket-Origin'] = options.value.origin; + else requestOptions.headers.Origin = options.value.origin; + } + + var self = this; + var req = httpObj.request(requestOptions); + + req.on('error', function onerror(error) { + self.emit('error', error); + cleanupWebsocketResources.call(self, error); + }); + + req.once('response', function response(res) { + var error; + + if (!self.emit('unexpected-response', req, res)) { + error = new Error('unexpected server response (' + res.statusCode + ')'); + req.abort(); + self.emit('error', error); + } + + cleanupWebsocketResources.call(self, error); + }); + + req.once('upgrade', function upgrade(res, socket, upgradeHead) { + if (self.readyState === WebSocket.CLOSED) { + // client closed before server accepted connection + self.emit('close'); + self.removeAllListeners(); + socket.end(); + return; + } + + var serverKey = res.headers['sec-websocket-accept']; + if (typeof serverKey === 'undefined' || serverKey !== expectedServerKey) { + self.emit('error', 'invalid server key'); + self.removeAllListeners(); + socket.end(); + return; + } + + var serverProt = res.headers['sec-websocket-protocol']; + var protList = (options.value.protocol || "").split(/, */); + var protError = null; + + if (!options.value.protocol && serverProt) { + protError = 'server sent a subprotocol even though none requested'; + } else if (options.value.protocol && !serverProt) { + protError = 'server sent no subprotocol even though requested'; + } else if (serverProt && protList.indexOf(serverProt) === -1) { + protError = 'server responded with an invalid protocol'; + } + + if (protError) { + self.emit('error', protError); + self.removeAllListeners(); + socket.end(); + return; + } else if (serverProt) { + self.protocol = serverProt; + } + + var serverExtensions = Extensions.parse(res.headers['sec-websocket-extensions']); + if (perMessageDeflate && serverExtensions[PerMessageDeflate.extensionName]) { + try { + perMessageDeflate.accept(serverExtensions[PerMessageDeflate.extensionName]); + } catch (err) { + self.emit('error', 'invalid extension parameter'); + self.removeAllListeners(); + socket.end(); + return; + } + self.extensions[PerMessageDeflate.extensionName] = perMessageDeflate; + } + + establishConnection.call(self, Receiver, Sender, socket, upgradeHead); + + // perform cleanup on http resources + req.removeAllListeners(); + req = null; + agent = null; + }); + + req.end(); + this.readyState = WebSocket.CONNECTING; +} + +function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) { + var ultron = this._ultron = new Ultron(socket) + , called = false + , self = this; + + socket.setTimeout(0); + socket.setNoDelay(true); + + this._receiver = new ReceiverClass(this.extensions); + this._socket = socket; + + // socket cleanup handlers + ultron.on('end', cleanupWebsocketResources.bind(this)); + ultron.on('close', cleanupWebsocketResources.bind(this)); + ultron.on('error', cleanupWebsocketResources.bind(this)); + + // ensure that the upgradeHead is added to the receiver + function firstHandler(data) { + if (called || self.readyState === WebSocket.CLOSED) return; + + called = true; + socket.removeListener('data', firstHandler); + ultron.on('data', realHandler); + + if (upgradeHead && upgradeHead.length > 0) { + realHandler(upgradeHead); + upgradeHead = null; + } + + if (data) realHandler(data); + } + + // subsequent packets are pushed straight to the receiver + function realHandler(data) { + self.bytesReceived += data.length; + self._receiver.add(data); + } + + ultron.on('data', firstHandler); + + // if data was passed along with the http upgrade, + // this will schedule a push of that on to the receiver. + // this has to be done on next tick, since the caller + // hasn't had a chance to set event handlers on this client + // object yet. + process.nextTick(firstHandler); + + // receiver event handlers + self._receiver.ontext = function ontext(data, flags) { + flags = flags || {}; + + self.emit('message', data, flags); + }; + + self._receiver.onbinary = function onbinary(data, flags) { + flags = flags || {}; + + flags.binary = true; + self.emit('message', data, flags); + }; + + self._receiver.onping = function onping(data, flags) { + flags = flags || {}; + + self.pong(data, { + mask: !self._isServer, + binary: flags.binary === true + }, true); + + self.emit('ping', data, flags); + }; + + self._receiver.onpong = function onpong(data, flags) { + self.emit('pong', data, flags || {}); + }; + + self._receiver.onclose = function onclose(code, data, flags) { + flags = flags || {}; + + self._closeReceived = true; + self.close(code, data); + }; + + self._receiver.onerror = function onerror(reason, errorCode) { + // close the connection when the receiver reports a HyBi error code + self.close(typeof errorCode !== 'undefined' ? errorCode : 1002, ''); + self.emit('error', reason, errorCode); + }; + + // finalize the client + this._sender = new SenderClass(socket, this.extensions); + this._sender.on('error', function onerror(error) { + self.close(1002, ''); + self.emit('error', error); + }); + + this.readyState = WebSocket.OPEN; + this.emit('open'); +} + +function startQueue(instance) { + instance._queue = instance._queue || []; +} + +function executeQueueSends(instance) { + var queue = instance._queue; + if (typeof queue === 'undefined') return; + + delete instance._queue; + for (var i = 0, l = queue.length; i < l; ++i) { + queue[i](); + } +} + +function sendStream(instance, stream, options, cb) { + stream.on('data', function incoming(data) { + if (instance.readyState !== WebSocket.OPEN) { + if (typeof cb === 'function') cb(new Error('not opened')); + else { + delete instance._queue; + instance.emit('error', new Error('not opened')); + } + return; + } + + options.fin = false; + instance._sender.send(data, options); + }); + + stream.on('end', function end() { + if (instance.readyState !== WebSocket.OPEN) { + if (typeof cb === 'function') cb(new Error('not opened')); + else { + delete instance._queue; + instance.emit('error', new Error('not opened')); + } + return; + } + + options.fin = true; + instance._sender.send(null, options); + + if (typeof cb === 'function') cb(null); + }); +} + +function cleanupWebsocketResources(error) { + if (this.readyState === WebSocket.CLOSED) return; + + var emitClose = this.readyState !== WebSocket.CONNECTING; + this.readyState = WebSocket.CLOSED; + + clearTimeout(this._closeTimer); + this._closeTimer = null; + + if (emitClose) { + // If the connection was closed abnormally (with an error), or if + // the close control frame was not received then the close code + // must default to 1006. + if (error || !this._closeReceived) { + this._closeCode = 1006; + } + this.emit('close', this._closeCode || 1000, this._closeMessage || ''); + } + + if (this._socket) { + if (this._ultron) this._ultron.destroy(); + this._socket.on('error', function onerror() { + try { this.destroy(); } + catch (e) {} + }); + + try { + if (!error) this._socket.end(); + else this._socket.destroy(); + } catch (e) { /* Ignore termination errors */ } + + this._socket = null; + this._ultron = null; + } + + if (this._sender) { + this._sender.removeAllListeners(); + this._sender = null; + } + + if (this._receiver) { + this._receiver.cleanup(); + this._receiver = null; + } + + if (this.extensions[PerMessageDeflate.extensionName]) { + this.extensions[PerMessageDeflate.extensionName].cleanup(); + } + + this.extensions = null; + + this.removeAllListeners(); + this.on('error', function onerror() {}); // catch all errors after this + delete this._queue; +} diff --git a/node_modules/ws/lib/WebSocketServer.js b/node_modules/ws/lib/WebSocketServer.js new file mode 100644 index 000000000..ba0e4c050 --- /dev/null +++ b/node_modules/ws/lib/WebSocketServer.js @@ -0,0 +1,513 @@ +/*! + * ws: a node.js websocket client + * Copyright(c) 2011 Einar Otto Stangvik + * MIT Licensed + */ + +var util = require('util') + , events = require('events') + , http = require('http') + , crypto = require('crypto') + , Options = require('options') + , WebSocket = require('./WebSocket') + , Extensions = require('./Extensions') + , PerMessageDeflate = require('./PerMessageDeflate') + , tls = require('tls') + , url = require('url'); + +/** + * WebSocket Server implementation + */ + +function WebSocketServer(options, callback) { + if (this instanceof WebSocketServer === false) { + return new WebSocketServer(options, callback); + } + + events.EventEmitter.call(this); + + options = new Options({ + host: '0.0.0.0', + port: null, + server: null, + verifyClient: null, + handleProtocols: null, + path: null, + noServer: false, + disableHixie: false, + clientTracking: true, + perMessageDeflate: true + }).merge(options); + + if (!options.isDefinedAndNonNull('port') && !options.isDefinedAndNonNull('server') && !options.value.noServer) { + throw new TypeError('`port` or a `server` must be provided'); + } + + var self = this; + + if (options.isDefinedAndNonNull('port')) { + this._server = http.createServer(function (req, res) { + var body = http.STATUS_CODES[426]; + res.writeHead(426, { + 'Content-Length': body.length, + 'Content-Type': 'text/plain' + }); + res.end(body); + }); + this._server.allowHalfOpen = false; + this._server.listen(options.value.port, options.value.host, callback); + this._closeServer = function() { if (self._server) self._server.close(); }; + } + else if (options.value.server) { + this._server = options.value.server; + if (options.value.path) { + // take note of the path, to avoid collisions when multiple websocket servers are + // listening on the same http server + if (this._server._webSocketPaths && options.value.server._webSocketPaths[options.value.path]) { + throw new Error('two instances of WebSocketServer cannot listen on the same http server path'); + } + if (typeof this._server._webSocketPaths !== 'object') { + this._server._webSocketPaths = {}; + } + this._server._webSocketPaths[options.value.path] = 1; + } + } + if (this._server) this._server.once('listening', function() { self.emit('listening'); }); + + if (typeof this._server != 'undefined') { + this._server.on('error', function(error) { + self.emit('error', error) + }); + this._server.on('upgrade', function(req, socket, upgradeHead) { + //copy upgradeHead to avoid retention of large slab buffers used in node core + var head = new Buffer(upgradeHead.length); + upgradeHead.copy(head); + + self.handleUpgrade(req, socket, head, function(client) { + self.emit('connection'+req.url, client); + self.emit('connection', client); + }); + }); + } + + this.options = options.value; + this.path = options.value.path; + this.clients = []; +} + +/** + * Inherits from EventEmitter. + */ + +util.inherits(WebSocketServer, events.EventEmitter); + +/** + * Immediately shuts down the connection. + * + * @api public + */ + +WebSocketServer.prototype.close = function(callback) { + // terminate all associated clients + var error = null; + try { + for (var i = 0, l = this.clients.length; i < l; ++i) { + this.clients[i].terminate(); + } + } + catch (e) { + error = e; + } + + // remove path descriptor, if any + if (this.path && this._server._webSocketPaths) { + delete this._server._webSocketPaths[this.path]; + if (Object.keys(this._server._webSocketPaths).length == 0) { + delete this._server._webSocketPaths; + } + } + + // close the http server if it was internally created + try { + if (typeof this._closeServer !== 'undefined') { + this._closeServer(); + } + } + finally { + delete this._server; + } + if(callback) + callback(error); + else if(error) + throw error; +} + +/** + * Handle a HTTP Upgrade request. + * + * @api public + */ + +WebSocketServer.prototype.handleUpgrade = function(req, socket, upgradeHead, cb) { + // check for wrong path + if (this.options.path) { + var u = url.parse(req.url); + if (u && u.pathname !== this.options.path) return; + } + + if (typeof req.headers.upgrade === 'undefined' || req.headers.upgrade.toLowerCase() !== 'websocket') { + abortConnection(socket, 400, 'Bad Request'); + return; + } + + if (req.headers['sec-websocket-key1']) handleHixieUpgrade.apply(this, arguments); + else handleHybiUpgrade.apply(this, arguments); +} + +module.exports = WebSocketServer; + +/** + * Entirely private apis, + * which may or may not be bound to a sepcific WebSocket instance. + */ + +function handleHybiUpgrade(req, socket, upgradeHead, cb) { + // handle premature socket errors + var errorHandler = function() { + try { socket.destroy(); } catch (e) {} + } + socket.on('error', errorHandler); + + // verify key presence + if (!req.headers['sec-websocket-key']) { + abortConnection(socket, 400, 'Bad Request'); + return; + } + + // verify version + var version = parseInt(req.headers['sec-websocket-version']); + if ([8, 13].indexOf(version) === -1) { + abortConnection(socket, 400, 'Bad Request'); + return; + } + + // verify protocol + var protocols = req.headers['sec-websocket-protocol']; + + // verify client + var origin = version < 13 ? + req.headers['sec-websocket-origin'] : + req.headers['origin']; + + // handle extensions offer + var extensionsOffer = Extensions.parse(req.headers['sec-websocket-extensions']); + + // handler to call when the connection sequence completes + var self = this; + var completeHybiUpgrade2 = function(protocol) { + + // calc key + var key = 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 + ]; + + if (typeof protocol != 'undefined') { + headers.push('Sec-WebSocket-Protocol: ' + protocol); + } + + var extensions = {}; + try { + extensions = acceptExtensions.call(self, extensionsOffer); + } catch (err) { + abortConnection(socket, 400, 'Bad Request'); + return; + } + + if (Object.keys(extensions).length) { + var serverExtensions = {}; + Object.keys(extensions).forEach(function(token) { + serverExtensions[token] = [extensions[token].params] + }); + headers.push('Sec-WebSocket-Extensions: ' + Extensions.format(serverExtensions)); + } + + // allows external modification/inspection of handshake headers + self.emit('headers', headers); + + socket.setTimeout(0); + socket.setNoDelay(true); + try { + socket.write(headers.concat('', '').join('\r\n')); + } + catch (e) { + // if the upgrade write fails, shut the connection down hard + try { socket.destroy(); } catch (e) {} + return; + } + + var client = new WebSocket([req, socket, upgradeHead], { + protocolVersion: version, + protocol: protocol, + extensions: extensions + }); + + if (self.options.clientTracking) { + self.clients.push(client); + client.on('close', function() { + var index = self.clients.indexOf(client); + if (index != -1) { + self.clients.splice(index, 1); + } + }); + } + + // signal upgrade complete + socket.removeListener('error', errorHandler); + cb(client); + } + + // optionally call external protocol selection handler before + // calling completeHybiUpgrade2 + var completeHybiUpgrade1 = function() { + // choose from the sub-protocols + if (typeof self.options.handleProtocols == 'function') { + var protList = (protocols || "").split(/, */); + var callbackCalled = false; + var res = self.options.handleProtocols(protList, function(result, protocol) { + callbackCalled = true; + if (!result) abortConnection(socket, 401, 'Unauthorized'); + else completeHybiUpgrade2(protocol); + }); + if (!callbackCalled) { + // the handleProtocols handler never called our callback + abortConnection(socket, 501, 'Could not process protocols'); + } + return; + } else { + if (typeof protocols !== 'undefined') { + completeHybiUpgrade2(protocols.split(/, */)[0]); + } + else { + completeHybiUpgrade2(); + } + } + } + + // optionally call external client verification handler + if (typeof this.options.verifyClient == 'function') { + var info = { + origin: origin, + secure: typeof req.connection.authorized !== 'undefined' || typeof req.connection.encrypted !== 'undefined', + req: req + }; + if (this.options.verifyClient.length == 2) { + this.options.verifyClient(info, function(result, code, name) { + if (typeof code === 'undefined') code = 401; + if (typeof name === 'undefined') name = http.STATUS_CODES[code]; + + if (!result) abortConnection(socket, code, name); + else completeHybiUpgrade1(); + }); + return; + } + else if (!this.options.verifyClient(info)) { + abortConnection(socket, 401, 'Unauthorized'); + return; + } + } + + completeHybiUpgrade1(); +} + +function handleHixieUpgrade(req, socket, upgradeHead, cb) { + // handle premature socket errors + var errorHandler = function() { + try { socket.destroy(); } catch (e) {} + } + socket.on('error', errorHandler); + + // bail if options prevent hixie + if (this.options.disableHixie) { + abortConnection(socket, 401, 'Hixie support disabled'); + return; + } + + // verify key presence + if (!req.headers['sec-websocket-key2']) { + abortConnection(socket, 400, 'Bad Request'); + return; + } + + var origin = req.headers['origin'] + , self = this; + + // setup handshake completion to run after client has been verified + var onClientVerified = function() { + var wshost; + if (!req.headers['x-forwarded-host']) + wshost = req.headers.host; + else + wshost = req.headers['x-forwarded-host']; + var location = ((req.headers['x-forwarded-proto'] === 'https' || socket.encrypted) ? 'wss' : 'ws') + '://' + wshost + req.url + , protocol = req.headers['sec-websocket-protocol']; + + // handshake completion code to run once nonce has been successfully retrieved + var completeHandshake = function(nonce, rest) { + // calculate key + var k1 = req.headers['sec-websocket-key1'] + , k2 = req.headers['sec-websocket-key2'] + , 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){ + abortConnection(socket, 400, 'Bad Request'); + return; + } + n /= spaces; + md5.update(String.fromCharCode( + n >> 24 & 0xFF, + n >> 16 & 0xFF, + n >> 8 & 0xFF, + n & 0xFF)); + }); + md5.update(nonce.toString('binary')); + + var headers = [ + 'HTTP/1.1 101 Switching Protocols' + , 'Upgrade: WebSocket' + , 'Connection: Upgrade' + , 'Sec-WebSocket-Location: ' + location + ]; + if (typeof protocol != 'undefined') headers.push('Sec-WebSocket-Protocol: ' + protocol); + if (typeof origin != 'undefined') headers.push('Sec-WebSocket-Origin: ' + origin); + + socket.setTimeout(0); + socket.setNoDelay(true); + try { + // merge header and hash buffer + var headerBuffer = new Buffer(headers.concat('', '').join('\r\n')); + var hashBuffer = new Buffer(md5.digest('binary'), 'binary'); + var handshakeBuffer = new Buffer(headerBuffer.length + hashBuffer.length); + headerBuffer.copy(handshakeBuffer, 0); + hashBuffer.copy(handshakeBuffer, headerBuffer.length); + + // do a single write, which - upon success - causes a new client websocket to be setup + socket.write(handshakeBuffer, 'binary', function(err) { + if (err) return; // do not create client if an error happens + var client = new WebSocket([req, socket, rest], { + protocolVersion: 'hixie-76', + protocol: protocol + }); + if (self.options.clientTracking) { + self.clients.push(client); + client.on('close', function() { + var index = self.clients.indexOf(client); + if (index != -1) { + self.clients.splice(index, 1); + } + }); + } + + // signal upgrade complete + socket.removeListener('error', errorHandler); + cb(client); + }); + } + catch (e) { + try { socket.destroy(); } catch (e) {} + return; + } + } + + // retrieve nonce + var nonceLength = 8; + if (upgradeHead && upgradeHead.length >= nonceLength) { + var nonce = upgradeHead.slice(0, nonceLength); + var rest = upgradeHead.length > nonceLength ? upgradeHead.slice(nonceLength) : null; + completeHandshake.call(self, nonce, rest); + } + else { + // nonce not present in upgradeHead, so we must wait for enough data + // data to arrive before continuing + var nonce = new Buffer(nonceLength); + upgradeHead.copy(nonce, 0); + var received = upgradeHead.length; + var rest = null; + var handler = function (data) { + var toRead = Math.min(data.length, nonceLength - received); + if (toRead === 0) return; + data.copy(nonce, received, 0, toRead); + received += toRead; + if (received == nonceLength) { + socket.removeListener('data', handler); + if (toRead < data.length) rest = data.slice(toRead); + completeHandshake.call(self, nonce, rest); + } + } + socket.on('data', handler); + } + } + + // verify client + if (typeof this.options.verifyClient == 'function') { + var info = { + origin: origin, + secure: typeof req.connection.authorized !== 'undefined' || typeof req.connection.encrypted !== 'undefined', + req: req + }; + if (this.options.verifyClient.length == 2) { + var self = this; + this.options.verifyClient(info, function(result, code, name) { + if (typeof code === 'undefined') code = 401; + if (typeof name === 'undefined') name = http.STATUS_CODES[code]; + + if (!result) abortConnection(socket, code, name); + else onClientVerified.apply(self); + }); + return; + } + else if (!this.options.verifyClient(info)) { + abortConnection(socket, 401, 'Unauthorized'); + return; + } + } + + // no client verification required + onClientVerified(); +} + +function acceptExtensions(offer) { + var extensions = {}; + var options = this.options.perMessageDeflate; + if (options && offer[PerMessageDeflate.extensionName]) { + var perMessageDeflate = new PerMessageDeflate(options !== true ? options : {}, true); + perMessageDeflate.accept(offer[PerMessageDeflate.extensionName]); + extensions[PerMessageDeflate.extensionName] = perMessageDeflate; + } + return extensions; +} + +function abortConnection(socket, code, name) { + try { + var response = [ + 'HTTP/1.1 ' + code + ' ' + name, + 'Content-type: text/html' + ]; + socket.write(response.concat('', '').join('\r\n')); + } + catch (e) { /* ignore errors - we've aborted this connection */ } + finally { + // ensure that an early aborted connection is shut down completely + try { socket.destroy(); } catch (e) {} + } +} diff --git a/node_modules/ws/package.json b/node_modules/ws/package.json new file mode 100644 index 000000000..2d32fd9b7 --- /dev/null +++ b/node_modules/ws/package.json @@ -0,0 +1,76 @@ +{ + "author": { + "name": "Einar Otto Stangvik", + "email": "einaros@gmail.com", + "url": "http://2x.io" + }, + "name": "ws", + "description": "simple to use, blazing fast and thoroughly tested websocket client, server and console for node.js, up-to-date against RFC-6455", + "version": "1.0.1", + "license": "MIT", + "keywords": [ + "Hixie", + "HyBi", + "Push", + "RFC-6455", + "WebSocket", + "WebSockets", + "real-time" + ], + "repository": { + "type": "git", + "url": "git://github.com/websockets/ws.git" + }, + "scripts": { + "test": "make test" + }, + "dependencies": { + "options": ">=0.0.5", + "ultron": "1.0.x" + }, + "devDependencies": { + "ansi": "0.3.x", + "benchmark": "0.3.x", + "bufferutil": "1.2.x", + "expect.js": "0.3.x", + "mocha": "2.3.x", + "should": "8.0.x", + "tinycolor": "0.0.x", + "utf-8-validate": "1.2.x" + }, + "gypfile": true, + "gitHead": "40a9d686288b5d0be13f2bf2f3f5da07afc8cda2", + "bugs": { + "url": "https://github.com/websockets/ws/issues" + }, + "homepage": "https://github.com/websockets/ws#readme", + "_id": "ws@1.0.1", + "_shasum": "7d0b2a2e58cddd819039c29c9de65045e1b310e9", + "_from": "ws@latest", + "_npmVersion": "3.5.1", + "_nodeVersion": "4.2.3", + "_npmUser": { + "name": "3rdeden", + "email": "npm@3rd-Eden.com" + }, + "maintainers": [ + { + "name": "einaros", + "email": "einaros@gmail.com" + }, + { + "name": "v1", + "email": "info@3rd-Eden.com" + }, + { + "name": "3rdeden", + "email": "npm@3rd-Eden.com" + } + ], + "dist": { + "shasum": "7d0b2a2e58cddd819039c29c9de65045e1b310e9", + "tarball": "http://registry.npmjs.org/ws/-/ws-1.0.1.tgz" + }, + "directories": {}, + "_resolved": "https://registry.npmjs.org/ws/-/ws-1.0.1.tgz" +}