default.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. /*!
  2. * socket.io-node
  3. * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
  4. * MIT Licensed
  5. */
  6. /**
  7. * Module requirements.
  8. */
  9. var Transport = require('../../transport')
  10. , EventEmitter = process.EventEmitter
  11. , crypto = require('crypto')
  12. , parser = require('../../parser');
  13. /**
  14. * Export the constructor.
  15. */
  16. exports = module.exports = WebSocket;
  17. /**
  18. * HTTP interface constructor. Interface compatible with all transports that
  19. * depend on request-response cycles.
  20. *
  21. * @api public
  22. */
  23. function WebSocket (mng, data, req) {
  24. // parser
  25. var self = this;
  26. this.parser = new Parser();
  27. this.parser.on('data', function (packet) {
  28. self.log.debug(self.name + ' received data packet', packet);
  29. self.onMessage(parser.decodePacket(packet));
  30. });
  31. this.parser.on('close', function () {
  32. self.end();
  33. });
  34. this.parser.on('error', function () {
  35. self.end();
  36. });
  37. Transport.call(this, mng, data, req);
  38. };
  39. /**
  40. * Inherits from Transport.
  41. */
  42. WebSocket.prototype.__proto__ = Transport.prototype;
  43. /**
  44. * Transport name
  45. *
  46. * @api public
  47. */
  48. WebSocket.prototype.name = 'websocket';
  49. /**
  50. * Websocket draft version
  51. *
  52. * @api public
  53. */
  54. WebSocket.prototype.protocolVersion = 'hixie-76';
  55. /**
  56. * Called when the socket connects.
  57. *
  58. * @api private
  59. */
  60. WebSocket.prototype.onSocketConnect = function () {
  61. var self = this;
  62. this.socket.setNoDelay(true);
  63. this.buffer = true;
  64. this.buffered = [];
  65. if (this.req.headers.upgrade !== 'WebSocket') {
  66. this.log.warn(this.name + ' connection invalid');
  67. this.end();
  68. return;
  69. }
  70. var origin = this.req.headers['origin']
  71. , waitingForNonce = false;
  72. if(this.manager.settings['match origin protocol']){
  73. location = (origin.indexOf('https')>-1 ? 'wss' : 'ws') + '://' + this.req.headers.host + this.req.url;
  74. }else if(this.socket.encrypted){
  75. location = 'wss://' + this.req.headers.host + this.req.url;
  76. }else{
  77. location = 'ws://' + this.req.headers.host + this.req.url;
  78. }
  79. if (this.req.headers['sec-websocket-key1']) {
  80. // If we don't have the nonce yet, wait for it (HAProxy compatibility).
  81. if (! (this.req.head && this.req.head.length >= 8)) {
  82. waitingForNonce = true;
  83. }
  84. var headers = [
  85. 'HTTP/1.1 101 WebSocket Protocol Handshake'
  86. , 'Upgrade: WebSocket'
  87. , 'Connection: Upgrade'
  88. , 'Sec-WebSocket-Origin: ' + origin
  89. , 'Sec-WebSocket-Location: ' + location
  90. ];
  91. if (this.req.headers['sec-websocket-protocol']){
  92. headers.push('Sec-WebSocket-Protocol: '
  93. + this.req.headers['sec-websocket-protocol']);
  94. }
  95. } else {
  96. var headers = [
  97. 'HTTP/1.1 101 Web Socket Protocol Handshake'
  98. , 'Upgrade: WebSocket'
  99. , 'Connection: Upgrade'
  100. , 'WebSocket-Origin: ' + origin
  101. , 'WebSocket-Location: ' + location
  102. ];
  103. }
  104. try {
  105. this.socket.write(headers.concat('', '').join('\r\n'));
  106. this.socket.setTimeout(0);
  107. this.socket.setNoDelay(true);
  108. this.socket.setEncoding('utf8');
  109. } catch (e) {
  110. this.end();
  111. return;
  112. }
  113. if (waitingForNonce) {
  114. this.socket.setEncoding('binary');
  115. } else if (this.proveReception(headers)) {
  116. self.flush();
  117. }
  118. var headBuffer = '';
  119. this.socket.on('data', function (data) {
  120. if (waitingForNonce) {
  121. headBuffer += data;
  122. if (headBuffer.length < 8) {
  123. return;
  124. }
  125. // Restore the connection to utf8 encoding after receiving the nonce
  126. self.socket.setEncoding('utf8');
  127. waitingForNonce = false;
  128. // Stuff the nonce into the location where it's expected to be
  129. self.req.head = headBuffer.substr(0, 8);
  130. headBuffer = '';
  131. if (self.proveReception(headers)) {
  132. self.flush();
  133. }
  134. return;
  135. }
  136. self.parser.add(data);
  137. });
  138. };
  139. /**
  140. * Writes to the socket.
  141. *
  142. * @api private
  143. */
  144. WebSocket.prototype.write = function (data) {
  145. if (this.open) {
  146. this.drained = false;
  147. if (this.buffer) {
  148. this.buffered.push(data);
  149. return this;
  150. }
  151. var length = Buffer.byteLength(data)
  152. , buffer = new Buffer(2 + length);
  153. buffer.write('\x00', 'binary');
  154. buffer.write(data, 1, 'utf8');
  155. buffer.write('\xff', 1 + length, 'binary');
  156. try {
  157. if (this.socket.write(buffer)) {
  158. this.drained = true;
  159. }
  160. } catch (e) {
  161. this.end();
  162. }
  163. this.log.debug(this.name + ' writing', data);
  164. }
  165. };
  166. /**
  167. * Flushes the internal buffer
  168. *
  169. * @api private
  170. */
  171. WebSocket.prototype.flush = function () {
  172. this.buffer = false;
  173. for (var i = 0, l = this.buffered.length; i < l; i++) {
  174. this.write(this.buffered.splice(0, 1)[0]);
  175. }
  176. };
  177. /**
  178. * Finishes the handshake.
  179. *
  180. * @api private
  181. */
  182. WebSocket.prototype.proveReception = function (headers) {
  183. var self = this
  184. , k1 = this.req.headers['sec-websocket-key1']
  185. , k2 = this.req.headers['sec-websocket-key2'];
  186. if (k1 && k2){
  187. var md5 = crypto.createHash('md5');
  188. [k1, k2].forEach(function (k) {
  189. var n = parseInt(k.replace(/[^\d]/g, ''))
  190. , spaces = k.replace(/[^ ]/g, '').length;
  191. if (spaces === 0 || n % spaces !== 0){
  192. self.log.warn('Invalid ' + self.name + ' key: "' + k + '".');
  193. self.end();
  194. return false;
  195. }
  196. n /= spaces;
  197. md5.update(String.fromCharCode(
  198. n >> 24 & 0xFF,
  199. n >> 16 & 0xFF,
  200. n >> 8 & 0xFF,
  201. n & 0xFF));
  202. });
  203. md5.update(this.req.head.toString('binary'));
  204. try {
  205. this.socket.write(md5.digest('binary'), 'binary');
  206. } catch (e) {
  207. this.end();
  208. }
  209. }
  210. return true;
  211. };
  212. /**
  213. * Writes a payload.
  214. *
  215. * @api private
  216. */
  217. WebSocket.prototype.payload = function (msgs) {
  218. for (var i = 0, l = msgs.length; i < l; i++) {
  219. this.write(msgs[i]);
  220. }
  221. return this;
  222. };
  223. /**
  224. * Closes the connection.
  225. *
  226. * @api private
  227. */
  228. WebSocket.prototype.doClose = function () {
  229. this.socket.end();
  230. };
  231. /**
  232. * WebSocket parser
  233. *
  234. * @api public
  235. */
  236. function Parser () {
  237. this.buffer = '';
  238. this.i = 0;
  239. };
  240. /**
  241. * Inherits from EventEmitter.
  242. */
  243. Parser.prototype.__proto__ = EventEmitter.prototype;
  244. /**
  245. * Adds data to the buffer.
  246. *
  247. * @api public
  248. */
  249. Parser.prototype.add = function (data) {
  250. this.buffer += data;
  251. this.parse();
  252. };
  253. /**
  254. * Parses the buffer.
  255. *
  256. * @api private
  257. */
  258. Parser.prototype.parse = function () {
  259. for (var i = this.i, chr, l = this.buffer.length; i < l; i++){
  260. chr = this.buffer[i];
  261. if (this.buffer.length == 2 && this.buffer[1] == '\u0000') {
  262. this.emit('close');
  263. this.buffer = '';
  264. this.i = 0;
  265. return;
  266. }
  267. if (i === 0){
  268. if (chr != '\u0000')
  269. this.error('Bad framing. Expected null byte as first frame');
  270. else
  271. continue;
  272. }
  273. if (chr == '\ufffd'){
  274. this.emit('data', this.buffer.substr(1, i - 1));
  275. this.buffer = this.buffer.substr(i + 1);
  276. this.i = 0;
  277. return this.parse();
  278. }
  279. }
  280. };
  281. /**
  282. * Handles an error
  283. *
  284. * @api private
  285. */
  286. Parser.prototype.error = function (reason) {
  287. this.buffer = '';
  288. this.i = 0;
  289. this.emit('error', reason);
  290. return this;
  291. };