Connection.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679
  1. /**
  2. * @file Represents a connection (both client and server sides)
  3. */
  4. 'use strict'
  5. var util = require('util'),
  6. events = require('events'),
  7. crypto = require('crypto'),
  8. InStream = require('./InStream'),
  9. OutStream = require('./OutStream'),
  10. frame = require('./frame'),
  11. Server = require('./Server')
  12. /**
  13. * @typedef {Object} Connection~Options
  14. * @param {string} path
  15. * @param {string} host
  16. * @param {?Object<string>} extraHeaders
  17. * @param {?Array<string>} protocols
  18. */
  19. /**
  20. * @class
  21. * @param {(net.Socket|tls.CleartextStream)} socket a net or tls socket
  22. * @param {(Server|Connection~Options)} parentOrOptions parent in case of server-side connection, object in case of client-side
  23. * @param {Function} [callback] will be added as a listener to 'connect'
  24. * @inherits EventEmitter
  25. * @event close the numeric code and string reason will be passed
  26. * @event error an error object is passed
  27. * @event text a string is passed
  28. * @event binary a inStream object is passed
  29. * @event pong a string is passed
  30. * @event connect
  31. */
  32. function Connection(socket, parentOrOptions, callback) {
  33. var that = this,
  34. connectEvent
  35. if (parentOrOptions instanceof Server) {
  36. // Server-side connection
  37. this.server = parentOrOptions
  38. this.path = null
  39. this.host = null
  40. this.extraHeaders = null
  41. this.protocols = []
  42. } else {
  43. // Client-side
  44. this.server = null
  45. this.path = parentOrOptions.path
  46. this.host = parentOrOptions.host
  47. this.extraHeaders = parentOrOptions.extraHeaders
  48. this.protocols = parentOrOptions.protocols || []
  49. }
  50. this.protocol = undefined
  51. this.socket = socket
  52. this.readyState = this.CONNECTING
  53. this.buffer = Buffer.alloc(0)
  54. this.frameBuffer = null // string for text frames and InStream for binary frames
  55. this.outStream = null // current allocated OutStream object for sending binary frames
  56. this.key = null // the Sec-WebSocket-Key header
  57. this.headers = {} // read only map of header names and values. Header names are lower-cased
  58. // Set listeners
  59. socket.on('readable', function () {
  60. that.doRead()
  61. })
  62. socket.on('error', function (err) {
  63. that.emit('error', err)
  64. })
  65. if (!this.server) {
  66. connectEvent = socket.constructor.name === 'CleartextStream' ? 'secureConnect' : 'connect'
  67. socket.on(connectEvent, function () {
  68. that.startHandshake()
  69. })
  70. }
  71. // Close listeners
  72. var onclose = function () {
  73. if (that.readyState === that.CONNECTING || that.readyState === that.OPEN) {
  74. that.emit('close', 1006, '')
  75. }
  76. that.readyState = this.CLOSED
  77. if (that.frameBuffer instanceof InStream) {
  78. that.frameBuffer.end()
  79. that.frameBuffer = null
  80. }
  81. if (that.outStream instanceof OutStream) {
  82. that.outStream.end()
  83. that.outStream = null
  84. }
  85. }
  86. socket.once('close', onclose)
  87. socket.once('finish', onclose)
  88. // super constructor
  89. events.EventEmitter.call(this)
  90. if (callback) {
  91. this.once('connect', callback)
  92. }
  93. }
  94. util.inherits(Connection, events.EventEmitter)
  95. module.exports = Connection
  96. /**
  97. * Minimum size of a pack of binary data to send in a single frame
  98. * @property {number} binaryFragmentation
  99. */
  100. Connection.binaryFragmentation = 512 * 1024 // .5 MiB
  101. /**
  102. * The maximum size the internal Buffer can grow
  103. * If at any time it stays bigger than this, the connection will be closed with code 1009
  104. * This is a security measure, to avoid memory attacks
  105. * @property {number} maxBufferLength
  106. */
  107. Connection.maxBufferLength = 2 * 1024 * 1024 // 2 MiB
  108. /**
  109. * Possible ready states for the connection
  110. * @constant {number} CONNECTING
  111. * @constant {number} OPEN
  112. * @constant {number} CLOSING
  113. * @constant {number} CLOSED
  114. */
  115. Connection.prototype.CONNECTING = 0
  116. Connection.prototype.OPEN = 1
  117. Connection.prototype.CLOSING = 2
  118. Connection.prototype.CLOSED = 3
  119. /**
  120. * Send a given string to the other side
  121. * @param {string} str
  122. * @param {Function} [callback] will be executed when the data is finally written out
  123. */
  124. Connection.prototype.sendText = function (str, callback) {
  125. if (this.readyState === this.OPEN) {
  126. if (!this.outStream) {
  127. return this.socket.write(frame.createTextFrame(str, !this.server), callback)
  128. }
  129. this.emit('error', new Error('You can\'t send a text frame until you finish sending binary frames'))
  130. } else {
  131. this.emit('error', new Error('You can\'t write to a non-open connection'))
  132. }
  133. }
  134. /**
  135. * Request for a OutStream to send binary data
  136. * @returns {OutStream}
  137. */
  138. Connection.prototype.beginBinary = function () {
  139. if (this.readyState === this.OPEN) {
  140. if (!this.outStream) {
  141. return (this.outStream = new OutStream(this, Connection.binaryFragmentation))
  142. }
  143. this.emit('error', new Error('You can\'t send more binary frames until you finish sending the previous binary frames'))
  144. } else {
  145. this.emit('error', new Error('You can\'t write to a non-open connection'))
  146. }
  147. }
  148. /**
  149. * Sends a binary buffer at once
  150. * @param {Buffer} data
  151. * @param {Function} [callback] will be executed when the data is finally written out
  152. */
  153. Connection.prototype.sendBinary = function (data, callback) {
  154. if (this.readyState === this.OPEN) {
  155. if (!this.outStream) {
  156. return this.socket.write(frame.createBinaryFrame(data, !this.server, true, true), callback)
  157. }
  158. this.emit('error', new Error('You can\'t send more binary frames until you finish sending the previous binary frames'))
  159. } else {
  160. this.emit('error', new Error('You can\'t write to a non-open connection'))
  161. }
  162. }
  163. /**
  164. * Sends a text or binary frame
  165. * @param {string|Buffer} data
  166. * @param {Function} [callback] will be executed when the data is finally written out
  167. */
  168. Connection.prototype.send = function (data, callback) {
  169. if (typeof data === 'string') {
  170. this.sendText(data, callback)
  171. } else if (Buffer.isBuffer(data)) {
  172. this.sendBinary(data, callback)
  173. } else {
  174. throw new TypeError('data should be either a string or a Buffer instance')
  175. }
  176. }
  177. /**
  178. * Sends a ping to the remote
  179. * @param {string} [data=''] - optional ping data
  180. * @fires pong when pong reply is received
  181. */
  182. Connection.prototype.sendPing = function (data) {
  183. if (this.readyState === this.OPEN) {
  184. this.socket.write(frame.createPingFrame(data || '', !this.server))
  185. } else {
  186. this.emit('error', new Error('You can\'t write to a non-open connection'))
  187. }
  188. }
  189. /**
  190. * Close the connection, sending a close frame and waiting for response
  191. * If the connection isn't OPEN, closes it without sending a close frame
  192. * @param {number} [code]
  193. * @param {string} [reason]
  194. * @fires close
  195. */
  196. Connection.prototype.close = function (code, reason) {
  197. if (this.readyState === this.OPEN) {
  198. this.socket.write(frame.createCloseFrame(code, reason, !this.server))
  199. this.readyState = this.CLOSING
  200. } else if (this.readyState !== this.CLOSED) {
  201. this.socket.end()
  202. this.readyState = this.CLOSED
  203. }
  204. this.emit('close', code, reason)
  205. }
  206. /**
  207. * Reads contents from the socket and process it
  208. * @fires connect
  209. * @private
  210. */
  211. Connection.prototype.doRead = function () {
  212. var buffer, temp
  213. // Fetches the data
  214. buffer = this.socket.read()
  215. if (!buffer) {
  216. // Waits for more data
  217. return
  218. }
  219. // Save to the internal buffer
  220. this.buffer = Buffer.concat([this.buffer, buffer], this.buffer.length + buffer.length)
  221. if (this.readyState === this.CONNECTING) {
  222. if (!this.readHandshake()) {
  223. // May have failed or we're waiting for more data
  224. return
  225. }
  226. }
  227. if (this.readyState !== this.CLOSED) {
  228. // Try to read as many frames as possible
  229. while ((temp = this.extractFrame()) === true) {}
  230. if (temp === false) {
  231. // Protocol error
  232. this.close(1002)
  233. } else if (this.buffer.length > Connection.maxBufferLength) {
  234. // Frame too big
  235. this.close(1009)
  236. }
  237. }
  238. }
  239. /**
  240. * Create and send a handshake as a client
  241. * @private
  242. */
  243. Connection.prototype.startHandshake = function () {
  244. var str, i, key, headers, header
  245. key = Buffer.alloc(16)
  246. for (i = 0; i < 16; i++) {
  247. key[i] = Math.floor(Math.random() * 256)
  248. }
  249. this.key = key.toString('base64')
  250. headers = {
  251. Host: this.host,
  252. Upgrade: 'websocket',
  253. Connection: 'Upgrade',
  254. 'Sec-WebSocket-Key': this.key,
  255. 'Sec-WebSocket-Version': '13'
  256. }
  257. if (this.protocols && this.protocols.length) {
  258. headers['Sec-WebSocket-Protocol'] = this.protocols.join(', ')
  259. }
  260. for (header in this.extraHeaders) {
  261. headers[header] = this.extraHeaders[header]
  262. }
  263. str = this.buildRequest('GET ' + this.path + ' HTTP/1.1', headers)
  264. this.socket.write(str)
  265. }
  266. /**
  267. * Try to read the handshake from the internal buffer
  268. * If it succeeds, the handshake data is consumed from the internal buffer
  269. * @returns {boolean} - whether the handshake was done
  270. * @private
  271. */
  272. Connection.prototype.readHandshake = function () {
  273. var found = false,
  274. i, data
  275. // Do the handshake and try to connect
  276. if (this.buffer.length > Connection.maxBufferLength) {
  277. // Too big for a handshake
  278. if (this.server) {
  279. this.socket.end('HTTP/1.1 400 Bad Request\r\n\r\n')
  280. } else {
  281. this.socket.end()
  282. this.emit('error', new Error('Handshake is too big'))
  283. }
  284. return false
  285. }
  286. // Search for '\r\n\r\n'
  287. for (i = 0; i < this.buffer.length - 3; i++) {
  288. if (this.buffer[i] === 13 && this.buffer[i + 2] === 13 &&
  289. this.buffer[i + 1] === 10 && this.buffer[i + 3] === 10) {
  290. found = true
  291. break
  292. }
  293. }
  294. if (!found) {
  295. // Wait for more data
  296. return false
  297. }
  298. data = this.buffer.slice(0, i + 4).toString().split('\r\n')
  299. if (this.server ? this.answerHandshake(data) : this.checkHandshake(data)) {
  300. this.buffer = this.buffer.slice(i + 4)
  301. this.readyState = this.OPEN
  302. this.emit('connect')
  303. return true
  304. } else {
  305. this.socket.end(this.server ? 'HTTP/1.1 400 Bad Request\r\n\r\n' : undefined)
  306. return false
  307. }
  308. }
  309. /**
  310. * Read headers from HTTP protocol
  311. * Update the Connection#headers property
  312. * @param {string[]} lines one for each '\r\n'-separated HTTP request line
  313. * @private
  314. */
  315. Connection.prototype.readHeaders = function (lines) {
  316. var i, match
  317. // Extract all headers
  318. // Ignore bad-formed lines and ignore the first line (HTTP header)
  319. for (i = 1; i < lines.length; i++) {
  320. if ((match = lines[i].match(/^([a-z-]+): (.+)$/i))) {
  321. this.headers[match[1].toLowerCase()] = match[2]
  322. }
  323. }
  324. }
  325. /**
  326. * Process and check a handshake answered by a server
  327. * @param {string[]} lines one for each '\r\n'-separated HTTP request line
  328. * @returns {boolean} if the handshake was sucessful. If not, the connection must be closed
  329. * @private
  330. */
  331. Connection.prototype.checkHandshake = function (lines) {
  332. var key, sha1, protocol
  333. // First line
  334. if (lines.length < 4) {
  335. this.emit('error', new Error('Invalid handshake: too short'))
  336. return false
  337. }
  338. if (!lines[0].match(/^HTTP\/\d\.\d 101( .*)?$/i)) {
  339. this.emit('error', new Error('Invalid handshake: invalid first line format'))
  340. return false
  341. }
  342. // Extract all headers
  343. this.readHeaders(lines)
  344. // Validate necessary headers
  345. if (!('upgrade' in this.headers) ||
  346. !('sec-websocket-accept' in this.headers) ||
  347. !('connection' in this.headers)) {
  348. this.emit('error', new Error('Invalid handshake: missing required headers'))
  349. return false
  350. }
  351. if (this.headers.upgrade.toLowerCase() !== 'websocket' ||
  352. this.headers.connection.toLowerCase().split(/\s*,\s*/).indexOf('upgrade') === -1) {
  353. this.emit('error', new Error('Invalid handshake: invalid Upgrade or Connection header'))
  354. return false
  355. }
  356. key = this.headers['sec-websocket-accept']
  357. // Check protocol negotiation
  358. protocol = this.headers['sec-websocket-protocol']
  359. if (this.protocols && this.protocols.length) {
  360. // The server must choose one from our list
  361. if (!protocol || this.protocols.indexOf(protocol) === -1) {
  362. this.emit('error', new Error('Invalid handshake: no protocol was negotiated'))
  363. return false
  364. }
  365. } else {
  366. // The server must not choose a protocol
  367. if (protocol) {
  368. this.emit('error', new Error('Invalid handshake: no protocol negotiation was expected'))
  369. return false
  370. }
  371. }
  372. this.protocol = protocol
  373. // Check the key
  374. sha1 = crypto.createHash('sha1')
  375. sha1.end(this.key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
  376. if (key !== sha1.read().toString('base64')) {
  377. this.emit('error', new Error('Invalid handshake: hash mismatch'))
  378. return false
  379. }
  380. return true
  381. }
  382. /**
  383. * Process and answer a handshake started by a client
  384. * @param {string[]} lines one for each '\r\n'-separated HTTP request line
  385. * @returns {boolean} if the handshake was sucessful. If not, the connection must be closed with error 400-Bad Request
  386. * @private
  387. */
  388. Connection.prototype.answerHandshake = function (lines) {
  389. var path, key, sha1, headers
  390. // First line
  391. if (lines.length < 6) {
  392. return false
  393. }
  394. path = lines[0].match(/^GET (.+) HTTP\/\d\.\d$/i)
  395. if (!path) {
  396. return false
  397. }
  398. this.path = path[1]
  399. // Extract all headers
  400. this.readHeaders(lines)
  401. // Validate necessary headers
  402. if (!('host' in this.headers) ||
  403. !('sec-websocket-key' in this.headers) ||
  404. !('upgrade' in this.headers) ||
  405. !('connection' in this.headers)) {
  406. return false
  407. }
  408. if (this.headers.upgrade.toLowerCase() !== 'websocket' ||
  409. this.headers.connection.toLowerCase().split(/\s*,\s*/).indexOf('upgrade') === -1) {
  410. return false
  411. }
  412. if (this.headers['sec-websocket-version'] !== '13') {
  413. return false
  414. }
  415. this.key = this.headers['sec-websocket-key']
  416. // Agree on a protocol
  417. if ('sec-websocket-protocol' in this.headers) {
  418. // Parse
  419. this.protocols = this.headers['sec-websocket-protocol'].split(',').map(function (each) {
  420. return each.trim()
  421. })
  422. // Select protocol
  423. if (this.server._selectProtocol) {
  424. this.protocol = this.server._selectProtocol(this, this.protocols)
  425. }
  426. }
  427. // Build and send the response
  428. sha1 = crypto.createHash('sha1')
  429. sha1.end(this.key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
  430. key = sha1.read().toString('base64')
  431. headers = {
  432. Upgrade: 'websocket',
  433. Connection: 'Upgrade',
  434. 'Sec-WebSocket-Accept': key
  435. }
  436. if (this.protocol) {
  437. headers['Sec-WebSocket-Protocol'] = this.protocol
  438. }
  439. this.socket.write(this.buildRequest('HTTP/1.1 101 Switching Protocols', headers))
  440. return true
  441. }
  442. /**
  443. * Try to extract frame contents from the buffer (and execute it)
  444. * @returns {(boolean|undefined)} false=something went wrong (the connection must be closed); undefined=there isn't enough data to catch a frame; true=the frame was successfully fetched and executed
  445. * @private
  446. */
  447. Connection.prototype.extractFrame = function () {
  448. var fin, opcode, B, HB, mask, len, payload, start, i, hasMask
  449. if (this.buffer.length < 2) {
  450. return
  451. }
  452. // Is this the last frame in a sequence?
  453. B = this.buffer[0]
  454. HB = B >> 4
  455. if (HB % 8) {
  456. // RSV1, RSV2 and RSV3 must be clear
  457. return false
  458. }
  459. fin = HB === 8
  460. opcode = B % 16
  461. if (opcode !== 0 && opcode !== 1 && opcode !== 2 &&
  462. opcode !== 8 && opcode !== 9 && opcode !== 10) {
  463. // Invalid opcode
  464. return false
  465. }
  466. if (opcode >= 8 && !fin) {
  467. // Control frames must not be fragmented
  468. return false
  469. }
  470. B = this.buffer[1]
  471. hasMask = B >> 7
  472. if ((this.server && !hasMask) || (!this.server && hasMask)) {
  473. // Frames sent by clients must be masked
  474. return false
  475. }
  476. len = B % 128
  477. start = hasMask ? 6 : 2
  478. if (this.buffer.length < start + len) {
  479. // Not enough data in the buffer
  480. return
  481. }
  482. // Get the actual payload length
  483. if (len === 126) {
  484. len = this.buffer.readUInt16BE(2)
  485. start += 2
  486. } else if (len === 127) {
  487. // Warning: JS can only store up to 2^53 in its number format
  488. len = this.buffer.readUInt32BE(2) * Math.pow(2, 32) + this.buffer.readUInt32BE(6)
  489. start += 8
  490. }
  491. if (this.buffer.length < start + len) {
  492. return
  493. }
  494. // Extract the payload
  495. payload = this.buffer.slice(start, start + len)
  496. if (hasMask) {
  497. // Decode with the given mask
  498. mask = this.buffer.slice(start - 4, start)
  499. for (i = 0; i < payload.length; i++) {
  500. payload[i] ^= mask[i % 4]
  501. }
  502. }
  503. this.buffer = this.buffer.slice(start + len)
  504. // Proceeds to frame processing
  505. return this.processFrame(fin, opcode, payload)
  506. }
  507. /**
  508. * Process a given frame received
  509. * @param {boolean} fin
  510. * @param {number} opcode
  511. * @param {Buffer} payload
  512. * @returns {boolean} false if any error occurs, true otherwise
  513. * @fires text
  514. * @fires binary
  515. * @private
  516. */
  517. Connection.prototype.processFrame = function (fin, opcode, payload) {
  518. if (opcode === 8) {
  519. // Close frame
  520. if (this.readyState === this.CLOSING) {
  521. this.socket.end()
  522. } else if (this.readyState === this.OPEN) {
  523. this.processCloseFrame(payload)
  524. }
  525. return true
  526. } else if (opcode === 9) {
  527. // Ping frame
  528. if (this.readyState === this.OPEN) {
  529. this.socket.write(frame.createPongFrame(payload.toString(), !this.server))
  530. }
  531. return true
  532. } else if (opcode === 10) {
  533. // Pong frame
  534. this.emit('pong', payload.toString())
  535. return true
  536. }
  537. if (this.readyState !== this.OPEN) {
  538. // Ignores if the connection isn't opened anymore
  539. return true
  540. }
  541. if (opcode === 0 && this.frameBuffer === null) {
  542. // Unexpected continuation frame
  543. return false
  544. } else if (opcode !== 0 && this.frameBuffer !== null) {
  545. // Last sequence didn't finished correctly
  546. return false
  547. }
  548. if (!opcode) {
  549. // Get the current opcode for fragmented frames
  550. opcode = typeof this.frameBuffer === 'string' ? 1 : 2
  551. }
  552. if (opcode === 1) {
  553. // Save text frame
  554. payload = payload.toString()
  555. this.frameBuffer = this.frameBuffer ? this.frameBuffer + payload : payload
  556. if (fin) {
  557. // Emits 'text' event
  558. this.emit('text', this.frameBuffer)
  559. this.frameBuffer = null
  560. }
  561. } else {
  562. // Sends the buffer for InStream object
  563. if (!this.frameBuffer) {
  564. // Emits the 'binary' event
  565. this.frameBuffer = new InStream
  566. this.emit('binary', this.frameBuffer)
  567. }
  568. this.frameBuffer.addData(payload)
  569. if (fin) {
  570. // Emits 'end' event
  571. this.frameBuffer.end()
  572. this.frameBuffer = null
  573. }
  574. }
  575. return true
  576. }
  577. /**
  578. * Process a close frame, emitting the close event and sending back the frame
  579. * @param {Buffer} payload
  580. * @fires close
  581. * @private
  582. */
  583. Connection.prototype.processCloseFrame = function (payload) {
  584. var code, reason
  585. if (payload.length >= 2) {
  586. code = payload.readUInt16BE(0)
  587. reason = payload.slice(2).toString()
  588. } else {
  589. code = 1005
  590. reason = ''
  591. }
  592. this.socket.write(frame.createCloseFrame(code, reason, !this.server))
  593. this.readyState = this.CLOSED
  594. this.emit('close', code, reason)
  595. }
  596. /**
  597. * Build the header string
  598. * @param {string} requestLine
  599. * @param {Object<string>} headers
  600. * @returns {string}
  601. * @private
  602. */
  603. Connection.prototype.buildRequest = function (requestLine, headers) {
  604. var headerString = requestLine + '\r\n',
  605. headerName
  606. for (headerName in headers) {
  607. headerString += headerName + ': ' + headers[headerName] + '\r\n'
  608. }
  609. return headerString + '\r\n'
  610. }