1/* 2 * noVNC: HTML5 VNC client 3 * Copyright (C) 2012 Joel Martin 4 * Copyright (C) 2013 Samuel Mannehed for Cendio AB 5 * Licensed under MPL 2.0 (see LICENSE.txt) 6 * 7 * See README.md for usage and integration instructions. 8 * 9 * TIGHT decoder portion: 10 * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca) 11 */ 12 13/*jslint white: false, browser: true */ 14/*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES */ 15 16var RFB; 17 18(function () { 19 "use strict"; 20 RFB = function (defaults) { 21 if (!defaults) { 22 defaults = {}; 23 } 24 25 this._rfb_host = ''; 26 this._rfb_port = 5900; 27 this._rfb_password = ''; 28 this._rfb_path = ''; 29 30 this._rfb_state = 'disconnected'; 31 this._rfb_version = 0; 32 this._rfb_max_version = 3.8; 33 this._rfb_auth_scheme = ''; 34 35 this._rfb_tightvnc = false; 36 this._rfb_xvp_ver = 0; 37 38 // In preference order 39 this._encodings = [ 40 ['COPYRECT', 0x01 ], 41 ['TIGHT', 0x07 ], 42 ['TIGHT_PNG', -260 ], 43 ['HEXTILE', 0x05 ], 44 ['RRE', 0x02 ], 45 ['RAW', 0x00 ], 46 ['DesktopSize', -223 ], 47 ['Cursor', -239 ], 48 49 // Psuedo-encoding settings 50 //['JPEG_quality_lo', -32 ], 51 ['JPEG_quality_med', -26 ], 52 //['JPEG_quality_hi', -23 ], 53 //['compress_lo', -255 ], 54 ['compress_hi', -247 ], 55 ['last_rect', -224 ], 56 ['xvp', -309 ] 57 ]; 58 59 this._encHandlers = {}; 60 this._encNames = {}; 61 this._encStats = {}; 62 63 this._sock = null; // Websock object 64 this._display = null; // Display object 65 this._keyboard = null; // Keyboard input handler object 66 this._mouse = null; // Mouse input handler object 67 this._sendTimer = null; // Send Queue check timer 68 this._disconnTimer = null; // disconnection timer 69 this._msgTimer = null; // queued handle_msg timer 70 71 // Frame buffer update state 72 this._FBU = { 73 rects: 0, 74 subrects: 0, // RRE 75 lines: 0, // RAW 76 tiles: 0, // HEXTILE 77 bytes: 0, 78 x: 0, 79 y: 0, 80 width: 0, 81 height: 0, 82 encoding: 0, 83 subencoding: -1, 84 background: null, 85 zlib: [] // TIGHT zlib streams 86 }; 87 88 this._fb_Bpp = 4; 89 this._fb_depth = 3; 90 this._fb_width = 0; 91 this._fb_height = 0; 92 this._fb_name = ""; 93 94 this._rre_chunk_sz = 100; 95 96 this._timing = { 97 last_fbu: 0, 98 fbu_total: 0, 99 fbu_total_cnt: 0, 100 full_fbu_total: 0, 101 full_fbu_cnt: 0, 102 103 fbu_rt_start: 0, 104 fbu_rt_total: 0, 105 fbu_rt_cnt: 0, 106 pixels: 0 107 }; 108 109 // Mouse state 110 this._mouse_buttonMask = 0; 111 this._mouse_arr = []; 112 this._viewportDragging = false; 113 this._viewportDragPos = {}; 114 115 // set the default value on user-facing properties 116 Util.set_defaults(this, defaults, { 117 'target': 'null', // VNC display rendering Canvas object 118 'focusContainer': document, // DOM element that captures keyboard input 119 'encrypt': false, // Use TLS/SSL/wss encryption 120 'true_color': true, // Request true color pixel data 121 'local_cursor': false, // Request locally rendered cursor 122 'shared': true, // Request shared mode 123 'view_only': false, // Disable client mouse/keyboard 124 'xvp_password_sep': '@', // Separator for XVP password fields 125 'disconnectTimeout': 3, // Time (s) to wait for disconnection 126 'wsProtocols': ['binary', 'base64'], // Protocols to use in the WebSocket connection 127 'repeaterID': '', // [UltraVNC] RepeaterID to connect to 128 'viewportDrag': false, // Move the viewport on mouse drags 129 130 // Callback functions 131 'onUpdateState': function () { }, // onUpdateState(rfb, state, oldstate, statusMsg): state update/change 132 'onPasswordRequired': function () { }, // onPasswordRequired(rfb): VNC password is required 133 'onClipboard': function () { }, // onClipboard(rfb, text): RFB clipboard contents received 134 'onBell': function () { }, // onBell(rfb): RFB Bell message received 135 'onFBUReceive': function () { }, // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed 136 'onFBUComplete': function () { }, // onFBUComplete(rfb, fbu): RFB FBU received and processed 137 'onFBResize': function () { }, // onFBResize(rfb, width, height): frame buffer resized 138 'onDesktopName': function () { }, // onDesktopName(rfb, name): desktop name received 139 'onXvpInit': function () { }, // onXvpInit(version): XVP extensions active for this connection 140 }); 141 142 // main setup 143 Util.Debug(">> RFB.constructor"); 144 145 // populate encHandlers with bound versions 146 Object.keys(RFB.encodingHandlers).forEach(function (encName) { 147 this._encHandlers[encName] = RFB.encodingHandlers[encName].bind(this); 148 }.bind(this)); 149 150 // Create lookup tables based on encoding number 151 for (var i = 0; i < this._encodings.length; i++) { 152 this._encHandlers[this._encodings[i][1]] = this._encHandlers[this._encodings[i][0]]; 153 this._encNames[this._encodings[i][1]] = this._encodings[i][0]; 154 this._encStats[this._encodings[i][1]] = [0, 0]; 155 } 156 157 try { 158 this._display = new Display({target: this._target}); 159 } catch (exc) { 160 Util.Error("Display exception: " + exc); 161 this._updateState('fatal', "No working Display"); 162 } 163 164 this._keyboard = new Keyboard({target: this._focusContainer, 165 onKeyPress: this._handleKeyPress.bind(this)}); 166 167 this._mouse = new Mouse({target: this._target, 168 onMouseButton: this._handleMouseButton.bind(this), 169 onMouseMove: this._handleMouseMove.bind(this), 170 notify: this._keyboard.sync.bind(this._keyboard)}); 171 172 this._sock = new Websock(); 173 this._sock.on('message', this._handle_message.bind(this)); 174 this._sock.on('open', function () { 175 if (this._rfb_state === 'connect') { 176 this._updateState('ProtocolVersion', "Starting VNC handshake"); 177 } else { 178 this._fail("Got unexpected WebSocket connection"); 179 } 180 }.bind(this)); 181 this._sock.on('close', function (e) { 182 Util.Warn("WebSocket on-close event"); 183 var msg = ""; 184 if (e.code) { 185 msg = " (code: " + e.code; 186 if (e.reason) { 187 msg += ", reason: " + e.reason; 188 } 189 msg += ")"; 190 } 191 if (this._rfb_state === 'disconnect') { 192 this._updateState('disconnected', 'VNC disconnected' + msg); 193 } else if (this._rfb_state === 'ProtocolVersion') { 194 this._fail('Failed to connect to server' + msg); 195 } else if (this._rfb_state in {'failed': 1, 'disconnected': 1}) { 196 Util.Error("Received onclose while disconnected" + msg); 197 } else { 198 this._fail("Server disconnected" + msg); 199 } 200 }.bind(this)); 201 this._sock.on('error', function (e) { 202 Util.Warn("WebSocket on-error event"); 203 }); 204 205 this._init_vars(); 206 207 var rmode = this._display.get_render_mode(); 208 if (Websock_native) { 209 Util.Info("Using native WebSockets"); 210 this._updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode); 211 } else { 212 Util.Warn("Using web-socket-js bridge. Flash version: " + Util.Flash.version); 213 if (!Util.Flash || Util.Flash.version < 9) { 214 this._updateState('fatal', "WebSockets or <a href='http://get.adobe.com/flashplayer'>Adobe Flash</a> is required"); 215 } else if (document.location.href.substr(0, 7) === 'file://') { 216 this._updateState('fatal', "'file://' URL is incompatible with Adobe Flash"); 217 } else { 218 this._updateState('loaded', 'noVNC ready: WebSockets emulation, ' + rmode); 219 } 220 } 221 222 Util.Debug("<< RFB.constructor"); 223 }; 224 225 RFB.prototype = { 226 // Public methods 227 connect: function (host, port, password, path) { 228 this._rfb_host = host; 229 this._rfb_port = port; 230 this._rfb_password = (password !== undefined) ? password : ""; 231 this._rfb_path = (path !== undefined) ? path : ""; 232 233 if (!this._rfb_host || !this._rfb_port) { 234 return this._fail("Must set host and port"); 235 } 236 237 this._updateState('connect'); 238 }, 239 240 disconnect: function () { 241 this._updateState('disconnect', 'Disconnecting'); 242 }, 243 244 sendPassword: function (passwd) { 245 this._rfb_password = passwd; 246 this._rfb_state = 'Authentication'; 247 setTimeout(this._init_msg.bind(this), 1); 248 }, 249 250 sendCtrlAltDel: function () { 251 if (this._rfb_state !== 'normal' || this._view_only) { return false; } 252 Util.Info("Sending Ctrl-Alt-Del"); 253 254 var arr = []; 255 arr = arr.concat(RFB.messages.keyEvent(0xFFE3, 1)); // Control 256 arr = arr.concat(RFB.messages.keyEvent(0xFFE9, 1)); // Alt 257 arr = arr.concat(RFB.messages.keyEvent(0xFFFF, 1)); // Delete 258 arr = arr.concat(RFB.messages.keyEvent(0xFFFF, 0)); // Delete 259 arr = arr.concat(RFB.messages.keyEvent(0xFFE9, 0)); // Alt 260 arr = arr.concat(RFB.messages.keyEvent(0xFFE3, 0)); // Control 261 this._sock.send(arr); 262 }, 263 264 xvpOp: function (ver, op) { 265 if (this._rfb_xvp_ver < ver) { return false; } 266 Util.Info("Sending XVP operation " + op + " (version " + ver + ")"); 267 this._sock.send_string("\xFA\x00" + String.fromCharCode(ver) + String.fromCharCode(op)); 268 return true; 269 }, 270 271 xvpShutdown: function () { 272 return this.xvpOp(1, 2); 273 }, 274 275 xvpReboot: function () { 276 return this.xvpOp(1, 3); 277 }, 278 279 xvpReset: function () { 280 return this.xvpOp(1, 4); 281 }, 282 283 // Send a key press. If 'down' is not specified then send a down key 284 // followed by an up key. 285 sendKey: function (code, down) { 286 if (this._rfb_state !== "normal" || this._view_only) { return false; } 287 var arr = []; 288 if (typeof down !== 'undefined') { 289 Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code); 290 arr = arr.concat(RFB.messages.keyEvent(code, down ? 1 : 0)); 291 } else { 292 Util.Info("Sending key code (down + up): " + code); 293 arr = arr.concat(RFB.messages.keyEvent(code, 1)); 294 arr = arr.concat(RFB.messages.keyEvent(code, 0)); 295 } 296 this._sock.send(arr); 297 }, 298 299 clipboardPasteFrom: function (text) { 300 if (this._rfb_state !== 'normal') { return; } 301 this._sock.send(RFB.messages.clientCutText(text)); 302 }, 303 304 // Private methods 305 306 _connect: function () { 307 Util.Debug(">> RFB.connect"); 308 309 var uri; 310 if (typeof UsingSocketIO !== 'undefined') { 311 uri = 'http'; 312 } else { 313 uri = this._encrypt ? 'wss' : 'ws'; 314 } 315 316 uri += '://' + this._rfb_host + ':' + this._rfb_port + '/' + this._rfb_path; 317 Util.Info("connecting to " + uri); 318 319 this._sock.open(uri, this._wsProtocols); 320 321 Util.Debug("<< RFB.connect"); 322 }, 323 324 _init_vars: function () { 325 // reset state 326 this._sock.init(); 327 328 this._FBU.rects = 0; 329 this._FBU.subrects = 0; // RRE and HEXTILE 330 this._FBU.lines = 0; // RAW 331 this._FBU.tiles = 0; // HEXTILE 332 this._FBU.zlibs = []; // TIGHT zlib encoders 333 this._mouse_buttonMask = 0; 334 this._mouse_arr = []; 335 this._rfb_tightvnc = false; 336 337 // Clear the per connection encoding stats 338 var i; 339 for (i = 0; i < this._encodings.length; i++) { 340 this._encStats[this._encodings[i][1]][0] = 0; 341 } 342 343 for (i = 0; i < 4; i++) { 344 this._FBU.zlibs[i] = new TINF(); 345 this._FBU.zlibs[i].init(); 346 } 347 }, 348 349 _print_stats: function () { 350 Util.Info("Encoding stats for this connection:"); 351 var i, s; 352 for (i = 0; i < this._encodings.length; i++) { 353 s = this._encStats[this._encodings[i][1]]; 354 if (s[0] + s[1] > 0) { 355 Util.Info(" " + this._encodings[i][0] + ": " + s[0] + " rects"); 356 } 357 } 358 359 Util.Info("Encoding stats since page load:"); 360 for (i = 0; i < this._encodings.length; i++) { 361 s = this._encStats[this._encodings[i][1]]; 362 Util.Info(" " + this._encodings[i][0] + ": " + s[1] + " rects"); 363 } 364 }, 365 366 367 /* 368 * Page states: 369 * loaded - page load, equivalent to disconnected 370 * disconnected - idle state 371 * connect - starting to connect (to ProtocolVersion) 372 * normal - connected 373 * disconnect - starting to disconnect 374 * failed - abnormal disconnect 375 * fatal - failed to load page, or fatal error 376 * 377 * RFB protocol initialization states: 378 * ProtocolVersion 379 * Security 380 * Authentication 381 * password - waiting for password, not part of RFB 382 * SecurityResult 383 * ClientInitialization - not triggered by server message 384 * ServerInitialization (to normal) 385 */ 386 _updateState: function (state, statusMsg) { 387 var oldstate = this._rfb_state; 388 389 if (state === oldstate) { 390 // Already here, ignore 391 Util.Debug("Already in state '" + state + "', ignoring"); 392 } 393 394 /* 395 * These are disconnected states. A previous connect may 396 * asynchronously cause a connection so make sure we are closed. 397 */ 398 if (state in {'disconnected': 1, 'loaded': 1, 'connect': 1, 399 'disconnect': 1, 'failed': 1, 'fatal': 1}) { 400 401 if (this._sendTimer) { 402 clearInterval(this._sendTimer); 403 this._sendTimer = null; 404 } 405 406 if (this._msgTimer) { 407 clearInterval(this._msgTimer); 408 this._msgTimer = null; 409 } 410 411 if (this._display && this._display.get_context()) { 412 this._keyboard.ungrab(); 413 this._mouse.ungrab(); 414 this._display.defaultCursor(); 415 if (Util.get_logging() !== 'debug' || state === 'loaded') { 416 // Show noVNC logo on load and when disconnected, unless in 417 // debug mode 418 this._display.clear(); 419 } 420 } 421 422 this._sock.close(); 423 } 424 425 if (oldstate === 'fatal') { 426 Util.Error('Fatal error, cannot continue'); 427 } 428 429 var cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : ""; 430 var fullmsg = "New state '" + state + "', was '" + oldstate + "'." + cmsg; 431 if (state === 'failed' || state === 'fatal') { 432 Util.Error(cmsg); 433 } else { 434 Util.Warn(cmsg); 435 } 436 437 if (oldstate === 'failed' && state === 'disconnected') { 438 // do disconnect action, but stay in failed state 439 this._rfb_state = 'failed'; 440 } else { 441 this._rfb_state = state; 442 } 443 444 if (this._disconnTimer && this._rfb_state !== 'disconnect') { 445 Util.Debug("Clearing disconnect timer"); 446 clearTimeout(this._disconnTimer); 447 this._disconnTimer = null; 448 } 449 450 switch (state) { 451 case 'normal': 452 if (oldstate === 'disconnected' || oldstate === 'failed') { 453 Util.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'"); 454 } 455 break; 456 457 case 'connect': 458 this._init_vars(); 459 this._connect(); 460 // WebSocket.onopen transitions to 'ProtocolVersion' 461 break; 462 463 case 'disconnect': 464 this._disconnTimer = setTimeout(function () { 465 this._fail("Disconnect timeout"); 466 }.bind(this), this._disconnectTimeout * 1000); 467 468 this._print_stats(); 469 470 // WebSocket.onclose transitions to 'disconnected' 471 break; 472 473 case 'failed': 474 if (oldstate === 'disconnected') { 475 Util.Error("Invalid transition from 'disconnected' to 'failed'"); 476 } else if (oldstate === 'normal') { 477 Util.Error("Error while connected."); 478 } else if (oldstate === 'init') { 479 Util.Error("Error while initializing."); 480 } 481 482 // Make sure we transition to disconnected 483 setTimeout(function () { 484 this._updateState('disconnected'); 485 }.bind(this), 50); 486 487 break; 488 489 default: 490 // No state change action to take 491 } 492 493 if (oldstate === 'failed' && state === 'disconnected') { 494 this._onUpdateState(this, state, oldstate); 495 } else { 496 this._onUpdateState(this, state, oldstate, statusMsg); 497 } 498 }, 499 500 _fail: function (msg) { 501 this._updateState('failed', msg); 502 return false; 503 }, 504 505 _handle_message: function () { 506 if (this._sock.rQlen() === 0) { 507 Util.Warn("handle_message called on an empty receive queue"); 508 return; 509 } 510 511 switch (this._rfb_state) { 512 case 'disconnected': 513 case 'failed': 514 Util.Error("Got data while disconnected"); 515 break; 516 case 'normal': 517 if (this._normal_msg() && this._sock.rQlen() > 0) { 518 // true means we can continue processing 519 // Give other events a chance to run 520 if (this._msgTimer === null) { 521 Util.Debug("More data to process, creating timer"); 522 this._msgTimer = setTimeout(function () { 523 this._msgTimer = null; 524 this._handle_message(); 525 }.bind(this), 10); 526 } else { 527 Util.Debug("More data to process, existing timer"); 528 } 529 } 530 break; 531 default: 532 this._init_msg(); 533 break; 534 } 535 }, 536 537 _checkEvents: function () { 538 if (this._rfb_state === 'normal' && !this._viewportDragging && this._mouse_arr.length > 0) { 539 this._sock.send(this._mouse_arr); 540 this._mouse_arr = []; 541 } 542 }, 543 544 _handleKeyPress: function (keysym, down) { 545 if (this._view_only) { return; } // View only, skip keyboard, events 546 this._sock.send(RFB.messages.keyEvent(keysym, down)); 547 }, 548 549 _handleMouseButton: function (x, y, down, bmask) { 550 if (down) { 551 this._mouse_buttonMask |= bmask; 552 } else { 553 this._mouse_buttonMask ^= bmask; 554 } 555 556 if (this._viewportDrag) { 557 if (down && !this._viewportDragging) { 558 this._viewportDragging = true; 559 this._viewportDragPos = {'x': x, 'y': y}; 560 561 // Skip sending mouse events 562 return; 563 } else { 564 this._viewportDragging = false; 565 } 566 } 567 568 if (this._view_only) { return; } // View only, skip mouse events 569 570 this._mouse_arr = this._mouse_arr.concat( 571 RFB.messages.pointerEvent(this._display.absX(x), this._display.absY(y), this._mouse_buttonMask)); 572 this._sock.send(this._mouse_arr); 573 this._mouse_arr = []; 574 }, 575 576 _handleMouseMove: function (x, y) { 577 if (this._viewportDragging) { 578 var deltaX = this._viewportDragPos.x - x; 579 var deltaY = this._viewportDragPos.y - y; 580 this._viewportDragPos = {'x': x, 'y': y}; 581 582 this._display.viewportChange(deltaX, deltaY); 583 584 // Skip sending mouse events 585 return; 586 } 587 588 if (this._view_only) { return; } // View only, skip mouse events 589 590 this._mouse_arr = this._mouse_arr.concat( 591 RFB.messages.pointerEvent(this._display.absX(x), this._display.absY(y), this._mouse_buttonMask)); 592 593 this._checkEvents(); 594 }, 595 596 // Message Handlers 597 598 _negotiate_protocol_version: function () { 599 if (this._sock.rQlen() < 12) { 600 return this._fail("Incomplete protocol version"); 601 } 602 603 var sversion = this._sock.rQshiftStr(12).substr(4, 7); 604 Util.Info("Server ProtocolVersion: " + sversion); 605 var is_repeater = 0; 606 switch (sversion) { 607 case "000.000": // UltraVNC repeater 608 is_repeater = 1; 609 break; 610 case "003.003": 611 case "003.006": // UltraVNC 612 case "003.889": // Apple Remote Desktop 613 this._rfb_version = 3.3; 614 break; 615 case "003.007": 616 this._rfb_version = 3.7; 617 break; 618 case "003.008": 619 case "004.000": // Intel AMT KVM 620 case "004.001": // RealVNC 4.6 621 this._rfb_version = 3.8; 622 break; 623 default: 624 return this._fail("Invalid server version " + sversion); 625 } 626 627 if (is_repeater) { 628 var repeaterID = this._repeaterID; 629 while (repeaterID.length < 250) { 630 repeaterID += "\0"; 631 } 632 this._sock.send_string(repeaterID); 633 return true; 634 } 635 636 if (this._rfb_version > this._rfb_max_version) { 637 this._rfb_version = this._rfb_max_version; 638 } 639 640 // Send updates either at a rate of 1 update per 50ms, or 641 // whatever slower rate the network can handle 642 this._sendTimer = setInterval(this._sock.flush.bind(this._sock), 50); 643 644 var cversion = "00" + parseInt(this._rfb_version, 10) + 645 ".00" + ((this._rfb_version * 10) % 10); 646 this._sock.send_string("RFB " + cversion + "\n"); 647 this._updateState('Security', 'Sent ProtocolVersion: ' + cversion); 648 }, 649 650 _negotiate_security: function () { 651 if (this._rfb_version >= 3.7) { 652 // Server sends supported list, client decides 653 var num_types = this._sock.rQshift8(); 654 if (this._sock.rQwait("security type", num_types, 1)) { return false; } 655 656 if (num_types === 0) { 657 var strlen = this._sock.rQshift32(); 658 var reason = this._sock.rQshiftStr(strlen); 659 return this._fail("Security failure: " + reason); 660 } 661 662 this._rfb_auth_scheme = 0; 663 var types = this._sock.rQshiftBytes(num_types); 664 Util.Debug("Server security types: " + types); 665 for (var i = 0; i < types.length; i++) { 666 if (types[i] > this._rfb_auth_scheme && (types[i] <= 16 || types[i] == 22)) { 667 this._rfb_auth_scheme = types[i]; 668 } 669 } 670 671 if (this._rfb_auth_scheme === 0) { 672 return this._fail("Unsupported security types: " + types); 673 } 674 675 this._sock.send([this._rfb_auth_scheme]); 676 } else { 677 // Server decides 678 if (this._sock.rQwait("security scheme", 4)) { return false; } 679 this._rfb_auth_scheme = this._sock.rQshift32(); 680 } 681 682 this._updateState('Authentication', 'Authenticating using scheme: ' + this._rfb_auth_scheme); 683 return this._init_msg(); // jump to authentication 684 }, 685 686 // authentication 687 _negotiate_xvp_auth: function () { 688 var xvp_sep = this._xvp_password_sep; 689 var xvp_auth = this._rfb_password.split(xvp_sep); 690 if (xvp_auth.length < 3) { 691 this._updateState('password', 'XVP credentials required (user' + xvp_sep + 692 'target' + xvp_sep + 'password) -- got only ' + this._rfb_password); 693 this._onPasswordRequired(this); 694 return false; 695 } 696 697 var xvp_auth_str = String.fromCharCode(xvp_auth[0].length) + 698 String.fromCharCode(xvp_auth[1].length) + 699 xvp_auth[0] + 700 xvp_auth[1]; 701 this._sock.send_string(xvp_auth_str); 702 this._rfb_password = xvp_auth.slice(2).join(xvp_sep); 703 this._rfb_auth_scheme = 2; 704 return this._negotiate_authentication(); 705 }, 706 707 _negotiate_std_vnc_auth: function () { 708 if (this._rfb_password.length === 0) { 709 // Notify via both callbacks since it's kind of 710 // an RFB state change and a UI interface issue 711 this._updateState('password', "Password Required"); 712 this._onPasswordRequired(this); 713 } 714 715 if (this._sock.rQwait("auth challenge", 16)) { return false; } 716 717 var challenge = this._sock.rQshiftBytes(16); 718 var response = RFB.genDES(this._rfb_password, challenge); 719 this._sock.send(response); 720 this._updateState("SecurityResult"); 721 return true; 722 }, 723 724 _negotiate_tight_tunnels: function (numTunnels) { 725 var clientSupportedTunnelTypes = { 726 0: { vendor: 'TGHT', signature: 'NOTUNNEL' } 727 }; 728 var serverSupportedTunnelTypes = {}; 729 // receive tunnel capabilities 730 for (var i = 0; i < numTunnels; i++) { 731 var cap_code = this._sock.rQshift32(); 732 var cap_vendor = this._sock.rQshiftStr(4); 733 var cap_signature = this._sock.rQshiftStr(8); 734 serverSupportedTunnelTypes[cap_code] = { vendor: cap_vendor, signature: cap_signature }; 735 } 736 737 // choose the notunnel type 738 if (serverSupportedTunnelTypes[0]) { 739 if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor || 740 serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) { 741 return this._fail("Client's tunnel type had the incorrect vendor or signature"); 742 } 743 this._sock.send([0, 0, 0, 0]); // use NOTUNNEL 744 return false; // wait until we receive the sub auth count to continue 745 } else { 746 return this._fail("Server wanted tunnels, but doesn't support the notunnel type"); 747 } 748 }, 749 750 _negotiate_tight_auth: function () { 751 if (!this._rfb_tightvnc) { // first pass, do the tunnel negotiation 752 if (this._sock.rQwait("num tunnels", 4)) { return false; } 753 var numTunnels = this._sock.rQshift32(); 754 if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; } 755 756 this._rfb_tightvnc = true; 757 758 if (numTunnels > 0) { 759 this._negotiate_tight_tunnels(numTunnels); 760 return false; // wait until we receive the sub auth to continue 761 } 762 } 763 764 // second pass, do the sub-auth negotiation 765 if (this._sock.rQwait("sub auth count", 4)) { return false; } 766 var subAuthCount = this._sock.rQshift32(); 767 if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; } 768 769 var clientSupportedTypes = { 770 'STDVNOAUTH__': 1, 771 'STDVVNCAUTH_': 2 772 }; 773 774 var serverSupportedTypes = []; 775 776 for (var i = 0; i < subAuthCount; i++) { 777 var capNum = this._sock.rQshift32(); 778 var capabilities = this._sock.rQshiftStr(12); 779 serverSupportedTypes.push(capabilities); 780 } 781 782 for (var authType in clientSupportedTypes) { 783 if (serverSupportedTypes.indexOf(authType) != -1) { 784 this._sock.send([0, 0, 0, clientSupportedTypes[authType]]); 785 786 switch (authType) { 787 case 'STDVNOAUTH__': // no auth 788 this._updateState('SecurityResult'); 789 return true; 790 case 'STDVVNCAUTH_': // VNC auth 791 this._rfb_auth_scheme = 2; 792 return this._init_msg(); 793 default: 794 return this._fail("Unsupported tiny auth scheme: " + authType); 795 } 796 } 797 } 798 799 this._fail("No supported sub-auth types!"); 800 }, 801 802 _negotiate_authentication: function () { 803 switch (this._rfb_auth_scheme) { 804 case 0: // connection failed 805 if (this._sock.rQwait("auth reason", 4)) { return false; } 806 var strlen = this._sock.rQshift32(); 807 var reason = this._sock.rQshiftStr(strlen); 808 return this._fail("Auth failure: " + reason); 809 810 case 1: // no auth 811 if (this._rfb_version >= 3.8) { 812 this._updateState('SecurityResult'); 813 return true; 814 } 815 this._updateState('ClientInitialisation', "No auth required"); 816 return this._init_msg(); 817 818 case 22: // XVP auth 819 return this._negotiate_xvp_auth(); 820 821 case 2: // VNC authentication 822 return this._negotiate_std_vnc_auth(); 823 824 case 16: // TightVNC Security Type 825 return this._negotiate_tight_auth(); 826 827 default: 828 return this._fail("Unsupported auth scheme: " + this._rfb_auth_scheme); 829 } 830 }, 831 832 _handle_security_result: function () { 833 if (this._sock.rQwait('VNC auth response ', 4)) { return false; } 834 switch (this._sock.rQshift32()) { 835 case 0: // OK 836 this._updateState('ClientInitialisation', 'Authentication OK'); 837 return this._init_msg(); 838 case 1: // failed 839 if (this._rfb_version >= 3.8) { 840 var length = this._sock.rQshift32(); 841 if (this._sock.rQwait("SecurityResult reason", length, 8)) { return false; } 842 var reason = this._sock.rQshiftStr(length); 843 return this._fail(reason); 844 } else { 845 return this._fail("Authentication failure"); 846 } 847 return false; 848 case 2: 849 return this._fail("Too many auth attempts"); 850 } 851 }, 852 853 _negotiate_server_init: function () { 854 if (this._sock.rQwait("server initialization", 24)) { return false; } 855 856 /* Screen size */ 857 this._fb_width = this._sock.rQshift16(); 858 this._fb_height = this._sock.rQshift16(); 859 860 /* PIXEL_FORMAT */ 861 var bpp = this._sock.rQshift8(); 862 var depth = this._sock.rQshift8(); 863 var big_endian = this._sock.rQshift8(); 864 var true_color = this._sock.rQshift8(); 865 866 var red_max = this._sock.rQshift16(); 867 var green_max = this._sock.rQshift16(); 868 var blue_max = this._sock.rQshift16(); 869 var red_shift = this._sock.rQshift8(); 870 var green_shift = this._sock.rQshift8(); 871 var blue_shift = this._sock.rQshift8(); 872 this._sock.rQskipBytes(3); // padding 873 874 // NB(directxman12): we don't want to call any callbacks or print messages until 875 // *after* we're past the point where we could backtrack 876 877 /* Connection name/title */ 878 var name_length = this._sock.rQshift32(); 879 if (this._sock.rQwait('server init name', name_length, 24)) { return false; } 880 this._fb_name = Util.decodeUTF8(this._sock.rQshiftStr(name_length)); 881 882 if (this._rfb_tightvnc) { 883 if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; } 884 // In TightVNC mode, ServerInit message is extended 885 var numServerMessages = this._sock.rQshift16(); 886 var numClientMessages = this._sock.rQshift16(); 887 var numEncodings = this._sock.rQshift16(); 888 this._sock.rQskipBytes(2); // padding 889 890 var totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16; 891 if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + name_length)) { return false; } 892 893 var i; 894 for (i = 0; i < numServerMessages; i++) { 895 var srvMsg = this._sock.rQshiftStr(16); 896 } 897 898 for (i = 0; i < numClientMessages; i++) { 899 var clientMsg = this._sock.rQshiftStr(16); 900 } 901 902 for (i = 0; i < numEncodings; i++) { 903 var encoding = this._sock.rQshiftStr(16); 904 } 905 } 906 907 // NB(directxman12): these are down here so that we don't run them multiple times 908 // if we backtrack 909 Util.Info("Screen: " + this._fb_width + "x" + this._fb_height + 910 ", bpp: " + bpp + ", depth: " + depth + 911 ", big_endian: " + big_endian + 912 ", true_color: " + true_color + 913 ", red_max: " + red_max + 914 ", green_max: " + green_max + 915 ", blue_max: " + blue_max + 916 ", red_shift: " + red_shift + 917 ", green_shift: " + green_shift + 918 ", blue_shift: " + blue_shift); 919 920 if (big_endian !== 0) { 921 Util.Warn("Server native endian is not little endian"); 922 } 923 924 if (red_shift !== 16) { 925 Util.Warn("Server native red-shift is not 16"); 926 } 927 928 if (blue_shift !== 0) { 929 Util.Warn("Server native blue-shift is not 0"); 930 } 931 932 // we're past the point where we could backtrack, so it's safe to call this 933 this._onDesktopName(this, this._fb_name); 934 935 if (this._true_color && this._fb_name === "Intel(r) AMT KVM") { 936 Util.Warn("Intel AMT KVM only supports 8/16 bit depths. Disabling true color"); 937 this._true_color = false; 938 } 939 940 this._display.set_true_color(this._true_color); 941 this._onFBResize(this, this._fb_width, this._fb_height); 942 this._display.resize(this._fb_width, this._fb_height); 943 this._keyboard.grab(); 944 this._mouse.grab(); 945 946 if (this._true_color) { 947 this._fb_Bpp = 4; 948 this._fb_depth = 3; 949 } else { 950 this._fb_Bpp = 1; 951 this._fb_depth = 1; 952 } 953 954 var response = RFB.messages.pixelFormat(this._fb_Bpp, this._fb_depth, this._true_color); 955 response = response.concat( 956 RFB.messages.clientEncodings(this._encodings, this._local_cursor, this._true_color)); 957 response = response.concat( 958 RFB.messages.fbUpdateRequests(this._display.getCleanDirtyReset(), 959 this._fb_width, this._fb_height)); 960 961 this._timing.fbu_rt_start = (new Date()).getTime(); 962 this._timing.pixels = 0; 963 this._sock.send(response); 964 965 this._checkEvents(); 966 967 if (this._encrypt) { 968 this._updateState('normal', 'Connected (encrypted) to: ' + this._fb_name); 969 } else { 970 this._updateState('normal', 'Connected (unencrypted) to: ' + this._fb_name); 971 } 972 }, 973 974 _init_msg: function () { 975 switch (this._rfb_state) { 976 case 'ProtocolVersion': 977 return this._negotiate_protocol_version(); 978 979 case 'Security': 980 return this._negotiate_security(); 981 982 case 'Authentication': 983 return this._negotiate_authentication(); 984 985 case 'SecurityResult': 986 return this._handle_security_result(); 987 988 case 'ClientInitialisation': 989 this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation 990 this._updateState('ServerInitialisation', "Authentication OK"); 991 return true; 992 993 case 'ServerInitialisation': 994 return this._negotiate_server_init(); 995 } 996 }, 997 998 _handle_set_colour_map_msg: function () { 999 Util.Debug("SetColorMapEntries"); 1000 this._sock.rQskip8(); // Padding 1001 1002 var first_colour = this._sock.rQshift16(); 1003 var num_colours = this._sock.rQshift16(); 1004 if (this._sock.rQwait('SetColorMapEntries', num_colours * 6, 6)) { return false; } 1005 1006 for (var c = 0; c < num_colours; c++) { 1007 var red = parseInt(this._sock.rQshift16() / 256, 10); 1008 var green = parseInt(this._sock.rQshift16() / 256, 10); 1009 var blue = parseInt(this._sock.rQshift16() / 256, 10); 1010 this._display.set_colourMap([blue, green, red], first_colour + c); 1011 } 1012 Util.Debug("colourMap: " + this._display.get_colourMap()); 1013 Util.Info("Registered " + num_colours + " colourMap entries"); 1014 1015 return true; 1016 }, 1017 1018 _handle_server_cut_text: function () { 1019 Util.Debug("ServerCutText"); 1020 if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; } 1021 this._sock.rQskipBytes(3); // Padding 1022 var length = this._sock.rQshift32(); 1023 if (this._sock.rQwait("ServerCutText", length, 8)) { return false; } 1024 1025 var text = this._sock.rQshiftStr(length); 1026 this._onClipboard(this, text); 1027 1028 return true; 1029 }, 1030 1031 _handle_xvp_msg: function () { 1032 if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; } 1033 this._sock.rQskip8(); // Padding 1034 var xvp_ver = this._sock.rQshift8(); 1035 var xvp_msg = this._sock.rQshift8(); 1036 1037 switch (xvp_msg) { 1038 case 0: // XVP_FAIL 1039 this._updateState(this._rfb_state, "Operation Failed"); 1040 break; 1041 case 1: // XVP_INIT 1042 this._rfb_xvp_ver = xvp_ver; 1043 Util.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")"); 1044 this._onXvpInit(this._rfb_xvp_ver); 1045 break; 1046 default: 1047 this._fail("Disconnected: illegal server XVP message " + xvp_msg); 1048 break; 1049 } 1050 1051 return true; 1052 }, 1053 1054 _normal_msg: function () { 1055 var msg_type; 1056 1057 if (this._FBU.rects > 0) { 1058 msg_type = 0; 1059 } else { 1060 msg_type = this._sock.rQshift8(); 1061 } 1062 1063 switch (msg_type) { 1064 case 0: // FramebufferUpdate 1065 var ret = this._framebufferUpdate(); 1066 if (ret) { 1067 this._sock.send(RFB.messages.fbUpdateRequests(this._display.getCleanDirtyReset(), 1068 this._fb_width, this._fb_height)); 1069 } 1070 return ret; 1071 1072 case 1: // SetColorMapEntries 1073 return this._handle_set_colour_map_msg(); 1074 1075 case 2: // Bell 1076 Util.Debug("Bell"); 1077 this._onBell(this); 1078 return true; 1079 1080 case 3: // ServerCutText 1081 return this._handle_server_cut_text(); 1082 1083 case 250: // XVP 1084 return this._handle_xvp_msg(); 1085 1086 default: 1087 this._fail("Disconnected: illegal server message type " + msg_type); 1088 Util.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30)); 1089 return true; 1090 } 1091 }, 1092 1093 _framebufferUpdate: function () { 1094 var ret = true; 1095 var now; 1096 1097 if (this._FBU.rects === 0) { 1098 if (this._sock.rQwait("FBU header", 3, 1)) { return false; } 1099 this._sock.rQskip8(); // Padding 1100 this._FBU.rects = this._sock.rQshift16(); 1101 this._FBU.bytes = 0; 1102 this._timing.cur_fbu = 0; 1103 if (this._timing.fbu_rt_start > 0) { 1104 now = (new Date()).getTime(); 1105 Util.Info("First FBU latency: " + (now - this._timing.fbu_rt_start)); 1106 } 1107 } 1108 1109 while (this._FBU.rects > 0) { 1110 if (this._rfb_state !== "normal") { return false; } 1111 1112 if (this._sock.rQwait("FBU", this._FBU.bytes)) { return false; } 1113 if (this._FBU.bytes === 0) { 1114 if (this._sock.rQwait("rect header", 12)) { return false; } 1115 /* New FramebufferUpdate */ 1116 1117 var hdr = this._sock.rQshiftBytes(12); 1118 this._FBU.x = (hdr[0] << 8) + hdr[1]; 1119 this._FBU.y = (hdr[2] << 8) + hdr[3]; 1120 this._FBU.width = (hdr[4] << 8) + hdr[5]; 1121 this._FBU.height = (hdr[6] << 8) + hdr[7]; 1122 this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) + 1123 (hdr[10] << 8) + hdr[11], 10); 1124 1125 this._onFBUReceive(this, 1126 {'x': this._FBU.x, 'y': this._FBU.y, 1127 'width': this._FBU.width, 'height': this._FBU.height, 1128 'encoding': this._FBU.encoding, 1129 'encodingName': this._encNames[this._FBU.encoding]}); 1130 1131 if (!this._encNames[this._FBU.encoding]) { 1132 this._fail("Disconnected: unsupported encoding " + 1133 this._FBU.encoding); 1134 return false; 1135 } 1136 } 1137 1138 this._timing.last_fbu = (new Date()).getTime(); 1139 1140 ret = this._encHandlers[this._FBU.encoding](); 1141 1142 now = (new Date()).getTime(); 1143 this._timing.cur_fbu += (now - this._timing.last_fbu); 1144 1145 if (ret) { 1146 this._encStats[this._FBU.encoding][0]++; 1147 this._encStats[this._FBU.encoding][1]++; 1148 this._timing.pixels += this._FBU.width * this._FBU.height; 1149 } 1150 1151 if (this._timing.pixels >= (this._fb_width * this._fb_height)) { 1152 if ((this._FBU.width === this._fb_width && this._FBU.height === this._fb_height) || 1153 this._timing.fbu_rt_start > 0) { 1154 this._timing.full_fbu_total += this._timing.cur_fbu; 1155 this._timing.full_fbu_cnt++; 1156 Util.Info("Timing of full FBU, curr: " + 1157 this._timing.cur_fbu + ", total: " + 1158 this._timing.full_fbu_total + ", cnt: " + 1159 this._timing.full_fbu_cnt + ", avg: " + 1160 (this._timing.full_fbu_total / this._timing.full_fbu_cnt)); 1161 } 1162 1163 if (this._timing.fbu_rt_start > 0) { 1164 var fbu_rt_diff = now - this._timing.fbu_rt_start; 1165 this._timing.fbu_rt_total += fbu_rt_diff; 1166 this._timing.fbu_rt_cnt++; 1167 Util.Info("full FBU round-trip, cur: " + 1168 fbu_rt_diff + ", total: " + 1169 this._timing.fbu_rt_total + ", cnt: " + 1170 this._timing.fbu_rt_cnt + ", avg: " + 1171 (this._timing.fbu_rt_total / this._timing.fbu_rt_cnt)); 1172 this._timing.fbu_rt_start = 0; 1173 } 1174 } 1175 1176 if (!ret) { return ret; } // need more data 1177 } 1178 1179 this._onFBUComplete(this, 1180 {'x': this._FBU.x, 'y': this._FBU.y, 1181 'width': this._FBU.width, 'height': this._FBU.height, 1182 'encoding': this._FBU.encoding, 1183 'encodingName': this._encNames[this._FBU.encoding]}); 1184 1185 return true; // We finished this FBU 1186 }, 1187 }; 1188 1189 Util.make_properties(RFB, [ 1190 ['target', 'wo', 'dom'], // VNC display rendering Canvas object 1191 ['focusContainer', 'wo', 'dom'], // DOM element that captures keyboard input 1192 ['encrypt', 'rw', 'bool'], // Use TLS/SSL/wss encryption 1193 ['true_color', 'rw', 'bool'], // Request true color pixel data 1194 ['local_cursor', 'rw', 'bool'], // Request locally rendered cursor 1195 ['shared', 'rw', 'bool'], // Request shared mode 1196 ['view_only', 'rw', 'bool'], // Disable client mouse/keyboard 1197 ['xvp_password_sep', 'rw', 'str'], // Separator for XVP password fields 1198 ['disconnectTimeout', 'rw', 'int'], // Time (s) to wait for disconnection 1199 ['wsProtocols', 'rw', 'arr'], // Protocols to use in the WebSocket connection 1200 ['repeaterID', 'rw', 'str'], // [UltraVNC] RepeaterID to connect to 1201 ['viewportDrag', 'rw', 'bool'], // Move the viewport on mouse drags 1202 1203 // Callback functions 1204 ['onUpdateState', 'rw', 'func'], // onUpdateState(rfb, state, oldstate, statusMsg): RFB state update/change 1205 ['onPasswordRequired', 'rw', 'func'], // onPasswordRequired(rfb): VNC password is required 1206 ['onClipboard', 'rw', 'func'], // onClipboard(rfb, text): RFB clipboard contents received 1207 ['onBell', 'rw', 'func'], // onBell(rfb): RFB Bell message received 1208 ['onFBUReceive', 'rw', 'func'], // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed 1209 ['onFBUComplete', 'rw', 'func'], // onFBUComplete(rfb, fbu): RFB FBU received and processed 1210 ['onFBResize', 'rw', 'func'], // onFBResize(rfb, width, height): frame buffer resized 1211 ['onDesktopName', 'rw', 'func'], // onDesktopName(rfb, name): desktop name received 1212 ['onXvpInit', 'rw', 'func'], // onXvpInit(version): XVP extensions active for this connection 1213 ]); 1214 1215 RFB.prototype.set_local_cursor = function (cursor) { 1216 if (!cursor || (cursor in {'0': 1, 'no': 1, 'false': 1})) { 1217 this._local_cursor = false; 1218 } else { 1219 if (this._display.get_cursor_uri()) { 1220 this._local_cursor = true; 1221 } else { 1222 Util.Warn("Browser does not support local cursor"); 1223 } 1224 } 1225 }; 1226 1227 RFB.prototype.get_display = function () { return this._display; }; 1228 RFB.prototype.get_keyboard = function () { return this._keyboard; }; 1229 RFB.prototype.get_mouse = function () { return this._mouse; }; 1230 1231 // Class Methods 1232 RFB.messages = { 1233 keyEvent: function (keysym, down) { 1234 var arr = [4]; 1235 arr.push8(down); 1236 arr.push16(0); 1237 arr.push32(keysym); 1238 return arr; 1239 }, 1240 1241 pointerEvent: function (x, y, mask) { 1242 var arr = [5]; // msg-type 1243 arr.push8(mask); 1244 arr.push16(x); 1245 arr.push16(y); 1246 return arr; 1247 }, 1248 1249 // TODO(directxman12): make this unicode compatible? 1250 clientCutText: function (text) { 1251 var arr = [6]; // msg-type 1252 arr.push8(0); // padding 1253 arr.push8(0); // padding 1254 arr.push8(0); // padding 1255 arr.push32(text.length); 1256 var n = text.length; 1257 for (var i = 0; i < n; i++) { 1258 arr.push(text.charCodeAt(i)); 1259 } 1260 1261 return arr; 1262 }, 1263 1264 pixelFormat: function (bpp, depth, true_color) { 1265 var arr = [0]; // msg-type 1266 arr.push8(0); // padding 1267 arr.push8(0); // padding 1268 arr.push8(0); // padding 1269 1270 arr.push8(bpp * 8); // bits-per-pixel 1271 arr.push8(depth * 8); // depth 1272 arr.push8(0); // little-endian 1273 arr.push8(true_color ? 1 : 0); // true-color 1274 1275 arr.push16(255); // red-max 1276 arr.push16(255); // green-max 1277 arr.push16(255); // blue-max 1278 arr.push8(16); // red-shift 1279 arr.push8(8); // green-shift 1280 arr.push8(0); // blue-shift 1281 1282 arr.push8(0); // padding 1283 arr.push8(0); // padding 1284 arr.push8(0); // padding 1285 return arr; 1286 }, 1287 1288 clientEncodings: function (encodings, local_cursor, true_color) { 1289 var i, encList = []; 1290 1291 for (i = 0; i < encodings.length; i++) { 1292 if (encodings[i][0] === "Cursor" && !local_cursor) { 1293 Util.Debug("Skipping Cursor pseudo-encoding"); 1294 } else if (encodings[i][0] === "TIGHT" && !true_color) { 1295 // TODO: remove this when we have tight+non-true-color 1296 Util.Warn("Skipping tight as it is only supported with true color"); 1297 } else { 1298 encList.push(encodings[i][1]); 1299 } 1300 } 1301 1302 var arr = [2]; // msg-type 1303 arr.push8(0); // padding 1304 1305 arr.push16(encList.length); // encoding count 1306 for (i = 0; i < encList.length; i++) { 1307 arr.push32(encList[i]); 1308 } 1309 1310 return arr; 1311 }, 1312 1313 fbUpdateRequests: function (cleanDirty, fb_width, fb_height) { 1314 var arr = []; 1315 1316 var cb = cleanDirty.cleanBox; 1317 var w, h; 1318 if (cb.w > 0 && cb.h > 0) { 1319 w = typeof cb.w === "undefined" ? fb_width : cb.w; 1320 h = typeof cb.h === "undefined" ? fb_height : cb.h; 1321 // Request incremental for clean box 1322 arr = arr.concat(RFB.messages.fbUpdateRequest(1, cb.x, cb.y, w, h)); 1323 } 1324 1325 for (var i = 0; i < cleanDirty.dirtyBoxes.length; i++) { 1326 var db = cleanDirty.dirtyBoxes[i]; 1327 // Force all (non-incremental) for dirty box 1328 w = typeof db.w === "undefined" ? fb_width : db.w; 1329 h = typeof db.h === "undefined" ? fb_height : db.h; 1330 arr = arr.concat(RFB.messages.fbUpdateRequest(0, db.x, db.y, w, h)); 1331 } 1332 1333 return arr; 1334 }, 1335 1336 fbUpdateRequest: function (incremental, x, y, w, h) { 1337 if (typeof(x) === "undefined") { x = 0; } 1338 if (typeof(y) === "undefined") { y = 0; } 1339 1340 var arr = [3]; // msg-type 1341 arr.push8(incremental); 1342 arr.push16(x); 1343 arr.push16(y); 1344 arr.push16(w); 1345 arr.push16(h); 1346 1347 return arr; 1348 } 1349 }; 1350 1351 RFB.genDES = function (password, challenge) { 1352 var passwd = []; 1353 for (var i = 0; i < password.length; i++) { 1354 passwd.push(password.charCodeAt(i)); 1355 } 1356 return (new DES(passwd)).encrypt(challenge); 1357 }; 1358 1359 RFB.extract_data_uri = function (arr) { 1360 return ";base64," + Base64.encode(arr); 1361 }; 1362 1363 RFB.encodingHandlers = { 1364 RAW: function () { 1365 if (this._FBU.lines === 0) { 1366 this._FBU.lines = this._FBU.height; 1367 } 1368 1369 this._FBU.bytes = this._FBU.width * this._fb_Bpp; // at least a line 1370 if (this._sock.rQwait("RAW", this._FBU.bytes)) { return false; } 1371 var cur_y = this._FBU.y + (this._FBU.height - this._FBU.lines); 1372 var curr_height = Math.min(this._FBU.lines, 1373 Math.floor(this._sock.rQlen() / (this._FBU.width * this._fb_Bpp))); 1374 this._display.blitImage(this._FBU.x, cur_y, this._FBU.width, 1375 curr_height, this._sock.get_rQ(), 1376 this._sock.get_rQi()); 1377 this._sock.rQskipBytes(this._FBU.width * curr_height * this._fb_Bpp); 1378 this._FBU.lines -= curr_height; 1379 1380 if (this._FBU.lines > 0) { 1381 this._FBU.bytes = this._FBU.width * this._fb_Bpp; // At least another line 1382 } else { 1383 this._FBU.rects--; 1384 this._FBU.bytes = 0; 1385 } 1386 1387 return true; 1388 }, 1389 1390 COPYRECT: function () { 1391 this._FBU.bytes = 4; 1392 if (this._sock.rQwait("COPYRECT", 4)) { return false; } 1393 this._display.renderQ_push({ 1394 'type': 'copy', 1395 'old_x': this._sock.rQshift16(), 1396 'old_y': this._sock.rQshift16(), 1397 'x': this._FBU.x, 1398 'y': this._FBU.y, 1399 'width': this._FBU.width, 1400 'height': this._FBU.height 1401 }); 1402 this._FBU.rects--; 1403 this._FBU.bytes = 0; 1404 return true; 1405 }, 1406 1407 RRE: function () { 1408 var color; 1409 if (this._FBU.subrects === 0) { 1410 this._FBU.bytes = 4 + this._fb_Bpp; 1411 if (this._sock.rQwait("RRE", 4 + this._fb_Bpp)) { return false; } 1412 this._FBU.subrects = this._sock.rQshift32(); 1413 color = this._sock.rQshiftBytes(this._fb_Bpp); // Background 1414 this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, color); 1415 } 1416 1417 while (this._FBU.subrects > 0 && this._sock.rQlen() >= (this._fb_Bpp + 8)) { 1418 color = this._sock.rQshiftBytes(this._fb_Bpp); 1419 var x = this._sock.rQshift16(); 1420 var y = this._sock.rQshift16(); 1421 var width = this._sock.rQshift16(); 1422 var height = this._sock.rQshift16(); 1423 this._display.fillRect(this._FBU.x + x, this._FBU.y + y, width, height, color); 1424 this._FBU.subrects--; 1425 } 1426 1427 if (this._FBU.subrects > 0) { 1428 var chunk = Math.min(this._rre_chunk_sz, this._FBU.subrects); 1429 this._FBU.bytes = (this._fb_Bpp + 8) * chunk; 1430 } else { 1431 this._FBU.rects--; 1432 this._FBU.bytes = 0; 1433 } 1434 1435 return true; 1436 }, 1437 1438 HEXTILE: function () { 1439 var rQ = this._sock.get_rQ(); 1440 var rQi = this._sock.get_rQi(); 1441 1442 if (this._FBU.tiles === 0) { 1443 this._FBU.tiles_x = Math.ceil(this._FBU.width / 16); 1444 this._FBU.tiles_y = Math.ceil(this._FBU.height / 16); 1445 this._FBU.total_tiles = this._FBU.tiles_x * this._FBU.tiles_y; 1446 this._FBU.tiles = this._FBU.total_tiles; 1447 } 1448 1449 while (this._FBU.tiles > 0) { 1450 this._FBU.bytes = 1; 1451 if (this._sock.rQwait("HEXTILE subencoding", this._FBU.bytes)) { return false; } 1452 var subencoding = rQ[rQi]; // Peek 1453 if (subencoding > 30) { // Raw 1454 this._fail("Disconnected: illegal hextile subencoding " + subencoding); 1455 return false; 1456 } 1457 1458 var subrects = 0; 1459 var curr_tile = this._FBU.total_tiles - this._FBU.tiles; 1460 var tile_x = curr_tile % this._FBU.tiles_x; 1461 var tile_y = Math.floor(curr_tile / this._FBU.tiles_x); 1462 var x = this._FBU.x + tile_x * 16; 1463 var y = this._FBU.y + tile_y * 16; 1464 var w = Math.min(16, (this._FBU.x + this._FBU.width) - x); 1465 var h = Math.min(16, (this._FBU.y + this._FBU.height) - y); 1466 1467 // Figure out how much we are expecting 1468 if (subencoding & 0x01) { // Raw 1469 this._FBU.bytes += w * h * this._fb_Bpp; 1470 } else { 1471 if (subencoding & 0x02) { // Background 1472 this._FBU.bytes += this._fb_Bpp; 1473 } 1474 if (subencoding & 0x04) { // Foreground 1475 this._FBU.bytes += this._fb_Bpp; 1476 } 1477 if (subencoding & 0x08) { // AnySubrects 1478 this._FBU.bytes++; // Since we aren't shifting it off 1479 if (this._sock.rQwait("hextile subrects header", this._FBU.bytes)) { return false; } 1480 subrects = rQ[rQi + this._FBU.bytes - 1]; // Peek 1481 if (subencoding & 0x10) { // SubrectsColoured 1482 this._FBU.bytes += subrects * (this._fb_Bpp + 2); 1483 } else { 1484 this._FBU.bytes += subrects * 2; 1485 } 1486 } 1487 } 1488 1489 if (this._sock.rQwait("hextile", this._FBU.bytes)) { return false; } 1490 1491 // We know the encoding and have a whole tile 1492 this._FBU.subencoding = rQ[rQi]; 1493 rQi++; 1494 if (this._FBU.subencoding === 0) { 1495 if (this._FBU.lastsubencoding & 0x01) { 1496 // Weird: ignore blanks are RAW 1497 Util.Debug(" Ignoring blank after RAW"); 1498 } else { 1499 this._display.fillRect(x, y, w, h, rQ, rQi); 1500 rQi += this._FBU.bytes - 1; 1501 } 1502 } else if (this._FBU.subencoding & 0x01) { // Raw 1503 this._display.blitImage(x, y, w, h, rQ, rQi); 1504 rQi += this._FBU.bytes - 1; 1505 } else { 1506 if (this._FBU.subencoding & 0x02) { // Background 1507 this._FBU.background = rQ.slice(rQi, rQi + this._fb_Bpp); 1508 rQi += this._fb_Bpp; 1509 } 1510 if (this._FBU.subencoding & 0x04) { // Foreground 1511 this._FBU.foreground = rQ.slice(rQi, rQi + this._fb_Bpp); 1512 rQi += this._fb_Bpp; 1513 } 1514 1515 this._display.startTile(x, y, w, h, this._FBU.background); 1516 if (this._FBU.subencoding & 0x08) { // AnySubrects 1517 subrects = rQ[rQi]; 1518 rQi++; 1519 1520 for (var s = 0; s < subrects; s++) { 1521 var color; 1522 if (this._FBU.subencoding & 0x10) { // SubrectsColoured 1523 color = rQ.slice(rQi, rQi + this._fb_Bpp); 1524 rQi += this._fb_Bpp; 1525 } else { 1526 color = this._FBU.foreground; 1527 } 1528 var xy = rQ[rQi]; 1529 rQi++; 1530 var sx = (xy >> 4); 1531 var sy = (xy & 0x0f); 1532 1533 var wh = rQ[rQi]; 1534 rQi++; 1535 var sw = (wh >> 4) + 1; 1536 var sh = (wh & 0x0f) + 1; 1537 1538 this._display.subTile(sx, sy, sw, sh, color); 1539 } 1540 } 1541 this._display.finishTile(); 1542 } 1543 this._sock.set_rQi(rQi); 1544 this._FBU.lastsubencoding = this._FBU.subencoding; 1545 this._FBU.bytes = 0; 1546 this._FBU.tiles--; 1547 } 1548 1549 if (this._FBU.tiles === 0) { 1550 this._FBU.rects--; 1551 } 1552 1553 return true; 1554 }, 1555 1556 getTightCLength: function (arr) { 1557 var header = 1, data = 0; 1558 data += arr[0] & 0x7f; 1559 if (arr[0] & 0x80) { 1560 header++; 1561 data += (arr[1] & 0x7f) << 7; 1562 if (arr[1] & 0x80) { 1563 header++; 1564 data += arr[2] << 14; 1565 } 1566 } 1567 return [header, data]; 1568 }, 1569 1570 display_tight: function (isTightPNG) { 1571 if (this._fb_depth === 1) { 1572 this._fail("Tight protocol handler only implements true color mode"); 1573 } 1574 1575 this._FBU.bytes = 1; // compression-control byte 1576 if (this._sock.rQwait("TIGHT compression-control", this._FBU.bytes)) { return false; } 1577 1578 var checksum = function (data) { 1579 var sum = 0; 1580 for (var i = 0; i < data.length; i++) { 1581 sum += data[i]; 1582 if (sum > 65536) sum -= 65536; 1583 } 1584 return sum; 1585 }; 1586 1587 var resetStreams = 0; 1588 var streamId = -1; 1589 var decompress = function (data) { 1590 for (var i = 0; i < 4; i++) { 1591 if ((resetStreams >> i) & 1) { 1592 this._FBU.zlibs[i].reset(); 1593 Util.Info("Reset zlib stream " + i); 1594 } 1595 } 1596 1597 var uncompressed = this._FBU.zlibs[streamId].uncompress(data, 0); 1598 if (uncompressed.status !== 0) { 1599 Util.Error("Invalid data in zlib stream"); 1600 } 1601 1602 return uncompressed.data; 1603 }.bind(this); 1604 1605 var indexedToRGB = function (data, numColors, palette, width, height) { 1606 // Convert indexed (palette based) image data to RGB 1607 // TODO: reduce number of calculations inside loop 1608 var dest = []; 1609 var x, y, dp, sp; 1610 if (numColors === 2) { 1611 var w = Math.floor((width + 7) / 8); 1612 var w1 = Math.floor(width / 8); 1613 1614 for (y = 0; y < height; y++) { 1615 var b; 1616 for (x = 0; x < w1; x++) { 1617 for (b = 7; b >= 0; b--) { 1618 dp = (y * width + x * 8 + 7 - b) * 3; 1619 sp = (data[y * w + x] >> b & 1) * 3; 1620 dest[dp] = palette[sp]; 1621 dest[dp + 1] = palette[sp + 1]; 1622 dest[dp + 2] = palette[sp + 2]; 1623 } 1624 } 1625 1626 for (b = 7; b >= 8 - width % 8; b--) { 1627 dp = (y * width + x * 8 + 7 - b) * 3; 1628 sp = (data[y * w + x] >> b & 1) * 3; 1629 dest[dp] = palette[sp]; 1630 dest[dp + 1] = palette[sp + 1]; 1631 dest[dp + 2] = palette[sp + 2]; 1632 } 1633 } 1634 } else { 1635 for (y = 0; y < height; y++) { 1636 for (x = 0; x < width; x++) { 1637 dp = (y * width + x) * 3; 1638 sp = data[y * width + x] * 3; 1639 dest[dp] = palette[sp]; 1640 dest[dp + 1] = palette[sp + 1]; 1641 dest[dp + 2] = palette[sp + 2]; 1642 } 1643 } 1644 } 1645 1646 return dest; 1647 }.bind(this); 1648 1649 var rQ = this._sock.get_rQ(); 1650 var rQi = this._sock.get_rQi(); 1651 var cmode, clength, data; 1652 1653 var handlePalette = function () { 1654 var numColors = rQ[rQi + 2] + 1; 1655 var paletteSize = numColors * this._fb_depth; 1656 this._FBU.bytes += paletteSize; 1657 if (this._sock.rQwait("TIGHT palette " + cmode, this._FBU.bytes)) { return false; } 1658 1659 var bpp = (numColors <= 2) ? 1 : 8; 1660 var rowSize = Math.floor((this._FBU.width * bpp + 7) / 8); 1661 var raw = false; 1662 if (rowSize * this._FBU.height < 12) { 1663 raw = true; 1664 clength = [0, rowSize * this._FBU.height]; 1665 } else { 1666 clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(3 + paletteSize, 1667 3 + paletteSize + 3)); 1668 } 1669 1670 this._FBU.bytes += clength[0] + clength[1]; 1671 if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; } 1672 1673 // Shift ctl, filter id, num colors, palette entries, and clength off 1674 this._sock.rQskipBytes(3); 1675 var palette = this._sock.rQshiftBytes(paletteSize); 1676 this._sock.rQskipBytes(clength[0]); 1677 1678 if (raw) { 1679 data = this._sock.rQshiftBytes(clength[1]); 1680 } else { 1681 data = decompress(this._sock.rQshiftBytes(clength[1])); 1682 } 1683 1684 // Convert indexed (palette based) image data to RGB 1685 var rgb = indexedToRGB(data, numColors, palette, this._FBU.width, this._FBU.height); 1686 1687 this._display.renderQ_push({ 1688 'type': 'blitRgb', 1689 'data': rgb, 1690 'x': this._FBU.x, 1691 'y': this._FBU.y, 1692 'width': this._FBU.width, 1693 'height': this._FBU.height 1694 }); 1695 1696 return true; 1697 }.bind(this); 1698 1699 var handleCopy = function () { 1700 var raw = false; 1701 var uncompressedSize = this._FBU.width * this._FBU.height * this._fb_depth; 1702 if (uncompressedSize < 12) { 1703 raw = true; 1704 clength = [0, uncompressedSize]; 1705 } else { 1706 clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(1, 4)); 1707 } 1708 this._FBU.bytes = 1 + clength[0] + clength[1]; 1709 if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; } 1710 1711 // Shift ctl, clength off 1712 this._sock.rQshiftBytes(1 + clength[0]); 1713 1714 if (raw) { 1715 data = this._sock.rQshiftBytes(clength[1]); 1716 } else { 1717 data = decompress(this._sock.rQshiftBytes(clength[1])); 1718 } 1719 1720 this._display.renderQ_push({ 1721 'type': 'blitRgb', 1722 'data': data, 1723 'x': this._FBU.x, 1724 'y': this._FBU.y, 1725 'width': this._FBU.width, 1726 'height': this._FBU.height 1727 }); 1728 1729 return true; 1730 }.bind(this); 1731 1732 var ctl = this._sock.rQpeek8(); 1733 1734 // Keep tight reset bits 1735 resetStreams = ctl & 0xF; 1736 1737 // Figure out filter 1738 ctl = ctl >> 4; 1739 streamId = ctl & 0x3; 1740 1741 if (ctl === 0x08) cmode = "fill"; 1742 else if (ctl === 0x09) cmode = "jpeg"; 1743 else if (ctl === 0x0A) cmode = "png"; 1744 else if (ctl & 0x04) cmode = "filter"; 1745 else if (ctl < 0x04) cmode = "copy"; 1746 else return this._fail("Illegal tight compression received, ctl: " + ctl); 1747 1748 if (isTightPNG && (cmode === "filter" || cmode === "copy")) { 1749 return this._fail("filter/copy received in tightPNG mode"); 1750 } 1751 1752 switch (cmode) { 1753 // fill use fb_depth because TPIXELs drop the padding byte 1754 case "fill": // TPIXEL 1755 this._FBU.bytes += this._fb_depth; 1756 break; 1757 case "jpeg": // max clength 1758 this._FBU.bytes += 3; 1759 break; 1760 case "png": // max clength 1761 this._FBU.bytes += 3; 1762 break; 1763 case "filter": // filter id + num colors if palette 1764 this._FBU.bytes += 2; 1765 break; 1766 case "copy": 1767 break; 1768 } 1769 1770 if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; } 1771 1772 // Determine FBU.bytes 1773 switch (cmode) { 1774 case "fill": 1775 this._sock.rQskip8(); // shift off ctl 1776 var color = this._sock.rQshiftBytes(this._fb_depth); 1777 this._display.renderQ_push({ 1778 'type': 'fill', 1779 'x': this._FBU.x, 1780 'y': this._FBU.y, 1781 'width': this._FBU.width, 1782 'height': this._FBU.height, 1783 'color': [color[2], color[1], color[0]] 1784 }); 1785 break; 1786 case "png": 1787 case "jpeg": 1788 clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(1, 4)); 1789 this._FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data 1790 if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; } 1791 1792 // We have everything, render it 1793 this._sock.rQskipBytes(1 + clength[0]); // shift off clt + compact length 1794 var img = new Image(); 1795 img.src = "data: image/" + cmode + 1796 RFB.extract_data_uri(this._sock.rQshiftBytes(clength[1])); 1797 this._display.renderQ_push({ 1798 'type': 'img', 1799 'img': img, 1800 'x': this._FBU.x, 1801 'y': this._FBU.y 1802 }); 1803 img = null; 1804 break; 1805 case "filter": 1806 var filterId = rQ[rQi + 1]; 1807 if (filterId === 1) { 1808 if (!handlePalette()) { return false; } 1809 } else { 1810 // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter 1811 // Filter 2, Gradient is valid but not use if jpeg is enabled 1812 // TODO(directxman12): why aren't we just calling '_fail' here 1813 throw new Error("Unsupported tight subencoding received, filter: " + filterId); 1814 } 1815 break; 1816 case "copy": 1817 if (!handleCopy()) { return false; } 1818 break; 1819 } 1820 1821 1822 this._FBU.bytes = 0; 1823 this._FBU.rects--; 1824 1825 return true; 1826 }, 1827 1828 TIGHT: function () { return this._encHandlers.display_tight(false); }, 1829 TIGHT_PNG: function () { return this._encHandlers.display_tight(true); }, 1830 1831 last_rect: function () { 1832 this._FBU.rects = 0; 1833 return true; 1834 }, 1835 1836 DesktopSize: function () { 1837 Util.Debug(">> set_desktopsize"); 1838 this._fb_width = this._FBU.width; 1839 this._fb_height = this._FBU.height; 1840 this._onFBResize(this, this._fb_width, this._fb_height); 1841 this._display.resize(this._fb_width, this._fb_height); 1842 this._timing.fbu_rt_start = (new Date()).getTime(); 1843 1844 this._FBU.bytes = 0; 1845 this._FBU.rects--; 1846 1847 Util.Debug("<< set_desktopsize"); 1848 return true; 1849 }, 1850 1851 Cursor: function () { 1852 Util.Debug(">> set_cursor"); 1853 var x = this._FBU.x; // hotspot-x 1854 var y = this._FBU.y; // hotspot-y 1855 var w = this._FBU.width; 1856 var h = this._FBU.height; 1857 1858 var pixelslength = w * h * this._fb_Bpp; 1859 var masklength = Math.floor((w + 7) / 8) * h; 1860 1861 this._FBU.bytes = pixelslength + masklength; 1862 if (this._sock.rQwait("cursor encoding", this._FBU.bytes)) { return false; } 1863 1864 this._display.changeCursor(this._sock.rQshiftBytes(pixelslength), 1865 this._sock.rQshiftBytes(masklength), 1866 x, y, w, h); 1867 1868 this._FBU.bytes = 0; 1869 this._FBU.rects--; 1870 1871 Util.Debug("<< set_cursor"); 1872 return true; 1873 }, 1874 1875 JPEG_quality_lo: function () { 1876 Util.Error("Server sent jpeg_quality pseudo-encoding"); 1877 }, 1878 1879 compress_lo: function () { 1880 Util.Error("Server sent compress level pseudo-encoding"); 1881 } 1882 }; 1883})(); 1884