opal 1.6.1 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (212) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build.yml +17 -0
  3. data/CHANGELOG.md +35 -1
  4. data/Gemfile +1 -0
  5. data/HACKING.md +47 -26
  6. data/benchmark/benchmarks +415 -103
  7. data/benchmark/bm_call_overhead.yml +28 -0
  8. data/benchmark/run.rb +61 -40
  9. data/docs/cdp_common.json +3364 -0
  10. data/docs/cdp_common.md +18 -0
  11. data/docs/{headless_chrome.md → headless_browsers.md} +31 -12
  12. data/lib/opal/ast/builder.rb +1 -1
  13. data/lib/opal/builder.rb +6 -1
  14. data/lib/opal/builder_processors.rb +5 -3
  15. data/lib/opal/cache.rb +1 -7
  16. data/lib/opal/cli_options.rb +72 -58
  17. data/lib/opal/cli_runners/chrome.rb +47 -9
  18. data/lib/opal/cli_runners/chrome_cdp_interface.rb +238 -112
  19. data/lib/opal/cli_runners/compiler.rb +146 -13
  20. data/lib/opal/cli_runners/deno.rb +32 -0
  21. data/lib/opal/cli_runners/firefox.rb +350 -0
  22. data/lib/opal/cli_runners/firefox_cdp_interface.rb +212 -0
  23. data/lib/opal/cli_runners/node_modules/.bin/chrome-remote-interface.cmd +17 -0
  24. data/lib/opal/cli_runners/node_modules/.bin/chrome-remote-interface.ps1 +28 -0
  25. data/lib/opal/cli_runners/node_modules/.package-lock.json +41 -0
  26. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/LICENSE +1 -1
  27. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/README.md +322 -182
  28. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/bin/client.js +99 -114
  29. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/chrome-remote-interface.js +1 -11
  30. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/index.js +16 -11
  31. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/lib/api.js +41 -33
  32. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/lib/chrome.js +224 -214
  33. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/lib/devtools.js +71 -191
  34. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/lib/external-request.js +26 -6
  35. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/lib/protocol.json +20788 -9049
  36. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/lib/websocket-wrapper.js +10 -3
  37. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/package.json +59 -123
  38. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/webpack.config.js +25 -32
  39. data/lib/opal/cli_runners/node_modules/commander/History.md +298 -0
  40. data/lib/opal/cli_runners/node_modules/commander/LICENSE +22 -0
  41. data/lib/opal/cli_runners/node_modules/commander/Readme.md +217 -61
  42. data/lib/opal/cli_runners/node_modules/commander/index.js +431 -145
  43. data/lib/opal/cli_runners/node_modules/commander/package.json +16 -79
  44. data/lib/opal/cli_runners/node_modules/ws/README.md +334 -98
  45. data/lib/opal/cli_runners/node_modules/ws/browser.js +8 -0
  46. data/lib/opal/cli_runners/node_modules/ws/index.js +5 -10
  47. data/lib/opal/cli_runners/node_modules/ws/lib/buffer-util.js +129 -0
  48. data/lib/opal/cli_runners/node_modules/ws/lib/constants.js +10 -0
  49. data/lib/opal/cli_runners/node_modules/ws/lib/event-target.js +184 -0
  50. data/lib/opal/cli_runners/node_modules/ws/lib/extension.js +223 -0
  51. data/lib/opal/cli_runners/node_modules/ws/lib/limiter.js +55 -0
  52. data/lib/opal/cli_runners/node_modules/ws/lib/permessage-deflate.js +518 -0
  53. data/lib/opal/cli_runners/node_modules/ws/lib/receiver.js +607 -0
  54. data/lib/opal/cli_runners/node_modules/ws/lib/sender.js +409 -0
  55. data/lib/opal/cli_runners/node_modules/ws/lib/stream.js +180 -0
  56. data/lib/opal/cli_runners/node_modules/ws/lib/validation.js +104 -0
  57. data/lib/opal/cli_runners/node_modules/ws/lib/websocket-server.js +447 -0
  58. data/lib/opal/cli_runners/node_modules/ws/lib/websocket.js +1195 -0
  59. data/lib/opal/cli_runners/node_modules/ws/package.json +40 -106
  60. data/lib/opal/cli_runners/package-lock.json +62 -0
  61. data/lib/opal/cli_runners/package.json +1 -1
  62. data/lib/opal/cli_runners.rb +26 -4
  63. data/lib/opal/nodes/args/prepare_post_args.rb +2 -2
  64. data/lib/opal/nodes/def.rb +8 -8
  65. data/lib/opal/nodes/iter.rb +12 -12
  66. data/lib/opal/nodes/logic.rb +1 -1
  67. data/lib/opal/nodes/masgn.rb +2 -2
  68. data/lib/opal/parser/with_ruby_lexer.rb +1 -1
  69. data/lib/opal/paths.rb +14 -0
  70. data/lib/opal/rewriter.rb +2 -0
  71. data/lib/opal/rewriters/forward_args.rb +52 -4
  72. data/lib/opal/rewriters/targeted_patches.rb +94 -0
  73. data/lib/opal/version.rb +1 -1
  74. data/opal/corelib/basic_object.rb +1 -1
  75. data/opal/corelib/boolean.rb +2 -2
  76. data/opal/corelib/class.rb +11 -0
  77. data/opal/corelib/constants.rb +3 -3
  78. data/opal/corelib/enumerable.rb +4 -0
  79. data/opal/corelib/enumerator.rb +1 -1
  80. data/opal/corelib/hash.rb +2 -2
  81. data/opal/corelib/helpers.rb +1 -1
  82. data/opal/corelib/kernel.rb +3 -3
  83. data/opal/corelib/method.rb +1 -1
  84. data/opal/corelib/module.rb +29 -8
  85. data/opal/corelib/proc.rb +7 -5
  86. data/opal/corelib/runtime.js +141 -78
  87. data/opal/corelib/set.rb +252 -0
  88. data/opal/corelib/string.rb +2 -1
  89. data/opal/corelib/time.rb +2 -2
  90. data/opal/opal.rb +1 -0
  91. data/opal.gemspec +1 -0
  92. data/spec/filters/bugs/array.rb +22 -13
  93. data/spec/filters/bugs/base64.rb +5 -5
  94. data/spec/filters/bugs/basicobject.rb +16 -8
  95. data/spec/filters/bugs/bigdecimal.rb +161 -160
  96. data/spec/filters/bugs/binding.rb +10 -10
  97. data/spec/filters/bugs/class.rb +8 -8
  98. data/spec/filters/bugs/complex.rb +2 -1
  99. data/spec/filters/bugs/date.rb +79 -81
  100. data/spec/filters/bugs/datetime.rb +29 -29
  101. data/spec/filters/bugs/delegate.rb +1 -3
  102. data/spec/filters/bugs/encoding.rb +69 -69
  103. data/spec/filters/bugs/enumerable.rb +22 -20
  104. data/spec/filters/bugs/enumerator.rb +88 -85
  105. data/spec/filters/bugs/exception.rb +46 -40
  106. data/spec/filters/bugs/file.rb +32 -32
  107. data/spec/filters/bugs/float.rb +26 -21
  108. data/spec/filters/bugs/freeze.rb +88 -0
  109. data/spec/filters/bugs/hash.rb +39 -38
  110. data/spec/filters/bugs/integer.rb +57 -44
  111. data/spec/filters/bugs/io.rb +1 -1
  112. data/spec/filters/bugs/kernel.rb +349 -269
  113. data/spec/filters/bugs/language.rb +220 -188
  114. data/spec/filters/bugs/main.rb +5 -3
  115. data/spec/filters/bugs/marshal.rb +38 -38
  116. data/spec/filters/bugs/math.rb +2 -1
  117. data/spec/filters/bugs/method.rb +73 -62
  118. data/spec/filters/bugs/module.rb +163 -143
  119. data/spec/filters/bugs/numeric.rb +6 -6
  120. data/spec/filters/bugs/objectspace.rb +16 -16
  121. data/spec/filters/bugs/openstruct.rb +1 -1
  122. data/spec/filters/bugs/pack_unpack.rb +51 -51
  123. data/spec/filters/bugs/pathname.rb +7 -7
  124. data/spec/filters/bugs/proc.rb +63 -63
  125. data/spec/filters/bugs/random.rb +7 -6
  126. data/spec/filters/bugs/range.rb +12 -9
  127. data/spec/filters/bugs/rational.rb +8 -7
  128. data/spec/filters/bugs/regexp.rb +49 -48
  129. data/spec/filters/bugs/ruby-32.rb +56 -0
  130. data/spec/filters/bugs/set.rb +30 -30
  131. data/spec/filters/bugs/singleton.rb +4 -4
  132. data/spec/filters/bugs/string.rb +187 -99
  133. data/spec/filters/bugs/stringio.rb +7 -0
  134. data/spec/filters/bugs/stringscanner.rb +68 -68
  135. data/spec/filters/bugs/struct.rb +11 -9
  136. data/spec/filters/bugs/symbol.rb +1 -1
  137. data/spec/filters/bugs/time.rb +78 -63
  138. data/spec/filters/bugs/trace_point.rb +4 -4
  139. data/spec/filters/bugs/unboundmethod.rb +32 -17
  140. data/spec/filters/bugs/warnings.rb +8 -12
  141. data/spec/filters/unsupported/array.rb +24 -107
  142. data/spec/filters/unsupported/basicobject.rb +12 -12
  143. data/spec/filters/unsupported/bignum.rb +27 -52
  144. data/spec/filters/unsupported/class.rb +1 -2
  145. data/spec/filters/unsupported/delegator.rb +3 -3
  146. data/spec/filters/unsupported/enumerable.rb +2 -9
  147. data/spec/filters/unsupported/enumerator.rb +2 -11
  148. data/spec/filters/unsupported/file.rb +1 -1
  149. data/spec/filters/unsupported/float.rb +28 -47
  150. data/spec/filters/unsupported/hash.rb +8 -14
  151. data/spec/filters/unsupported/integer.rb +75 -91
  152. data/spec/filters/unsupported/kernel.rb +17 -35
  153. data/spec/filters/unsupported/language.rb +11 -19
  154. data/spec/filters/unsupported/marshal.rb +22 -41
  155. data/spec/filters/unsupported/matchdata.rb +28 -52
  156. data/spec/filters/unsupported/math.rb +1 -1
  157. data/spec/filters/unsupported/privacy.rb +229 -285
  158. data/spec/filters/unsupported/range.rb +1 -5
  159. data/spec/filters/unsupported/regexp.rb +40 -66
  160. data/spec/filters/unsupported/set.rb +2 -2
  161. data/spec/filters/unsupported/singleton.rb +4 -4
  162. data/spec/filters/unsupported/string.rb +305 -508
  163. data/spec/filters/unsupported/struct.rb +3 -4
  164. data/spec/filters/unsupported/symbol.rb +15 -18
  165. data/spec/filters/unsupported/thread.rb +1 -7
  166. data/spec/filters/unsupported/time.rb +159 -202
  167. data/spec/filters/unsupported/usage_of_files.rb +170 -259
  168. data/spec/lib/builder_spec.rb +4 -4
  169. data/spec/lib/rewriters/forward_args_spec.rb +32 -12
  170. data/spec/mspec-opal/runner.rb +2 -0
  171. data/spec/ruby_specs +4 -0
  172. data/stdlib/deno/base.rb +28 -0
  173. data/stdlib/deno/file.rb +340 -0
  174. data/stdlib/{headless_chrome.rb → headless_browser/base.rb} +1 -1
  175. data/stdlib/headless_browser/file.rb +15 -0
  176. data/stdlib/headless_browser.rb +4 -0
  177. data/stdlib/native.rb +1 -1
  178. data/stdlib/nodejs/file.rb +5 -0
  179. data/stdlib/opal/platform.rb +8 -6
  180. data/stdlib/opal-platform.rb +14 -8
  181. data/stdlib/set.rb +1 -258
  182. data/tasks/benchmarking.rake +62 -19
  183. data/tasks/performance.rake +1 -1
  184. data/tasks/testing.rake +5 -3
  185. data/test/nodejs/test_file.rb +29 -10
  186. data/test/opal/http_server.rb +28 -11
  187. data/test/opal/unsupported_and_bugs.rb +2 -1
  188. metadata +89 -50
  189. data/lib/opal/cli_runners/node_modules/ultron/LICENSE +0 -22
  190. data/lib/opal/cli_runners/node_modules/ultron/index.js +0 -138
  191. data/lib/opal/cli_runners/node_modules/ultron/package.json +0 -112
  192. data/lib/opal/cli_runners/node_modules/ws/SECURITY.md +0 -33
  193. data/lib/opal/cli_runners/node_modules/ws/lib/BufferUtil.fallback.js +0 -56
  194. data/lib/opal/cli_runners/node_modules/ws/lib/BufferUtil.js +0 -15
  195. data/lib/opal/cli_runners/node_modules/ws/lib/ErrorCodes.js +0 -28
  196. data/lib/opal/cli_runners/node_modules/ws/lib/EventTarget.js +0 -158
  197. data/lib/opal/cli_runners/node_modules/ws/lib/Extensions.js +0 -69
  198. data/lib/opal/cli_runners/node_modules/ws/lib/PerMessageDeflate.js +0 -339
  199. data/lib/opal/cli_runners/node_modules/ws/lib/Receiver.js +0 -520
  200. data/lib/opal/cli_runners/node_modules/ws/lib/Sender.js +0 -438
  201. data/lib/opal/cli_runners/node_modules/ws/lib/Validation.fallback.js +0 -9
  202. data/lib/opal/cli_runners/node_modules/ws/lib/Validation.js +0 -17
  203. data/lib/opal/cli_runners/node_modules/ws/lib/WebSocket.js +0 -705
  204. data/lib/opal/cli_runners/node_modules/ws/lib/WebSocketServer.js +0 -336
  205. data/spec/filters/bugs/boolean.rb +0 -3
  206. data/spec/filters/bugs/matrix.rb +0 -3
  207. data/spec/filters/unsupported/fixnum.rb +0 -15
  208. data/spec/filters/unsupported/freeze.rb +0 -102
  209. data/spec/filters/unsupported/pathname.rb +0 -4
  210. data/spec/filters/unsupported/proc.rb +0 -4
  211. data/spec/filters/unsupported/random.rb +0 -5
  212. data/spec/filters/unsupported/taint.rb +0 -162
@@ -0,0 +1,607 @@
1
+ 'use strict';
2
+
3
+ const { Writable } = require('stream');
4
+
5
+ const PerMessageDeflate = require('./permessage-deflate');
6
+ const {
7
+ BINARY_TYPES,
8
+ EMPTY_BUFFER,
9
+ kStatusCode,
10
+ kWebSocket
11
+ } = require('./constants');
12
+ const { concat, toArrayBuffer, unmask } = require('./buffer-util');
13
+ const { isValidStatusCode, isValidUTF8 } = require('./validation');
14
+
15
+ const GET_INFO = 0;
16
+ const GET_PAYLOAD_LENGTH_16 = 1;
17
+ const GET_PAYLOAD_LENGTH_64 = 2;
18
+ const GET_MASK = 3;
19
+ const GET_DATA = 4;
20
+ const INFLATING = 5;
21
+
22
+ /**
23
+ * HyBi Receiver implementation.
24
+ *
25
+ * @extends Writable
26
+ */
27
+ class Receiver extends Writable {
28
+ /**
29
+ * Creates a Receiver instance.
30
+ *
31
+ * @param {String} [binaryType=nodebuffer] The type for binary data
32
+ * @param {Object} [extensions] An object containing the negotiated extensions
33
+ * @param {Boolean} [isServer=false] Specifies whether to operate in client or
34
+ * server mode
35
+ * @param {Number} [maxPayload=0] The maximum allowed message length
36
+ */
37
+ constructor(binaryType, extensions, isServer, maxPayload) {
38
+ super();
39
+
40
+ this._binaryType = binaryType || BINARY_TYPES[0];
41
+ this[kWebSocket] = undefined;
42
+ this._extensions = extensions || {};
43
+ this._isServer = !!isServer;
44
+ this._maxPayload = maxPayload | 0;
45
+
46
+ this._bufferedBytes = 0;
47
+ this._buffers = [];
48
+
49
+ this._compressed = false;
50
+ this._payloadLength = 0;
51
+ this._mask = undefined;
52
+ this._fragmented = 0;
53
+ this._masked = false;
54
+ this._fin = false;
55
+ this._opcode = 0;
56
+
57
+ this._totalPayloadLength = 0;
58
+ this._messageLength = 0;
59
+ this._fragments = [];
60
+
61
+ this._state = GET_INFO;
62
+ this._loop = false;
63
+ }
64
+
65
+ /**
66
+ * Implements `Writable.prototype._write()`.
67
+ *
68
+ * @param {Buffer} chunk The chunk of data to write
69
+ * @param {String} encoding The character encoding of `chunk`
70
+ * @param {Function} cb Callback
71
+ * @private
72
+ */
73
+ _write(chunk, encoding, cb) {
74
+ if (this._opcode === 0x08 && this._state == GET_INFO) return cb();
75
+
76
+ this._bufferedBytes += chunk.length;
77
+ this._buffers.push(chunk);
78
+ this.startLoop(cb);
79
+ }
80
+
81
+ /**
82
+ * Consumes `n` bytes from the buffered data.
83
+ *
84
+ * @param {Number} n The number of bytes to consume
85
+ * @return {Buffer} The consumed bytes
86
+ * @private
87
+ */
88
+ consume(n) {
89
+ this._bufferedBytes -= n;
90
+
91
+ if (n === this._buffers[0].length) return this._buffers.shift();
92
+
93
+ if (n < this._buffers[0].length) {
94
+ const buf = this._buffers[0];
95
+ this._buffers[0] = buf.slice(n);
96
+ return buf.slice(0, n);
97
+ }
98
+
99
+ const dst = Buffer.allocUnsafe(n);
100
+
101
+ do {
102
+ const buf = this._buffers[0];
103
+ const offset = dst.length - n;
104
+
105
+ if (n >= buf.length) {
106
+ dst.set(this._buffers.shift(), offset);
107
+ } else {
108
+ dst.set(new Uint8Array(buf.buffer, buf.byteOffset, n), offset);
109
+ this._buffers[0] = buf.slice(n);
110
+ }
111
+
112
+ n -= buf.length;
113
+ } while (n > 0);
114
+
115
+ return dst;
116
+ }
117
+
118
+ /**
119
+ * Starts the parsing loop.
120
+ *
121
+ * @param {Function} cb Callback
122
+ * @private
123
+ */
124
+ startLoop(cb) {
125
+ let err;
126
+ this._loop = true;
127
+
128
+ do {
129
+ switch (this._state) {
130
+ case GET_INFO:
131
+ err = this.getInfo();
132
+ break;
133
+ case GET_PAYLOAD_LENGTH_16:
134
+ err = this.getPayloadLength16();
135
+ break;
136
+ case GET_PAYLOAD_LENGTH_64:
137
+ err = this.getPayloadLength64();
138
+ break;
139
+ case GET_MASK:
140
+ this.getMask();
141
+ break;
142
+ case GET_DATA:
143
+ err = this.getData(cb);
144
+ break;
145
+ default:
146
+ // `INFLATING`
147
+ this._loop = false;
148
+ return;
149
+ }
150
+ } while (this._loop);
151
+
152
+ cb(err);
153
+ }
154
+
155
+ /**
156
+ * Reads the first two bytes of a frame.
157
+ *
158
+ * @return {(RangeError|undefined)} A possible error
159
+ * @private
160
+ */
161
+ getInfo() {
162
+ if (this._bufferedBytes < 2) {
163
+ this._loop = false;
164
+ return;
165
+ }
166
+
167
+ const buf = this.consume(2);
168
+
169
+ if ((buf[0] & 0x30) !== 0x00) {
170
+ this._loop = false;
171
+ return error(
172
+ RangeError,
173
+ 'RSV2 and RSV3 must be clear',
174
+ true,
175
+ 1002,
176
+ 'WS_ERR_UNEXPECTED_RSV_2_3'
177
+ );
178
+ }
179
+
180
+ const compressed = (buf[0] & 0x40) === 0x40;
181
+
182
+ if (compressed && !this._extensions[PerMessageDeflate.extensionName]) {
183
+ this._loop = false;
184
+ return error(
185
+ RangeError,
186
+ 'RSV1 must be clear',
187
+ true,
188
+ 1002,
189
+ 'WS_ERR_UNEXPECTED_RSV_1'
190
+ );
191
+ }
192
+
193
+ this._fin = (buf[0] & 0x80) === 0x80;
194
+ this._opcode = buf[0] & 0x0f;
195
+ this._payloadLength = buf[1] & 0x7f;
196
+
197
+ if (this._opcode === 0x00) {
198
+ if (compressed) {
199
+ this._loop = false;
200
+ return error(
201
+ RangeError,
202
+ 'RSV1 must be clear',
203
+ true,
204
+ 1002,
205
+ 'WS_ERR_UNEXPECTED_RSV_1'
206
+ );
207
+ }
208
+
209
+ if (!this._fragmented) {
210
+ this._loop = false;
211
+ return error(
212
+ RangeError,
213
+ 'invalid opcode 0',
214
+ true,
215
+ 1002,
216
+ 'WS_ERR_INVALID_OPCODE'
217
+ );
218
+ }
219
+
220
+ this._opcode = this._fragmented;
221
+ } else if (this._opcode === 0x01 || this._opcode === 0x02) {
222
+ if (this._fragmented) {
223
+ this._loop = false;
224
+ return error(
225
+ RangeError,
226
+ `invalid opcode ${this._opcode}`,
227
+ true,
228
+ 1002,
229
+ 'WS_ERR_INVALID_OPCODE'
230
+ );
231
+ }
232
+
233
+ this._compressed = compressed;
234
+ } else if (this._opcode > 0x07 && this._opcode < 0x0b) {
235
+ if (!this._fin) {
236
+ this._loop = false;
237
+ return error(
238
+ RangeError,
239
+ 'FIN must be set',
240
+ true,
241
+ 1002,
242
+ 'WS_ERR_EXPECTED_FIN'
243
+ );
244
+ }
245
+
246
+ if (compressed) {
247
+ this._loop = false;
248
+ return error(
249
+ RangeError,
250
+ 'RSV1 must be clear',
251
+ true,
252
+ 1002,
253
+ 'WS_ERR_UNEXPECTED_RSV_1'
254
+ );
255
+ }
256
+
257
+ if (this._payloadLength > 0x7d) {
258
+ this._loop = false;
259
+ return error(
260
+ RangeError,
261
+ `invalid payload length ${this._payloadLength}`,
262
+ true,
263
+ 1002,
264
+ 'WS_ERR_INVALID_CONTROL_PAYLOAD_LENGTH'
265
+ );
266
+ }
267
+ } else {
268
+ this._loop = false;
269
+ return error(
270
+ RangeError,
271
+ `invalid opcode ${this._opcode}`,
272
+ true,
273
+ 1002,
274
+ 'WS_ERR_INVALID_OPCODE'
275
+ );
276
+ }
277
+
278
+ if (!this._fin && !this._fragmented) this._fragmented = this._opcode;
279
+ this._masked = (buf[1] & 0x80) === 0x80;
280
+
281
+ if (this._isServer) {
282
+ if (!this._masked) {
283
+ this._loop = false;
284
+ return error(
285
+ RangeError,
286
+ 'MASK must be set',
287
+ true,
288
+ 1002,
289
+ 'WS_ERR_EXPECTED_MASK'
290
+ );
291
+ }
292
+ } else if (this._masked) {
293
+ this._loop = false;
294
+ return error(
295
+ RangeError,
296
+ 'MASK must be clear',
297
+ true,
298
+ 1002,
299
+ 'WS_ERR_UNEXPECTED_MASK'
300
+ );
301
+ }
302
+
303
+ if (this._payloadLength === 126) this._state = GET_PAYLOAD_LENGTH_16;
304
+ else if (this._payloadLength === 127) this._state = GET_PAYLOAD_LENGTH_64;
305
+ else return this.haveLength();
306
+ }
307
+
308
+ /**
309
+ * Gets extended payload length (7+16).
310
+ *
311
+ * @return {(RangeError|undefined)} A possible error
312
+ * @private
313
+ */
314
+ getPayloadLength16() {
315
+ if (this._bufferedBytes < 2) {
316
+ this._loop = false;
317
+ return;
318
+ }
319
+
320
+ this._payloadLength = this.consume(2).readUInt16BE(0);
321
+ return this.haveLength();
322
+ }
323
+
324
+ /**
325
+ * Gets extended payload length (7+64).
326
+ *
327
+ * @return {(RangeError|undefined)} A possible error
328
+ * @private
329
+ */
330
+ getPayloadLength64() {
331
+ if (this._bufferedBytes < 8) {
332
+ this._loop = false;
333
+ return;
334
+ }
335
+
336
+ const buf = this.consume(8);
337
+ const num = buf.readUInt32BE(0);
338
+
339
+ //
340
+ // The maximum safe integer in JavaScript is 2^53 - 1. An error is returned
341
+ // if payload length is greater than this number.
342
+ //
343
+ if (num > Math.pow(2, 53 - 32) - 1) {
344
+ this._loop = false;
345
+ return error(
346
+ RangeError,
347
+ 'Unsupported WebSocket frame: payload length > 2^53 - 1',
348
+ false,
349
+ 1009,
350
+ 'WS_ERR_UNSUPPORTED_DATA_PAYLOAD_LENGTH'
351
+ );
352
+ }
353
+
354
+ this._payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4);
355
+ return this.haveLength();
356
+ }
357
+
358
+ /**
359
+ * Payload length has been read.
360
+ *
361
+ * @return {(RangeError|undefined)} A possible error
362
+ * @private
363
+ */
364
+ haveLength() {
365
+ if (this._payloadLength && this._opcode < 0x08) {
366
+ this._totalPayloadLength += this._payloadLength;
367
+ if (this._totalPayloadLength > this._maxPayload && this._maxPayload > 0) {
368
+ this._loop = false;
369
+ return error(
370
+ RangeError,
371
+ 'Max payload size exceeded',
372
+ false,
373
+ 1009,
374
+ 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH'
375
+ );
376
+ }
377
+ }
378
+
379
+ if (this._masked) this._state = GET_MASK;
380
+ else this._state = GET_DATA;
381
+ }
382
+
383
+ /**
384
+ * Reads mask bytes.
385
+ *
386
+ * @private
387
+ */
388
+ getMask() {
389
+ if (this._bufferedBytes < 4) {
390
+ this._loop = false;
391
+ return;
392
+ }
393
+
394
+ this._mask = this.consume(4);
395
+ this._state = GET_DATA;
396
+ }
397
+
398
+ /**
399
+ * Reads data bytes.
400
+ *
401
+ * @param {Function} cb Callback
402
+ * @return {(Error|RangeError|undefined)} A possible error
403
+ * @private
404
+ */
405
+ getData(cb) {
406
+ let data = EMPTY_BUFFER;
407
+
408
+ if (this._payloadLength) {
409
+ if (this._bufferedBytes < this._payloadLength) {
410
+ this._loop = false;
411
+ return;
412
+ }
413
+
414
+ data = this.consume(this._payloadLength);
415
+ if (this._masked) unmask(data, this._mask);
416
+ }
417
+
418
+ if (this._opcode > 0x07) return this.controlMessage(data);
419
+
420
+ if (this._compressed) {
421
+ this._state = INFLATING;
422
+ this.decompress(data, cb);
423
+ return;
424
+ }
425
+
426
+ if (data.length) {
427
+ //
428
+ // This message is not compressed so its lenght is the sum of the payload
429
+ // length of all fragments.
430
+ //
431
+ this._messageLength = this._totalPayloadLength;
432
+ this._fragments.push(data);
433
+ }
434
+
435
+ return this.dataMessage();
436
+ }
437
+
438
+ /**
439
+ * Decompresses data.
440
+ *
441
+ * @param {Buffer} data Compressed data
442
+ * @param {Function} cb Callback
443
+ * @private
444
+ */
445
+ decompress(data, cb) {
446
+ const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
447
+
448
+ perMessageDeflate.decompress(data, this._fin, (err, buf) => {
449
+ if (err) return cb(err);
450
+
451
+ if (buf.length) {
452
+ this._messageLength += buf.length;
453
+ if (this._messageLength > this._maxPayload && this._maxPayload > 0) {
454
+ return cb(
455
+ error(
456
+ RangeError,
457
+ 'Max payload size exceeded',
458
+ false,
459
+ 1009,
460
+ 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH'
461
+ )
462
+ );
463
+ }
464
+
465
+ this._fragments.push(buf);
466
+ }
467
+
468
+ const er = this.dataMessage();
469
+ if (er) return cb(er);
470
+
471
+ this.startLoop(cb);
472
+ });
473
+ }
474
+
475
+ /**
476
+ * Handles a data message.
477
+ *
478
+ * @return {(Error|undefined)} A possible error
479
+ * @private
480
+ */
481
+ dataMessage() {
482
+ if (this._fin) {
483
+ const messageLength = this._messageLength;
484
+ const fragments = this._fragments;
485
+
486
+ this._totalPayloadLength = 0;
487
+ this._messageLength = 0;
488
+ this._fragmented = 0;
489
+ this._fragments = [];
490
+
491
+ if (this._opcode === 2) {
492
+ let data;
493
+
494
+ if (this._binaryType === 'nodebuffer') {
495
+ data = concat(fragments, messageLength);
496
+ } else if (this._binaryType === 'arraybuffer') {
497
+ data = toArrayBuffer(concat(fragments, messageLength));
498
+ } else {
499
+ data = fragments;
500
+ }
501
+
502
+ this.emit('message', data);
503
+ } else {
504
+ const buf = concat(fragments, messageLength);
505
+
506
+ if (!isValidUTF8(buf)) {
507
+ this._loop = false;
508
+ return error(
509
+ Error,
510
+ 'invalid UTF-8 sequence',
511
+ true,
512
+ 1007,
513
+ 'WS_ERR_INVALID_UTF8'
514
+ );
515
+ }
516
+
517
+ this.emit('message', buf.toString());
518
+ }
519
+ }
520
+
521
+ this._state = GET_INFO;
522
+ }
523
+
524
+ /**
525
+ * Handles a control message.
526
+ *
527
+ * @param {Buffer} data Data to handle
528
+ * @return {(Error|RangeError|undefined)} A possible error
529
+ * @private
530
+ */
531
+ controlMessage(data) {
532
+ if (this._opcode === 0x08) {
533
+ this._loop = false;
534
+
535
+ if (data.length === 0) {
536
+ this.emit('conclude', 1005, '');
537
+ this.end();
538
+ } else if (data.length === 1) {
539
+ return error(
540
+ RangeError,
541
+ 'invalid payload length 1',
542
+ true,
543
+ 1002,
544
+ 'WS_ERR_INVALID_CONTROL_PAYLOAD_LENGTH'
545
+ );
546
+ } else {
547
+ const code = data.readUInt16BE(0);
548
+
549
+ if (!isValidStatusCode(code)) {
550
+ return error(
551
+ RangeError,
552
+ `invalid status code ${code}`,
553
+ true,
554
+ 1002,
555
+ 'WS_ERR_INVALID_CLOSE_CODE'
556
+ );
557
+ }
558
+
559
+ const buf = data.slice(2);
560
+
561
+ if (!isValidUTF8(buf)) {
562
+ return error(
563
+ Error,
564
+ 'invalid UTF-8 sequence',
565
+ true,
566
+ 1007,
567
+ 'WS_ERR_INVALID_UTF8'
568
+ );
569
+ }
570
+
571
+ this.emit('conclude', code, buf.toString());
572
+ this.end();
573
+ }
574
+ } else if (this._opcode === 0x09) {
575
+ this.emit('ping', data);
576
+ } else {
577
+ this.emit('pong', data);
578
+ }
579
+
580
+ this._state = GET_INFO;
581
+ }
582
+ }
583
+
584
+ module.exports = Receiver;
585
+
586
+ /**
587
+ * Builds an error object.
588
+ *
589
+ * @param {function(new:Error|RangeError)} ErrorCtor The error constructor
590
+ * @param {String} message The error message
591
+ * @param {Boolean} prefix Specifies whether or not to add a default prefix to
592
+ * `message`
593
+ * @param {Number} statusCode The status code
594
+ * @param {String} errorCode The exposed error code
595
+ * @return {(Error|RangeError)} The error
596
+ * @private
597
+ */
598
+ function error(ErrorCtor, message, prefix, statusCode, errorCode) {
599
+ const err = new ErrorCtor(
600
+ prefix ? `Invalid WebSocket frame: ${message}` : message
601
+ );
602
+
603
+ Error.captureStackTrace(err, error);
604
+ err.code = errorCode;
605
+ err[kStatusCode] = statusCode;
606
+ return err;
607
+ }