util.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. 'use strict'
  2. const { kReadyState, kController, kResponse, kBinaryType, kWebSocketURL } = require('./symbols')
  3. const { states, opcodes } = require('./constants')
  4. const { MessageEvent, ErrorEvent } = require('./events')
  5. /* globals Blob */
  6. /**
  7. * @param {import('./websocket').WebSocket} ws
  8. */
  9. function isEstablished (ws) {
  10. // If the server's response is validated as provided for above, it is
  11. // said that _The WebSocket Connection is Established_ and that the
  12. // WebSocket Connection is in the OPEN state.
  13. return ws[kReadyState] === states.OPEN
  14. }
  15. /**
  16. * @param {import('./websocket').WebSocket} ws
  17. */
  18. function isClosing (ws) {
  19. // Upon either sending or receiving a Close control frame, it is said
  20. // that _The WebSocket Closing Handshake is Started_ and that the
  21. // WebSocket connection is in the CLOSING state.
  22. return ws[kReadyState] === states.CLOSING
  23. }
  24. /**
  25. * @param {import('./websocket').WebSocket} ws
  26. */
  27. function isClosed (ws) {
  28. return ws[kReadyState] === states.CLOSED
  29. }
  30. /**
  31. * @see https://dom.spec.whatwg.org/#concept-event-fire
  32. * @param {string} e
  33. * @param {EventTarget} target
  34. * @param {EventInit | undefined} eventInitDict
  35. */
  36. function fireEvent (e, target, eventConstructor = Event, eventInitDict) {
  37. // 1. If eventConstructor is not given, then let eventConstructor be Event.
  38. // 2. Let event be the result of creating an event given eventConstructor,
  39. // in the relevant realm of target.
  40. // 3. Initialize event’s type attribute to e.
  41. const event = new eventConstructor(e, eventInitDict) // eslint-disable-line new-cap
  42. // 4. Initialize any other IDL attributes of event as described in the
  43. // invocation of this algorithm.
  44. // 5. Return the result of dispatching event at target, with legacy target
  45. // override flag set if set.
  46. target.dispatchEvent(event)
  47. }
  48. /**
  49. * @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol
  50. * @param {import('./websocket').WebSocket} ws
  51. * @param {number} type Opcode
  52. * @param {Buffer} data application data
  53. */
  54. function websocketMessageReceived (ws, type, data) {
  55. // 1. If ready state is not OPEN (1), then return.
  56. if (ws[kReadyState] !== states.OPEN) {
  57. return
  58. }
  59. // 2. Let dataForEvent be determined by switching on type and binary type:
  60. let dataForEvent
  61. if (type === opcodes.TEXT) {
  62. // -> type indicates that the data is Text
  63. // a new DOMString containing data
  64. try {
  65. dataForEvent = new TextDecoder('utf-8', { fatal: true }).decode(data)
  66. } catch {
  67. failWebsocketConnection(ws, 'Received invalid UTF-8 in text frame.')
  68. return
  69. }
  70. } else if (type === opcodes.BINARY) {
  71. if (ws[kBinaryType] === 'blob') {
  72. // -> type indicates that the data is Binary and binary type is "blob"
  73. // a new Blob object, created in the relevant Realm of the WebSocket
  74. // object, that represents data as its raw data
  75. dataForEvent = new Blob([data])
  76. } else {
  77. // -> type indicates that the data is Binary and binary type is "arraybuffer"
  78. // a new ArrayBuffer object, created in the relevant Realm of the
  79. // WebSocket object, whose contents are data
  80. dataForEvent = new Uint8Array(data).buffer
  81. }
  82. }
  83. // 3. Fire an event named message at the WebSocket object, using MessageEvent,
  84. // with the origin attribute initialized to the serialization of the WebSocket
  85. // object’s url's origin, and the data attribute initialized to dataForEvent.
  86. fireEvent('message', ws, MessageEvent, {
  87. origin: ws[kWebSocketURL].origin,
  88. data: dataForEvent
  89. })
  90. }
  91. /**
  92. * @see https://datatracker.ietf.org/doc/html/rfc6455
  93. * @see https://datatracker.ietf.org/doc/html/rfc2616
  94. * @see https://bugs.chromium.org/p/chromium/issues/detail?id=398407
  95. * @param {string} protocol
  96. */
  97. function isValidSubprotocol (protocol) {
  98. // If present, this value indicates one
  99. // or more comma-separated subprotocol the client wishes to speak,
  100. // ordered by preference. The elements that comprise this value
  101. // MUST be non-empty strings with characters in the range U+0021 to
  102. // U+007E not including separator characters as defined in
  103. // [RFC2616] and MUST all be unique strings.
  104. if (protocol.length === 0) {
  105. return false
  106. }
  107. for (const char of protocol) {
  108. const code = char.charCodeAt(0)
  109. if (
  110. code < 0x21 ||
  111. code > 0x7E ||
  112. char === '(' ||
  113. char === ')' ||
  114. char === '<' ||
  115. char === '>' ||
  116. char === '@' ||
  117. char === ',' ||
  118. char === ';' ||
  119. char === ':' ||
  120. char === '\\' ||
  121. char === '"' ||
  122. char === '/' ||
  123. char === '[' ||
  124. char === ']' ||
  125. char === '?' ||
  126. char === '=' ||
  127. char === '{' ||
  128. char === '}' ||
  129. code === 32 || // SP
  130. code === 9 // HT
  131. ) {
  132. return false
  133. }
  134. }
  135. return true
  136. }
  137. /**
  138. * @see https://datatracker.ietf.org/doc/html/rfc6455#section-7-4
  139. * @param {number} code
  140. */
  141. function isValidStatusCode (code) {
  142. if (code >= 1000 && code < 1015) {
  143. return (
  144. code !== 1004 && // reserved
  145. code !== 1005 && // "MUST NOT be set as a status code"
  146. code !== 1006 // "MUST NOT be set as a status code"
  147. )
  148. }
  149. return code >= 3000 && code <= 4999
  150. }
  151. /**
  152. * @param {import('./websocket').WebSocket} ws
  153. * @param {string|undefined} reason
  154. */
  155. function failWebsocketConnection (ws, reason) {
  156. const { [kController]: controller, [kResponse]: response } = ws
  157. controller.abort()
  158. if (response?.socket && !response.socket.destroyed) {
  159. response.socket.destroy()
  160. }
  161. if (reason) {
  162. fireEvent('error', ws, ErrorEvent, {
  163. error: new Error(reason)
  164. })
  165. }
  166. }
  167. module.exports = {
  168. isEstablished,
  169. isClosing,
  170. isClosed,
  171. fireEvent,
  172. isValidSubprotocol,
  173. isValidStatusCode,
  174. failWebsocketConnection,
  175. websocketMessageReceived
  176. }