@novnc/novnc 1.2.0-test → 1.3.0-g1075cd8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/LICENSE.txt +0 -6
  2. package/README.md +16 -6
  3. package/core/decoders/copyrect.js +5 -0
  4. package/core/decoders/hextile.js +57 -3
  5. package/core/decoders/jpeg.js +141 -0
  6. package/core/decoders/raw.js +12 -2
  7. package/core/decoders/tight.js +24 -8
  8. package/core/decoders/zrle.js +185 -0
  9. package/core/display.js +9 -151
  10. package/core/encodings.js +4 -0
  11. package/core/input/domkeytable.js +25 -21
  12. package/core/input/keyboard.js +12 -127
  13. package/core/input/util.js +18 -35
  14. package/core/input/vkeys.js +0 -1
  15. package/core/input/xtscancodes.js +5 -3
  16. package/core/ra2.js +567 -0
  17. package/core/rfb.js +332 -114
  18. package/core/util/browser.js +0 -17
  19. package/core/util/cursor.js +1 -11
  20. package/core/util/events.js +0 -4
  21. package/core/util/md5.js +79 -0
  22. package/core/websock.js +76 -17
  23. package/docs/API.md +46 -6
  24. package/docs/LIBRARY.md +3 -7
  25. package/lib/base64.js +5 -5
  26. package/lib/decoders/copyrect.js +8 -3
  27. package/lib/decoders/hextile.js +65 -9
  28. package/lib/decoders/jpeg.js +188 -0
  29. package/lib/decoders/raw.js +14 -5
  30. package/lib/decoders/rre.js +3 -3
  31. package/lib/decoders/tight.js +40 -22
  32. package/lib/decoders/tightpng.js +10 -10
  33. package/lib/decoders/zrle.js +234 -0
  34. package/lib/deflator.js +5 -5
  35. package/lib/des.js +3 -3
  36. package/lib/display.js +47 -214
  37. package/lib/encodings.js +8 -0
  38. package/lib/inflator.js +5 -5
  39. package/lib/input/domkeytable.js +197 -194
  40. package/lib/input/fixedkeys.js +2 -2
  41. package/lib/input/gesturehandler.js +3 -3
  42. package/lib/input/keyboard.js +40 -160
  43. package/lib/input/keysym.js +2 -2
  44. package/lib/input/keysymdef.js +2 -2
  45. package/lib/input/util.js +35 -80
  46. package/lib/input/vkeys.js +2 -4
  47. package/lib/input/xtscancodes.js +11 -5
  48. package/lib/ra2.js +1257 -0
  49. package/lib/rfb.js +656 -306
  50. package/lib/util/browser.js +9 -27
  51. package/lib/util/cursor.js +5 -17
  52. package/lib/util/events.js +3 -5
  53. package/lib/util/eventtarget.js +4 -4
  54. package/lib/util/int.js +1 -1
  55. package/lib/util/logging.js +2 -2
  56. package/lib/util/md5.js +103 -0
  57. package/lib/vendor/pako/lib/utils/common.js +2 -2
  58. package/lib/vendor/pako/lib/zlib/adler32.js +1 -1
  59. package/lib/vendor/pako/lib/zlib/constants.js +2 -2
  60. package/lib/vendor/pako/lib/zlib/crc32.js +1 -1
  61. package/lib/vendor/pako/lib/zlib/deflate.js +114 -113
  62. package/lib/vendor/pako/lib/zlib/gzheader.js +1 -1
  63. package/lib/vendor/pako/lib/zlib/inffast.js +5 -5
  64. package/lib/vendor/pako/lib/zlib/inflate.js +51 -49
  65. package/lib/vendor/pako/lib/zlib/inftrees.js +4 -4
  66. package/lib/vendor/pako/lib/zlib/messages.js +2 -2
  67. package/lib/vendor/pako/lib/zlib/trees.js +5 -5
  68. package/lib/vendor/pako/lib/zlib/zstream.js +1 -1
  69. package/lib/websock.js +107 -46
  70. package/package.json +2 -10
  71. package/core/util/polyfill.js +0 -61
  72. package/lib/util/polyfill.js +0 -72
  73. package/lib/vendor/promise.js +0 -255
package/core/rfb.js CHANGED
@@ -25,7 +25,8 @@ import DES from "./des.js";
25
25
  import KeyTable from "./input/keysym.js";
26
26
  import XtScancode from "./input/xtscancodes.js";
27
27
  import { encodings } from "./encodings.js";
28
- import "./util/polyfill.js";
28
+ import RSAAESAuthenticationState from "./ra2.js";
29
+ import { MD5 } from "./util/md5.js";
29
30
 
30
31
  import RawDecoder from "./decoders/raw.js";
31
32
  import CopyRectDecoder from "./decoders/copyrect.js";
@@ -33,6 +34,8 @@ import RREDecoder from "./decoders/rre.js";
33
34
  import HextileDecoder from "./decoders/hextile.js";
34
35
  import TightDecoder from "./decoders/tight.js";
35
36
  import TightPNGDecoder from "./decoders/tightpng.js";
37
+ import ZRLEDecoder from "./decoders/zrle.js";
38
+ import JPEGDecoder from "./decoders/jpeg.js";
36
39
 
37
40
  // How many seconds to wait for a disconnect to finish
38
41
  const DISCONNECT_TIMEOUT = 3;
@@ -67,20 +70,25 @@ const extendedClipboardActionPeek = 1 << 26;
67
70
  const extendedClipboardActionNotify = 1 << 27;
68
71
  const extendedClipboardActionProvide = 1 << 28;
69
72
 
70
-
71
73
  export default class RFB extends EventTargetMixin {
72
- constructor(target, url, options) {
74
+ constructor(target, urlOrChannel, options) {
73
75
  if (!target) {
74
76
  throw new Error("Must specify target");
75
77
  }
76
- if (!url) {
77
- throw new Error("Must specify URL");
78
+ if (!urlOrChannel) {
79
+ throw new Error("Must specify URL, WebSocket or RTCDataChannel");
78
80
  }
79
81
 
80
82
  super();
81
83
 
82
84
  this._target = target;
83
- this._url = url;
85
+
86
+ if (typeof urlOrChannel === "string") {
87
+ this._url = urlOrChannel;
88
+ } else {
89
+ this._url = null;
90
+ this._rawChannel = urlOrChannel;
91
+ }
84
92
 
85
93
  // Connection details
86
94
  options = options || {};
@@ -94,6 +102,7 @@ export default class RFB extends EventTargetMixin {
94
102
  this._rfbInitState = '';
95
103
  this._rfbAuthScheme = -1;
96
104
  this._rfbCleanDisconnect = true;
105
+ this._rfbRSAAESAuthenticationState = null;
97
106
 
98
107
  // Server capabilities
99
108
  this._rfbVersion = 0;
@@ -130,6 +139,7 @@ export default class RFB extends EventTargetMixin {
130
139
  this._flushing = false; // Display flushing state
131
140
  this._keyboard = null; // Keyboard input handler object
132
141
  this._gestures = null; // Gesture input handler object
142
+ this._resizeObserver = null; // Resize observer object
133
143
 
134
144
  // Timers
135
145
  this._disconnTimer = null; // disconnection timer
@@ -167,10 +177,12 @@ export default class RFB extends EventTargetMixin {
167
177
  // Bound event handlers
168
178
  this._eventHandlers = {
169
179
  focusCanvas: this._focusCanvas.bind(this),
170
- windowResize: this._windowResize.bind(this),
180
+ handleResize: this._handleResize.bind(this),
171
181
  handleMouse: this._handleMouse.bind(this),
172
182
  handleWheel: this._handleWheel.bind(this),
173
183
  handleGesture: this._handleGesture.bind(this),
184
+ handleRSAAESCredentialsRequired: this._handleRSAAESCredentialsRequired.bind(this),
185
+ handleRSAAESServerVerification: this._handleRSAAESServerVerification.bind(this),
174
186
  };
175
187
 
176
188
  // main setup
@@ -187,8 +199,6 @@ export default class RFB extends EventTargetMixin {
187
199
  this._canvas.style.margin = 'auto';
188
200
  // Some browsers add an outline on focus
189
201
  this._canvas.style.outline = 'none';
190
- // IE miscalculates width without this :(
191
- this._canvas.style.flexShrink = '0';
192
202
  this._canvas.width = 0;
193
203
  this._canvas.height = 0;
194
204
  this._canvas.tabIndex = -1;
@@ -215,6 +225,8 @@ export default class RFB extends EventTargetMixin {
215
225
  this._decoders[encodings.encodingHextile] = new HextileDecoder();
216
226
  this._decoders[encodings.encodingTight] = new TightDecoder();
217
227
  this._decoders[encodings.encodingTightPNG] = new TightPNGDecoder();
228
+ this._decoders[encodings.encodingZRLE] = new ZRLEDecoder();
229
+ this._decoders[encodings.encodingJPEG] = new JPEGDecoder();
218
230
 
219
231
  // NB: nothing that needs explicit teardown should be done
220
232
  // before this point, since this can throw an exception
@@ -232,58 +244,17 @@ export default class RFB extends EventTargetMixin {
232
244
  this._gestures = new GestureHandler();
233
245
 
234
246
  this._sock = new Websock();
235
- this._sock.on('message', () => {
236
- this._handleMessage();
237
- });
238
- this._sock.on('open', () => {
239
- if ((this._rfbConnectionState === 'connecting') &&
240
- (this._rfbInitState === '')) {
241
- this._rfbInitState = 'ProtocolVersion';
242
- Log.Debug("Starting VNC handshake");
243
- } else {
244
- this._fail("Unexpected server connection while " +
245
- this._rfbConnectionState);
246
- }
247
- });
248
- this._sock.on('close', (e) => {
249
- Log.Debug("WebSocket on-close event");
250
- let msg = "";
251
- if (e.code) {
252
- msg = "(code: " + e.code;
253
- if (e.reason) {
254
- msg += ", reason: " + e.reason;
255
- }
256
- msg += ")";
257
- }
258
- switch (this._rfbConnectionState) {
259
- case 'connecting':
260
- this._fail("Connection closed " + msg);
261
- break;
262
- case 'connected':
263
- // Handle disconnects that were initiated server-side
264
- this._updateConnectionState('disconnecting');
265
- this._updateConnectionState('disconnected');
266
- break;
267
- case 'disconnecting':
268
- // Normal disconnection path
269
- this._updateConnectionState('disconnected');
270
- break;
271
- case 'disconnected':
272
- this._fail("Unexpected server disconnect " +
273
- "when already disconnected " + msg);
274
- break;
275
- default:
276
- this._fail("Unexpected server disconnect before connecting " +
277
- msg);
278
- break;
279
- }
280
- this._sock.off('close');
281
- });
282
- this._sock.on('error', e => Log.Warn("WebSocket on-error event"));
247
+ this._sock.on('open', this._socketOpen.bind(this));
248
+ this._sock.on('close', this._socketClose.bind(this));
249
+ this._sock.on('message', this._handleMessage.bind(this));
250
+ this._sock.on('error', this._socketError.bind(this));
283
251
 
284
- // Slight delay of the actual connection so that the caller has
285
- // time to set up callbacks
286
- setTimeout(this._updateConnectionState.bind(this, 'connecting'));
252
+ this._expectedClientWidth = null;
253
+ this._expectedClientHeight = null;
254
+ this._resizeObserver = new ResizeObserver(this._eventHandlers.handleResize);
255
+
256
+ // All prepared, kick off the connection
257
+ this._updateConnectionState('connecting');
287
258
 
288
259
  Log.Debug("<< RFB.constructor");
289
260
 
@@ -412,6 +383,15 @@ export default class RFB extends EventTargetMixin {
412
383
  this._sock.off('error');
413
384
  this._sock.off('message');
414
385
  this._sock.off('open');
386
+ if (this._rfbRSAAESAuthenticationState !== null) {
387
+ this._rfbRSAAESAuthenticationState.disconnect();
388
+ }
389
+ }
390
+
391
+ approveServer() {
392
+ if (this._rfbRSAAESAuthenticationState !== null) {
393
+ this._rfbRSAAESAuthenticationState.approveServer();
394
+ }
415
395
  }
416
396
 
417
397
  sendCredentials(creds) {
@@ -472,8 +452,8 @@ export default class RFB extends EventTargetMixin {
472
452
  }
473
453
  }
474
454
 
475
- focus() {
476
- this._canvas.focus();
455
+ focus(options) {
456
+ this._canvas.focus(options);
477
457
  }
478
458
 
479
459
  blur() {
@@ -504,16 +484,22 @@ export default class RFB extends EventTargetMixin {
504
484
  _connect() {
505
485
  Log.Debug(">> RFB.connect");
506
486
 
507
- Log.Info("connecting to " + this._url);
508
-
509
- try {
510
- // WebSocket.onopen transitions to the RFB init states
487
+ if (this._url) {
488
+ Log.Info(`connecting to ${this._url}`);
511
489
  this._sock.open(this._url, this._wsProtocols);
512
- } catch (e) {
513
- if (e.name === 'SyntaxError') {
514
- this._fail("Invalid host or port (" + e + ")");
515
- } else {
516
- this._fail("Error when opening socket (" + e + ")");
490
+ } else {
491
+ Log.Info(`attaching ${this._rawChannel} to Websock`);
492
+ this._sock.attach(this._rawChannel);
493
+
494
+ if (this._sock.readyState === 'closed') {
495
+ throw Error("Cannot use already closed WebSocket/RTCDataChannel");
496
+ }
497
+
498
+ if (this._sock.readyState === 'open') {
499
+ // FIXME: _socketOpen() can in theory call _fail(), which
500
+ // isn't allowed this early, but I'm not sure that can
501
+ // happen without a bug messing up our state variables
502
+ this._socketOpen();
517
503
  }
518
504
  }
519
505
 
@@ -525,9 +511,8 @@ export default class RFB extends EventTargetMixin {
525
511
  this._cursor.attach(this._canvas);
526
512
  this._refreshCursor();
527
513
 
528
- // Monitor size changes of the screen
529
- // FIXME: Use ResizeObserver, or hidden overflow
530
- window.addEventListener('resize', this._eventHandlers.windowResize);
514
+ // Monitor size changes of the screen element
515
+ this._resizeObserver.observe(this._screen);
531
516
 
532
517
  // Always grab focus on some kind of click event
533
518
  this._canvas.addEventListener("mousedown", this._eventHandlers.focusCanvas);
@@ -568,7 +553,7 @@ export default class RFB extends EventTargetMixin {
568
553
  this._canvas.removeEventListener('contextmenu', this._eventHandlers.handleMouse);
569
554
  this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas);
570
555
  this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas);
571
- window.removeEventListener('resize', this._eventHandlers.windowResize);
556
+ this._resizeObserver.disconnect();
572
557
  this._keyboard.ungrab();
573
558
  this._gestures.detach();
574
559
  this._sock.close();
@@ -587,12 +572,64 @@ export default class RFB extends EventTargetMixin {
587
572
  Log.Debug("<< RFB.disconnect");
588
573
  }
589
574
 
575
+ _socketOpen() {
576
+ if ((this._rfbConnectionState === 'connecting') &&
577
+ (this._rfbInitState === '')) {
578
+ this._rfbInitState = 'ProtocolVersion';
579
+ Log.Debug("Starting VNC handshake");
580
+ } else {
581
+ this._fail("Unexpected server connection while " +
582
+ this._rfbConnectionState);
583
+ }
584
+ }
585
+
586
+ _socketClose(e) {
587
+ Log.Debug("WebSocket on-close event");
588
+ let msg = "";
589
+ if (e.code) {
590
+ msg = "(code: " + e.code;
591
+ if (e.reason) {
592
+ msg += ", reason: " + e.reason;
593
+ }
594
+ msg += ")";
595
+ }
596
+ switch (this._rfbConnectionState) {
597
+ case 'connecting':
598
+ this._fail("Connection closed " + msg);
599
+ break;
600
+ case 'connected':
601
+ // Handle disconnects that were initiated server-side
602
+ this._updateConnectionState('disconnecting');
603
+ this._updateConnectionState('disconnected');
604
+ break;
605
+ case 'disconnecting':
606
+ // Normal disconnection path
607
+ this._updateConnectionState('disconnected');
608
+ break;
609
+ case 'disconnected':
610
+ this._fail("Unexpected server disconnect " +
611
+ "when already disconnected " + msg);
612
+ break;
613
+ default:
614
+ this._fail("Unexpected server disconnect before connecting " +
615
+ msg);
616
+ break;
617
+ }
618
+ this._sock.off('close');
619
+ // Delete reference to raw channel to allow cleanup.
620
+ this._rawChannel = null;
621
+ }
622
+
623
+ _socketError(e) {
624
+ Log.Warn("WebSocket on-error event");
625
+ }
626
+
590
627
  _focusCanvas(event) {
591
628
  if (!this.focusOnClick) {
592
629
  return;
593
630
  }
594
631
 
595
- this.focus();
632
+ this.focus({ preventScroll: true });
596
633
  }
597
634
 
598
635
  _setDesktopName(name) {
@@ -602,7 +639,26 @@ export default class RFB extends EventTargetMixin {
602
639
  { detail: { name: this._fbName } }));
603
640
  }
604
641
 
605
- _windowResize(event) {
642
+ _saveExpectedClientSize() {
643
+ this._expectedClientWidth = this._screen.clientWidth;
644
+ this._expectedClientHeight = this._screen.clientHeight;
645
+ }
646
+
647
+ _currentClientSize() {
648
+ return [this._screen.clientWidth, this._screen.clientHeight];
649
+ }
650
+
651
+ _clientHasExpectedSize() {
652
+ const [currentWidth, currentHeight] = this._currentClientSize();
653
+ return currentWidth == this._expectedClientWidth &&
654
+ currentHeight == this._expectedClientHeight;
655
+ }
656
+
657
+ _handleResize() {
658
+ // Don't change anything if the client size is already as expected
659
+ if (this._clientHasExpectedSize()) {
660
+ return;
661
+ }
606
662
  // If the window resized then our screen element might have
607
663
  // as well. Update the viewport dimensions.
608
664
  window.requestAnimationFrame(() => {
@@ -643,6 +699,12 @@ export default class RFB extends EventTargetMixin {
643
699
  this._display.viewportChangeSize(size.w, size.h);
644
700
  this._fixScrollbars();
645
701
  }
702
+
703
+ // When changing clipping we might show or hide scrollbars.
704
+ // This causes the expected client dimensions to change.
705
+ if (curClip !== newClip) {
706
+ this._saveExpectedClientSize();
707
+ }
646
708
  }
647
709
 
648
710
  _updateScale() {
@@ -667,6 +729,7 @@ export default class RFB extends EventTargetMixin {
667
729
  }
668
730
 
669
731
  const size = this._screenSize();
732
+
670
733
  RFB.messages.setDesktopSize(this._sock,
671
734
  Math.floor(size.w), Math.floor(size.h),
672
735
  this._screenID, this._screenFlags);
@@ -682,12 +745,13 @@ export default class RFB extends EventTargetMixin {
682
745
  }
683
746
 
684
747
  _fixScrollbars() {
685
- // This is a hack because Chrome screws up the calculation
686
- // for when scrollbars are needed. So to fix it we temporarily
687
- // toggle them off and on.
748
+ // This is a hack because Safari on macOS screws up the calculation
749
+ // for when scrollbars are needed. We get scrollbars when making the
750
+ // browser smaller, despite remote resize being enabled. So to fix it
751
+ // we temporarily toggle them off and on.
688
752
  const orig = this._screen.style.overflow;
689
753
  this._screen.style.overflow = 'hidden';
690
- // Force Chrome to recalculate the layout by asking for
754
+ // Force Safari to recalculate the layout by asking for
691
755
  // an element's dimensions
692
756
  this._screen.getBoundingClientRect();
693
757
  this._screen.style.overflow = orig;
@@ -1225,13 +1289,13 @@ export default class RFB extends EventTargetMixin {
1225
1289
  break;
1226
1290
  case "003.003":
1227
1291
  case "003.006": // UltraVNC
1228
- case "003.889": // Apple Remote Desktop
1229
1292
  this._rfbVersion = 3.3;
1230
1293
  break;
1231
1294
  case "003.007":
1232
1295
  this._rfbVersion = 3.7;
1233
1296
  break;
1234
1297
  case "003.008":
1298
+ case "003.889": // Apple Remote Desktop
1235
1299
  case "004.000": // Intel AMT KVM
1236
1300
  case "004.001": // RealVNC 4.6
1237
1301
  case "005.000": // RealVNC 5.3
@@ -1263,17 +1327,6 @@ export default class RFB extends EventTargetMixin {
1263
1327
  }
1264
1328
 
1265
1329
  _negotiateSecurity() {
1266
- // Polyfill since IE and PhantomJS doesn't have
1267
- // TypedArray.includes()
1268
- function includes(item, array) {
1269
- for (let i = 0; i < array.length; i++) {
1270
- if (array[i] === item) {
1271
- return true;
1272
- }
1273
- }
1274
- return false;
1275
- }
1276
-
1277
1330
  if (this._rfbVersion >= 3.7) {
1278
1331
  // Server sends supported list, client decides
1279
1332
  const numTypes = this._sock.rQshift8();
@@ -1290,15 +1343,19 @@ export default class RFB extends EventTargetMixin {
1290
1343
  Log.Debug("Server security types: " + types);
1291
1344
 
1292
1345
  // Look for each auth in preferred order
1293
- if (includes(1, types)) {
1346
+ if (types.includes(1)) {
1294
1347
  this._rfbAuthScheme = 1; // None
1295
- } else if (includes(22, types)) {
1348
+ } else if (types.includes(22)) {
1296
1349
  this._rfbAuthScheme = 22; // XVP
1297
- } else if (includes(16, types)) {
1350
+ } else if (types.includes(16)) {
1298
1351
  this._rfbAuthScheme = 16; // Tight
1299
- } else if (includes(2, types)) {
1352
+ } else if (types.includes(6)) {
1353
+ this._rfbAuthScheme = 6; // RA2ne Auth
1354
+ } else if (types.includes(2)) {
1300
1355
  this._rfbAuthScheme = 2; // VNC Auth
1301
- } else if (includes(19, types)) {
1356
+ } else if (types.includes(30)) {
1357
+ this._rfbAuthScheme = 30; // ARD Auth
1358
+ } else if (types.includes(19)) {
1302
1359
  this._rfbAuthScheme = 19; // VeNCrypt Auth
1303
1360
  } else {
1304
1361
  return this._fail("Unsupported security types (types: " + types + ")");
@@ -1441,8 +1498,8 @@ export default class RFB extends EventTargetMixin {
1441
1498
 
1442
1499
  // negotiated Plain subtype, server waits for password
1443
1500
  if (this._rfbVeNCryptState == 4) {
1444
- if (!this._rfbCredentials.username ||
1445
- !this._rfbCredentials.password) {
1501
+ if (this._rfbCredentials.username === undefined ||
1502
+ this._rfbCredentials.password === undefined) {
1446
1503
  this.dispatchEvent(new CustomEvent(
1447
1504
  "credentialsrequired",
1448
1505
  { detail: { types: ["username", "password"] } }));
@@ -1452,9 +1509,18 @@ export default class RFB extends EventTargetMixin {
1452
1509
  const user = encodeUTF8(this._rfbCredentials.username);
1453
1510
  const pass = encodeUTF8(this._rfbCredentials.password);
1454
1511
 
1455
- // XXX we assume lengths are <= 255 (should not be an issue in the real world)
1456
- this._sock.send([0, 0, 0, user.length]);
1457
- this._sock.send([0, 0, 0, pass.length]);
1512
+ this._sock.send([
1513
+ (user.length >> 24) & 0xFF,
1514
+ (user.length >> 16) & 0xFF,
1515
+ (user.length >> 8) & 0xFF,
1516
+ user.length & 0xFF
1517
+ ]);
1518
+ this._sock.send([
1519
+ (pass.length >> 24) & 0xFF,
1520
+ (pass.length >> 16) & 0xFF,
1521
+ (pass.length >> 8) & 0xFF,
1522
+ pass.length & 0xFF
1523
+ ]);
1458
1524
  this._sock.sendString(user);
1459
1525
  this._sock.sendString(pass);
1460
1526
 
@@ -1481,6 +1547,117 @@ export default class RFB extends EventTargetMixin {
1481
1547
  return true;
1482
1548
  }
1483
1549
 
1550
+ _negotiateARDAuth() {
1551
+
1552
+ if (this._rfbCredentials.username === undefined ||
1553
+ this._rfbCredentials.password === undefined) {
1554
+ this.dispatchEvent(new CustomEvent(
1555
+ "credentialsrequired",
1556
+ { detail: { types: ["username", "password"] } }));
1557
+ return false;
1558
+ }
1559
+
1560
+ if (this._rfbCredentials.ardPublicKey != undefined &&
1561
+ this._rfbCredentials.ardCredentials != undefined) {
1562
+ // if the async web crypto is done return the results
1563
+ this._sock.send(this._rfbCredentials.ardCredentials);
1564
+ this._sock.send(this._rfbCredentials.ardPublicKey);
1565
+ this._rfbCredentials.ardCredentials = null;
1566
+ this._rfbCredentials.ardPublicKey = null;
1567
+ this._rfbInitState = "SecurityResult";
1568
+ return true;
1569
+ }
1570
+
1571
+ if (this._sock.rQwait("read ard", 4)) { return false; }
1572
+
1573
+ let generator = this._sock.rQshiftBytes(2); // DH base generator value
1574
+
1575
+ let keyLength = this._sock.rQshift16();
1576
+
1577
+ if (this._sock.rQwait("read ard keylength", keyLength*2, 4)) { return false; }
1578
+
1579
+ // read the server values
1580
+ let prime = this._sock.rQshiftBytes(keyLength); // predetermined prime modulus
1581
+ let serverPublicKey = this._sock.rQshiftBytes(keyLength); // other party's public key
1582
+
1583
+ let clientPrivateKey = window.crypto.getRandomValues(new Uint8Array(keyLength));
1584
+ let padding = Array.from(window.crypto.getRandomValues(new Uint8Array(64)), byte => String.fromCharCode(65+byte%26)).join('');
1585
+
1586
+ this._negotiateARDAuthAsync(generator, keyLength, prime, serverPublicKey, clientPrivateKey, padding);
1587
+
1588
+ return false;
1589
+ }
1590
+
1591
+ _modPow(base, exponent, modulus) {
1592
+
1593
+ let baseHex = "0x"+Array.from(base, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
1594
+ let exponentHex = "0x"+Array.from(exponent, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
1595
+ let modulusHex = "0x"+Array.from(modulus, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
1596
+
1597
+ let b = BigInt(baseHex);
1598
+ let e = BigInt(exponentHex);
1599
+ let m = BigInt(modulusHex);
1600
+ let r = 1n;
1601
+ b = b % m;
1602
+ while (e > 0) {
1603
+ if (e % 2n === 1n) {
1604
+ r = (r * b) % m;
1605
+ }
1606
+ e = e / 2n;
1607
+ b = (b * b) % m;
1608
+ }
1609
+ let hexResult = r.toString(16);
1610
+
1611
+ while (hexResult.length/2<exponent.length || (hexResult.length%2 != 0)) {
1612
+ hexResult = "0"+hexResult;
1613
+ }
1614
+
1615
+ let bytesResult = [];
1616
+ for (let c = 0; c < hexResult.length; c += 2) {
1617
+ bytesResult.push(parseInt(hexResult.substr(c, 2), 16));
1618
+ }
1619
+ return bytesResult;
1620
+ }
1621
+
1622
+ async _aesEcbEncrypt(string, key) {
1623
+ // perform AES-ECB blocks
1624
+ let keyString = Array.from(key, byte => String.fromCharCode(byte)).join('');
1625
+ let aesKey = await window.crypto.subtle.importKey("raw", MD5(keyString), {name: "AES-CBC"}, false, ["encrypt"]);
1626
+ let data = new Uint8Array(string.length);
1627
+ for (let i = 0; i < string.length; ++i) {
1628
+ data[i] = string.charCodeAt(i);
1629
+ }
1630
+ let encrypted = new Uint8Array(data.length);
1631
+ for (let i=0;i<data.length;i+=16) {
1632
+ let block = data.slice(i, i+16);
1633
+ let encryptedBlock = await window.crypto.subtle.encrypt({name: "AES-CBC", iv: block},
1634
+ aesKey, new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
1635
+ );
1636
+ encrypted.set((new Uint8Array(encryptedBlock)).slice(0, 16), i);
1637
+ }
1638
+ return encrypted;
1639
+ }
1640
+
1641
+ async _negotiateARDAuthAsync(generator, keyLength, prime, serverPublicKey, clientPrivateKey, padding) {
1642
+ // calculate the DH keys
1643
+ let clientPublicKey = this._modPow(generator, clientPrivateKey, prime);
1644
+ let sharedKey = this._modPow(serverPublicKey, clientPrivateKey, prime);
1645
+
1646
+ let username = encodeUTF8(this._rfbCredentials.username).substring(0, 63);
1647
+ let password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);
1648
+
1649
+ let paddedUsername = username + '\0' + padding.substring(0, 63);
1650
+ let paddedPassword = password + '\0' + padding.substring(0, 63);
1651
+ let credentials = paddedUsername.substring(0, 64) + paddedPassword.substring(0, 64);
1652
+
1653
+ let encrypted = await this._aesEcbEncrypt(credentials, sharedKey);
1654
+
1655
+ this._rfbCredentials.ardCredentials = encrypted;
1656
+ this._rfbCredentials.ardPublicKey = clientPublicKey;
1657
+
1658
+ setTimeout(this._initMsg.bind(this), 0);
1659
+ }
1660
+
1484
1661
  _negotiateTightUnixAuth() {
1485
1662
  if (this._rfbCredentials.username === undefined ||
1486
1663
  this._rfbCredentials.password === undefined) {
@@ -1604,6 +1781,44 @@ export default class RFB extends EventTargetMixin {
1604
1781
  return this._fail("No supported sub-auth types!");
1605
1782
  }
1606
1783
 
1784
+ _handleRSAAESCredentialsRequired(event) {
1785
+ this.dispatchEvent(event);
1786
+ }
1787
+
1788
+ _handleRSAAESServerVerification(event) {
1789
+ this.dispatchEvent(event);
1790
+ }
1791
+
1792
+ _negotiateRA2neAuth() {
1793
+ if (this._rfbRSAAESAuthenticationState === null) {
1794
+ this._rfbRSAAESAuthenticationState = new RSAAESAuthenticationState(this._sock, () => this._rfbCredentials);
1795
+ this._rfbRSAAESAuthenticationState.addEventListener(
1796
+ "serververification", this._eventHandlers.handleRSAAESServerVerification);
1797
+ this._rfbRSAAESAuthenticationState.addEventListener(
1798
+ "credentialsrequired", this._eventHandlers.handleRSAAESCredentialsRequired);
1799
+ }
1800
+ this._rfbRSAAESAuthenticationState.checkInternalEvents();
1801
+ if (!this._rfbRSAAESAuthenticationState.hasStarted) {
1802
+ this._rfbRSAAESAuthenticationState.negotiateRA2neAuthAsync()
1803
+ .catch((e) => {
1804
+ if (e.message !== "disconnect normally") {
1805
+ this._fail(e.message);
1806
+ }
1807
+ }).then(() => {
1808
+ this.dispatchEvent(new CustomEvent('securityresult'));
1809
+ this._rfbInitState = "SecurityResult";
1810
+ this._initMsg();
1811
+ }).finally(() => {
1812
+ this._rfbRSAAESAuthenticationState.removeEventListener(
1813
+ "serververification", this._eventHandlers.handleRSAAESServerVerification);
1814
+ this._rfbRSAAESAuthenticationState.removeEventListener(
1815
+ "credentialsrequired", this._eventHandlers.handleRSAAESCredentialsRequired);
1816
+ this._rfbRSAAESAuthenticationState = null;
1817
+ });
1818
+ }
1819
+ return false;
1820
+ }
1821
+
1607
1822
  _negotiateAuthentication() {
1608
1823
  switch (this._rfbAuthScheme) {
1609
1824
  case 1: // no auth
@@ -1617,6 +1832,9 @@ export default class RFB extends EventTargetMixin {
1617
1832
  case 22: // XVP auth
1618
1833
  return this._negotiateXvpAuth();
1619
1834
 
1835
+ case 30: // ARD auth
1836
+ return this._negotiateARDAuth();
1837
+
1620
1838
  case 2: // VNC authentication
1621
1839
  return this._negotiateStdVNCAuth();
1622
1840
 
@@ -1629,6 +1847,9 @@ export default class RFB extends EventTargetMixin {
1629
1847
  case 129: // TightVNC UNIX Security Type
1630
1848
  return this._negotiateTightUnixAuth();
1631
1849
 
1850
+ case 6: // RA2ne Security Type
1851
+ return this._negotiateRA2neAuth();
1852
+
1632
1853
  default:
1633
1854
  return this._fail("Unsupported auth scheme (scheme: " +
1634
1855
  this._rfbAuthScheme + ")");
@@ -1757,6 +1978,8 @@ export default class RFB extends EventTargetMixin {
1757
1978
  if (this._fbDepth == 24) {
1758
1979
  encs.push(encodings.encodingTight);
1759
1980
  encs.push(encodings.encodingTightPNG);
1981
+ encs.push(encodings.encodingZRLE);
1982
+ encs.push(encodings.encodingJPEG);
1760
1983
  encs.push(encodings.encodingHextile);
1761
1984
  encs.push(encodings.encodingRRE);
1762
1985
  }
@@ -2183,15 +2406,7 @@ export default class RFB extends EventTargetMixin {
2183
2406
  return this._handleCursor();
2184
2407
 
2185
2408
  case encodings.pseudoEncodingQEMUExtendedKeyEvent:
2186
- // Old Safari doesn't support creating keyboard events
2187
- try {
2188
- const keyboardEvent = document.createEvent("keyboardEvent");
2189
- if (keyboardEvent.code !== undefined) {
2190
- this._qemuExtKeyEventSupported = true;
2191
- }
2192
- } catch (err) {
2193
- // Do nothing
2194
- }
2409
+ this._qemuExtKeyEventSupported = true;
2195
2410
  return true;
2196
2411
 
2197
2412
  case encodings.pseudoEncodingDesktopName:
@@ -2493,6 +2708,9 @@ export default class RFB extends EventTargetMixin {
2493
2708
  this._updateScale();
2494
2709
 
2495
2710
  this._updateContinuousUpdates();
2711
+
2712
+ // Keep this size until browser client size changes
2713
+ this._saveExpectedClientSize();
2496
2714
  }
2497
2715
 
2498
2716
  _xvpOp(ver, op) {
@@ -2882,9 +3100,9 @@ RFB.messages = {
2882
3100
  buff[offset + 12] = 0; // blue-max
2883
3101
  buff[offset + 13] = (1 << bits) - 1; // blue-max
2884
3102
 
2885
- buff[offset + 14] = bits * 2; // red-shift
3103
+ buff[offset + 14] = bits * 0; // red-shift
2886
3104
  buff[offset + 15] = bits * 1; // green-shift
2887
- buff[offset + 16] = bits * 0; // blue-shift
3105
+ buff[offset + 16] = bits * 2; // blue-shift
2888
3106
 
2889
3107
  buff[offset + 17] = 0; // padding
2890
3108
  buff[offset + 18] = 0; // padding