]> git.proxmox.com Git - mirror_novnc.git/blame - include/rfb.js
Force Travis to use Node 0.11.13
[mirror_novnc.git] / include / rfb.js
CommitLineData
a7a89626
JM
1/*
2 * noVNC: HTML5 VNC client
d58f8b51 3 * Copyright (C) 2012 Joel Martin
406a8b4e 4 * Copyright (C) 2013 Samuel Mannehed for Cendio AB
1d728ace 5 * Licensed under MPL 2.0 (see LICENSE.txt)
a7a89626
JM
6 *
7 * See README.md for usage and integration instructions.
d38db74a
MT
8 *
9 * TIGHT decoder portion:
10 * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
a7a89626
JM
11 */
12
b1dee947 13/*jslint white: false, browser: true */
0911173a 14/*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES */
a7a89626 15
b1dee947 16var RFB;
a7a89626 17
b1dee947
SR
18(function () {
19 "use strict";
20 RFB = function (defaults) {
21 if (!defaults) {
22 defaults = {};
23 }
5210330a 24
b1dee947
SR
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 };
5210330a 87
b1dee947
SR
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];
b688a909 155 }
b1dee947
SR
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");
d890e864 162 }
b1dee947
SR
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);
0d1e1b72 211 } else {
b1dee947
SR
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 }
a679a97d 220 }
8db09746 221
b1dee947
SR
222 Util.Debug("<< RFB.constructor");
223 };
a7a89626 224
b1dee947
SR
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 : "";
a7a89626 232
b1dee947
SR
233 if (!this._rfb_host || !this._rfb_port) {
234 return this._fail("Must set host and port");
235 }
e3efeb32 236
b1dee947
SR
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 },
e3efeb32 303
b1dee947
SR
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';
159ad55d 314 }
e3efeb32 315
b1dee947
SR
316 uri += '://' + this._rfb_host + ':' + this._rfb_port + '/' + this._rfb_path;
317 Util.Info("connecting to " + uri);
a7a89626 318
b1dee947 319 this._sock.open(uri, this._sockProtocols);
a7a89626 320
b1dee947
SR
321 Util.Debug("<< RFB.connect");
322 },
a7a89626 323
b1dee947
SR
324 _init_vars: function () {
325 // reset state
326 this._sock.init();
a7a89626 327
b1dee947
SR
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;
8db09746 336
b1dee947
SR
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 }
a7a89626 342
b1dee947
SR
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 }
a7a89626 358
b1dee947
SR
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 },
a7a89626 365
a7a89626 366
b1dee947
SR
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 }
a679a97d 393
b1dee947
SR
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}) {
a7a89626 400
b1dee947
SR
401 if (this._sendTimer) {
402 clearInterval(this._sendTimer);
403 this._sendTimer = null;
404 }
a7a89626 405
b1dee947
SR
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 }
a7a89626 424
b1dee947
SR
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);
4ed717ad 433 } else {
b1dee947 434 Util.Warn(cmsg);
4ed717ad 435 }
a5df24b4 436
b1dee947
SR
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 }
06a9ef0c 449
b1dee947
SR
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 }
8db09746 481
b1dee947
SR
482 // Make sure we transition to disconnected
483 setTimeout(function () {
484 this._updateState('disconnected');
485 }.bind(this), 50);
a5df24b4 486
b1dee947 487 break;
a5df24b4 488
b1dee947
SR
489 default:
490 // No state change action to take
491 }
a5df24b4 492
b1dee947
SR
493 if (oldstate === 'failed' && state === 'disconnected') {
494 this._onUpdateState(this, state, oldstate);
495 } else {
496 this._onUpdateState(this, state, oldstate, statusMsg);
497 }
498 },
a5df24b4 499
b1dee947
SR
500 _fail: function (msg) {
501 this._updateState('failed', msg);
502 return false;
503 },
06a9ef0c 504
b1dee947
SR
505 _handle_message: function () {
506 if (this._sock.rQlen() === 0) {
507 Util.Warn("handle_message called on an empty receive queue");
508 return;
509 }
8db09746 510
b1dee947
SR
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 },
8db09746 536
b1dee947
SR
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 },
8db09746 543
b1dee947
SR
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 },
8db09746 548
b1dee947
SR
549 _handleMouseButton: function (x, y, down, bmask) {
550 if (down) {
551 this._mouse_buttonMask |= bmask;
552 } else {
d02a99f0 553 this._mouse_buttonMask ^= bmask;
b1dee947 554 }
8db09746 555
b1dee947
SR
556 if (this._viewportDrag) {
557 if (down && !this._viewportDragging) {
558 this._viewportDragging = true;
559 this._viewportDragPos = {'x': x, 'y': y};
8db09746 560
b1dee947
SR
561 // Skip sending mouse events
562 return;
563 } else {
564 this._viewportDragging = false;
565 }
b50f3406 566 }
8db09746 567
b1dee947
SR
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);
8db09746 583
b1dee947
SR
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";
8db09746 631 }
b1dee947
SR
632 this._sock.send_string(repeaterID);
633 return true;
8db09746 634 }
b1dee947
SR
635
636 if (this._rfb_version > this._rfb_max_version) {
637 this._rfb_version = this._rfb_max_version;
8db09746 638 }
b1dee947
SR
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);
1f758e87 660 }
b1dee947
SR
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);
03ab2515 673 }
b1dee947
SR
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) +
03ab2515
MS
698 String.fromCharCode(xvp_auth[1].length) +
699 xvp_auth[0] +
700 xvp_auth[1];
b1dee947
SR
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");
8db09746 742 }
b1dee947
SR
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 },
95eb681b 749
b1dee947
SR
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; }
d86cc2d9 755
b1dee947 756 this._rfb_tightvnc = true;
d86cc2d9 757
b1dee947
SR
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; }
95eb681b 768
b1dee947
SR
769 var clientSupportedTypes = {
770 'STDVNOAUTH__': 1,
771 'STDVVNCAUTH_': 2
772 };
d86cc2d9 773
b1dee947 774 var serverSupportedTypes = [];
d86cc2d9 775
b1dee947
SR
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 }
d86cc2d9 781
b1dee947
SR
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 }
d86cc2d9 796 }
b1dee947 797 }
d86cc2d9 798
b1dee947
SR
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");
d86cc2d9 846 }
b1dee947
SR
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);
d86cc2d9
BR
896 }
897
b1dee947
SR
898 for (i = 0; i < numClientMessages; i++) {
899 var clientMsg = this._sock.rQshiftStr(16);
900 }
d86cc2d9 901
b1dee947
SR
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);
60440cee 1134 return false;
aa787069 1135 }
8db09746 1136 }
ac99a1f7 1137
b1dee947
SR
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 }
8db09746 1175
b1dee947 1176 if (!ret) { return ret; } // need more data
d86cc2d9 1177 }
d86cc2d9 1178
b1dee947
SR
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]});
8db09746 1184
b1dee947
SR
1185 return true; // We finished this FBU
1186 },
1187 };
8db09746 1188
b1dee947
SR
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 ]);
a7a89626 1214
b1dee947
SR
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;
a7a89626 1221 } else {
b1dee947 1222 Util.Warn("Browser does not support local cursor");
a7a89626
JM
1223 }
1224 }
b1dee947
SR
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 }
a7a89626 1260
b1dee947
SR
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 }
a7a89626 1301
b1dee947
SR
1302 var arr = [2]; // msg-type
1303 arr.push8(0); // padding
a7a89626 1304
b1dee947
SR
1305 arr.push16(encList.length); // encoding count
1306 for (i = 0; i < encList.length; i++) {
1307 arr.push32(encList[i]);
1308 }
a7a89626 1309
b1dee947
SR
1310 return arr;
1311 },
a679a97d 1312
b1dee947
SR
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));
a7a89626 1323 }
b1dee947
SR
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;
a7a89626 1348 }
b1dee947
SR
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));
a7a89626 1355 }
b1dee947
SR
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;
a7a89626 1367 }
a7a89626 1368
b1dee947
SR
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
a7a89626 1382 } else {
b1dee947
SR
1383 this._FBU.rects--;
1384 this._FBU.bytes = 0;
a7a89626 1385 }
b1dee947
SR
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");
a7a89626 1498 } else {
b1dee947
SR
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;
a7a89626 1509 }
b1dee947
SR
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);
a7a89626 1532
b1dee947
SR
1533 var wh = rQ[rQi];
1534 rQi++;
1535 var sw = (wh >> 4) + 1;
1536 var sh = (wh & 0x0f) + 1;
a7a89626 1537
b1dee947
SR
1538 this._display.subTile(sx, sy, sw, sh, color);
1539 }
1540 }
1541 this._display.finishTile();
a7a89626 1542 }
b1dee947
SR
1543 this._sock.set_rQi(rQi);
1544 this._FBU.lastsubencoding = this._FBU.subencoding;
1545 this._FBU.bytes = 0;
1546 this._FBU.tiles--;
a7a89626 1547 }
d065cad9 1548
b1dee947
SR
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 }
9b75bcaa 1574
b1dee947
SR
1575 this._FBU.bytes = 1; // compression-control byte
1576 if (this._sock.rQwait("TIGHT compression-control", this._FBU.bytes)) { return false; }
9b75bcaa 1577
b1dee947
SR
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 }
de84e098 1596
b1dee947
SR
1597 var uncompressed = this._FBU.zlibs[streamId].uncompress(data, 0);
1598 if (uncompressed.status !== 0) {
1599 Util.Error("Invalid data in zlib stream");
1600 }
de84e098 1601
b1dee947
SR
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 }
d065cad9 1625
b1dee947
SR
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 }
e16ad2fd
JM
1643 }
1644 }
b1dee947
SR
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));
e16ad2fd 1668 }
b1dee947
SR
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]));
e16ad2fd 1718 }
b1dee947
SR
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");
e16ad2fd 1750 }
de84e098 1751
b1dee947
SR
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 }
d065cad9 1769
b1dee947
SR
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 }
5ca5e2d8 1820
de84e098 1821
b1dee947
SR
1822 this._FBU.bytes = 0;
1823 this._FBU.rects--;
c514fd5e 1824
b1dee947
SR
1825 return true;
1826 },
de84e098 1827
b1dee947
SR
1828 TIGHT: function () { return this._encHandlers.display_tight(false); },
1829 TIGHT_PNG: function () { return this._encHandlers.display_tight(true); },
a7a89626 1830
b1dee947
SR
1831 last_rect: function () {
1832 this._FBU.rects = 0;
1833 return true;
1834 },
a7a89626 1835
b1dee947
SR
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;
8db09746 1860
b1dee947
SR
1861 this._FBU.bytes = pixelslength + masklength;
1862 if (this._sock.rQwait("cursor encoding", this._FBU.bytes)) { return false; }
8db09746 1863
b1dee947
SR
1864 this._display.changeCursor(this._sock.rQshiftBytes(pixelslength),
1865 this._sock.rQshiftBytes(masklength),
1866 x, y, w, h);
a7a89626 1867
b1dee947
SR
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})();