Abstract RFB errors to avoid sending strings
The API allowed strings to be passed from the RFB module to the application using the disconnect reason. This caused problems since the application didn't have control over translations for these strings. Most of the information being passed using this string was very technical and not helpful to the end user. One exception to this was the security result information regarding for example authentication failures. The protocol allows the VNC server to pass a string directly to the user in the security result. So the disconnect reason is replaced by a boolean saying if the disconnection was clean or not. And for the security result information from the server, a new event has been added.
This commit is contained in:
parent
ee5cae9fee
commit
d472f3f19e
20
app/ui.js
20
app/ui.js
|
@ -1023,6 +1023,7 @@ var UI = {
|
||||||
UI.rfb.addEventListener("connect", UI.connectFinished);
|
UI.rfb.addEventListener("connect", UI.connectFinished);
|
||||||
UI.rfb.addEventListener("disconnect", UI.disconnectFinished);
|
UI.rfb.addEventListener("disconnect", UI.disconnectFinished);
|
||||||
UI.rfb.addEventListener("credentialsrequired", UI.credentials);
|
UI.rfb.addEventListener("credentialsrequired", UI.credentials);
|
||||||
|
UI.rfb.addEventListener("securityfailure", UI.securityFailed);
|
||||||
UI.rfb.addEventListener("capabilities", function () { UI.updatePowerButton(); UI.initialResize(); });
|
UI.rfb.addEventListener("capabilities", function () { UI.updatePowerButton(); UI.initialResize(); });
|
||||||
UI.rfb.addEventListener("clipboard", UI.clipboardReceive);
|
UI.rfb.addEventListener("clipboard", UI.clipboardReceive);
|
||||||
UI.rfb.addEventListener("bell", UI.bell);
|
UI.rfb.addEventListener("bell", UI.bell);
|
||||||
|
@ -1080,9 +1081,10 @@ var UI = {
|
||||||
// UI.disconnect() won't be used in those cases.
|
// UI.disconnect() won't be used in those cases.
|
||||||
UI.connected = false;
|
UI.connected = false;
|
||||||
|
|
||||||
if (typeof e.detail.reason !== 'undefined') {
|
if (!e.detail.clean) {
|
||||||
UI.showStatus(e.detail.reason, 'error');
|
|
||||||
UI.updateVisualState('disconnected');
|
UI.updateVisualState('disconnected');
|
||||||
|
UI.showStatus(_("Something went wrong, connection is closed"),
|
||||||
|
'error');
|
||||||
} else if (UI.getSetting('reconnect', false) === true && !UI.inhibit_reconnect) {
|
} else if (UI.getSetting('reconnect', false) === true && !UI.inhibit_reconnect) {
|
||||||
UI.updateVisualState('reconnecting');
|
UI.updateVisualState('reconnecting');
|
||||||
|
|
||||||
|
@ -1098,6 +1100,20 @@ var UI = {
|
||||||
UI.openConnectPanel();
|
UI.openConnectPanel();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
securityFailed: function (e) {
|
||||||
|
let msg = "";
|
||||||
|
// On security failures we might get a string with a reason
|
||||||
|
// directly from the server. Note that we can't control if
|
||||||
|
// this string is translated or not.
|
||||||
|
if ('reason' in e.detail) {
|
||||||
|
msg = _("New connection has been rejected with reason: ") +
|
||||||
|
e.detail.reason;
|
||||||
|
} else {
|
||||||
|
msg = _("New connection has been rejected");
|
||||||
|
}
|
||||||
|
UI.showStatus(msg, 'error');
|
||||||
|
},
|
||||||
|
|
||||||
cancelReconnect: function() {
|
cancelReconnect: function() {
|
||||||
if (UI.reconnect_callback !== null) {
|
if (UI.reconnect_callback !== null) {
|
||||||
clearTimeout(UI.reconnect_callback);
|
clearTimeout(UI.reconnect_callback);
|
||||||
|
|
219
core/rfb.js
219
core/rfb.js
|
@ -11,7 +11,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as Log from './util/logging.js';
|
import * as Log from './util/logging.js';
|
||||||
import _ from './util/localization.js';
|
|
||||||
import { decodeUTF8 } from './util/strings.js';
|
import { decodeUTF8 } from './util/strings.js';
|
||||||
import { browserSupportsCursorURIs, isTouchDevice } from './util/browsers.js';
|
import { browserSupportsCursorURIs, isTouchDevice } from './util/browsers.js';
|
||||||
import EventTargetMixin from './util/eventtarget.js';
|
import EventTargetMixin from './util/eventtarget.js';
|
||||||
|
@ -54,7 +53,7 @@ export default function RFB(target, url, options) {
|
||||||
this._rfb_connection_state = '';
|
this._rfb_connection_state = '';
|
||||||
this._rfb_init_state = '';
|
this._rfb_init_state = '';
|
||||||
this._rfb_auth_scheme = '';
|
this._rfb_auth_scheme = '';
|
||||||
this._rfb_disconnect_reason = "";
|
this._rfb_clean_disconnect = true;
|
||||||
|
|
||||||
// Server capabilities
|
// Server capabilities
|
||||||
this._rfb_version = 0;
|
this._rfb_version = 0;
|
||||||
|
@ -190,38 +189,40 @@ export default function RFB(target, url, options) {
|
||||||
this._rfb_init_state = 'ProtocolVersion';
|
this._rfb_init_state = 'ProtocolVersion';
|
||||||
Log.Debug("Starting VNC handshake");
|
Log.Debug("Starting VNC handshake");
|
||||||
} else {
|
} else {
|
||||||
this._fail("Unexpected server connection");
|
this._fail("Unexpected server connection while " +
|
||||||
|
this._rfb_connection_state);
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
this._sock.on('close', function (e) {
|
this._sock.on('close', function (e) {
|
||||||
Log.Warn("WebSocket on-close event");
|
Log.Warn("WebSocket on-close event");
|
||||||
var msg = "";
|
var msg = "";
|
||||||
if (e.code) {
|
if (e.code) {
|
||||||
msg = " (code: " + e.code;
|
msg = "(code: " + e.code;
|
||||||
if (e.reason) {
|
if (e.reason) {
|
||||||
msg += ", reason: " + e.reason;
|
msg += ", reason: " + e.reason;
|
||||||
}
|
}
|
||||||
msg += ")";
|
msg += ")";
|
||||||
}
|
}
|
||||||
switch (this._rfb_connection_state) {
|
switch (this._rfb_connection_state) {
|
||||||
case 'disconnecting':
|
|
||||||
this._updateConnectionState('disconnected');
|
|
||||||
break;
|
|
||||||
case 'connecting':
|
case 'connecting':
|
||||||
this._fail('Failed to connect to server', msg);
|
this._fail("Connection closed " + msg);
|
||||||
break;
|
break;
|
||||||
case 'connected':
|
case 'connected':
|
||||||
// Handle disconnects that were initiated server-side
|
// Handle disconnects that were initiated server-side
|
||||||
this._updateConnectionState('disconnecting');
|
this._updateConnectionState('disconnecting');
|
||||||
this._updateConnectionState('disconnected');
|
this._updateConnectionState('disconnected');
|
||||||
break;
|
break;
|
||||||
|
case 'disconnecting':
|
||||||
|
// Normal disconnection path
|
||||||
|
this._updateConnectionState('disconnected');
|
||||||
|
break;
|
||||||
case 'disconnected':
|
case 'disconnected':
|
||||||
this._fail("Unexpected server disconnect",
|
this._fail("Unexpected server disconnect " +
|
||||||
"Already disconnected: " + msg);
|
"when already disconnected " + msg);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
this._fail("Unexpected server disconnect",
|
this._fail("Unexpected server disconnect before connecting " +
|
||||||
"Not in any state yet: " + msg);
|
msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this._sock.off('close');
|
this._sock.off('close');
|
||||||
|
@ -384,9 +385,9 @@ RFB.prototype = {
|
||||||
this._sock.open(this._url, ['binary']);
|
this._sock.open(this._url, ['binary']);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.name === 'SyntaxError') {
|
if (e.name === 'SyntaxError') {
|
||||||
this._fail("Invalid host or port value given", e);
|
this._fail("Invalid host or port (" + e + ")");
|
||||||
} else {
|
} else {
|
||||||
this._fail("Error while connecting", e);
|
this._fail("Error when opening socket (" + e + ")");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -539,19 +540,15 @@ RFB.prototype = {
|
||||||
this._disconnect();
|
this._disconnect();
|
||||||
|
|
||||||
this._disconnTimer = setTimeout(function () {
|
this._disconnTimer = setTimeout(function () {
|
||||||
this._rfb_disconnect_reason = _("Disconnect timeout");
|
Log.Error("Disconnection timed out.");
|
||||||
this._updateConnectionState('disconnected');
|
this._updateConnectionState('disconnected');
|
||||||
}.bind(this), DISCONNECT_TIMEOUT * 1000);
|
}.bind(this), DISCONNECT_TIMEOUT * 1000);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'disconnected':
|
case 'disconnected':
|
||||||
if (this._rfb_disconnect_reason !== "") {
|
event = new CustomEvent(
|
||||||
event = new CustomEvent("disconnect",
|
"disconnect", { detail:
|
||||||
{ detail: { reason: this._rfb_disconnect_reason } });
|
{ clean: this._rfb_clean_disconnect } });
|
||||||
} else {
|
|
||||||
// No reason means clean disconnect
|
|
||||||
event = new CustomEvent("disconnect", { detail: {} });
|
|
||||||
}
|
|
||||||
this.dispatchEvent(event);
|
this.dispatchEvent(event);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -559,29 +556,25 @@ RFB.prototype = {
|
||||||
|
|
||||||
/* Print errors and disconnect
|
/* Print errors and disconnect
|
||||||
*
|
*
|
||||||
* The optional parameter 'details' is used for information that
|
* The parameter 'details' is used for information that
|
||||||
* should be logged but not sent to the user interface.
|
* should be logged but not sent to the user interface.
|
||||||
*/
|
*/
|
||||||
_fail: function (msg, details) {
|
_fail: function (details) {
|
||||||
var fullmsg = msg;
|
|
||||||
if (typeof details !== 'undefined') {
|
|
||||||
fullmsg = msg + " (" + details + ")";
|
|
||||||
}
|
|
||||||
switch (this._rfb_connection_state) {
|
switch (this._rfb_connection_state) {
|
||||||
case 'disconnecting':
|
case 'disconnecting':
|
||||||
Log.Error("Failed when disconnecting: " + fullmsg);
|
Log.Error("Failed when disconnecting: " + details);
|
||||||
break;
|
break;
|
||||||
case 'connected':
|
case 'connected':
|
||||||
Log.Error("Failed while connected: " + fullmsg);
|
Log.Error("Failed while connected: " + details);
|
||||||
break;
|
break;
|
||||||
case 'connecting':
|
case 'connecting':
|
||||||
Log.Error("Failed when connecting: " + fullmsg);
|
Log.Error("Failed when connecting: " + details);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
Log.Error("RFB failure: " + fullmsg);
|
Log.Error("RFB failure: " + details);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this._rfb_disconnect_reason = msg; //This is sent to the UI
|
this._rfb_clean_disconnect = false; //This is sent to the UI
|
||||||
|
|
||||||
// Transition to disconnected without waiting for socket to close
|
// Transition to disconnected without waiting for socket to close
|
||||||
this._updateConnectionState('disconnecting');
|
this._updateConnectionState('disconnecting');
|
||||||
|
@ -693,8 +686,7 @@ RFB.prototype = {
|
||||||
|
|
||||||
_negotiate_protocol_version: function () {
|
_negotiate_protocol_version: function () {
|
||||||
if (this._sock.rQlen() < 12) {
|
if (this._sock.rQlen() < 12) {
|
||||||
return this._fail("Error while negotiating with server",
|
return this._fail("Received incomplete protocol version.");
|
||||||
"Incomplete protocol version");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var sversion = this._sock.rQshiftStr(12).substr(4, 7);
|
var sversion = this._sock.rQshiftStr(12).substr(4, 7);
|
||||||
|
@ -719,8 +711,7 @@ RFB.prototype = {
|
||||||
this._rfb_version = 3.8;
|
this._rfb_version = 3.8;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return this._fail("Unsupported server",
|
return this._fail("Invalid server version " + sversion);
|
||||||
"Invalid server version: " + sversion);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_repeater) {
|
if (is_repeater) {
|
||||||
|
@ -762,10 +753,7 @@ RFB.prototype = {
|
||||||
if (this._sock.rQwait("security type", num_types, 1)) { return false; }
|
if (this._sock.rQwait("security type", num_types, 1)) { return false; }
|
||||||
|
|
||||||
if (num_types === 0) {
|
if (num_types === 0) {
|
||||||
var strlen = this._sock.rQshift32();
|
return this._handle_security_failure("no security types");
|
||||||
var reason = this._sock.rQshiftStr(strlen);
|
|
||||||
return this._fail("Error while negotiating with server",
|
|
||||||
"Security failure: " + reason);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var types = this._sock.rQshiftBytes(num_types);
|
var types = this._sock.rQshiftBytes(num_types);
|
||||||
|
@ -782,8 +770,7 @@ RFB.prototype = {
|
||||||
} else if (includes(2, types)) {
|
} else if (includes(2, types)) {
|
||||||
this._rfb_auth_scheme = 2; // VNC Auth
|
this._rfb_auth_scheme = 2; // VNC Auth
|
||||||
} else {
|
} else {
|
||||||
return this._fail("Unsupported server",
|
return this._fail("Unsupported security types (types: " + types + ")");
|
||||||
"Unsupported security types: " + types);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._sock.send([this._rfb_auth_scheme]);
|
this._sock.send([this._rfb_auth_scheme]);
|
||||||
|
@ -799,6 +786,59 @@ RFB.prototype = {
|
||||||
return this._init_msg(); // jump to authentication
|
return this._init_msg(); // jump to authentication
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the security failure reason if sent from the server and
|
||||||
|
* send the 'securityfailure' event.
|
||||||
|
*
|
||||||
|
* - The optional parameter context can be used to add some extra
|
||||||
|
* context to the log output.
|
||||||
|
*
|
||||||
|
* - The optional parameter security_result_status can be used to
|
||||||
|
* add a custom status code to the event.
|
||||||
|
*/
|
||||||
|
_handle_security_failure: function (context, security_result_status) {
|
||||||
|
|
||||||
|
if (typeof context === 'undefined') {
|
||||||
|
context = "";
|
||||||
|
} else {
|
||||||
|
context = " on " + context;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof security_result_status === 'undefined') {
|
||||||
|
security_result_status = 1; // fail
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._sock.rQwait("reason length", 4)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let strlen = this._sock.rQshift32();
|
||||||
|
let reason = "";
|
||||||
|
|
||||||
|
if (strlen > 0) {
|
||||||
|
if (this._sock.rQwait("reason", strlen, 8)) { return false; }
|
||||||
|
reason = this._sock.rQshiftStr(strlen);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reason !== "") {
|
||||||
|
|
||||||
|
let event = new CustomEvent(
|
||||||
|
"securityfailure",
|
||||||
|
{ detail: { status: security_result_status, reason: reason } });
|
||||||
|
this.dispatchEvent(event);
|
||||||
|
|
||||||
|
return this._fail("Security negotiation failed" + context +
|
||||||
|
" (reason: " + reason + ")");
|
||||||
|
} else {
|
||||||
|
|
||||||
|
let event = new CustomEvent(
|
||||||
|
"securityfailure",
|
||||||
|
{ detail: { status: security_result_status } });
|
||||||
|
this.dispatchEvent(event);
|
||||||
|
|
||||||
|
return this._fail("Security negotiation failed" + context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// authentication
|
// authentication
|
||||||
_negotiate_xvp_auth: function () {
|
_negotiate_xvp_auth: function () {
|
||||||
if (!this._rfb_credentials.username ||
|
if (!this._rfb_credentials.username ||
|
||||||
|
@ -854,15 +894,13 @@ RFB.prototype = {
|
||||||
if (serverSupportedTunnelTypes[0]) {
|
if (serverSupportedTunnelTypes[0]) {
|
||||||
if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor ||
|
if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor ||
|
||||||
serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) {
|
serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) {
|
||||||
return this._fail("Unsupported server",
|
return this._fail("Client's tunnel type had the incorrect " +
|
||||||
"Client's tunnel type had the incorrect " +
|
|
||||||
"vendor or signature");
|
"vendor or signature");
|
||||||
}
|
}
|
||||||
this._sock.send([0, 0, 0, 0]); // use NOTUNNEL
|
this._sock.send([0, 0, 0, 0]); // use NOTUNNEL
|
||||||
return false; // wait until we receive the sub auth count to continue
|
return false; // wait until we receive the sub auth count to continue
|
||||||
} else {
|
} else {
|
||||||
return this._fail("Unsupported server",
|
return this._fail("Server wanted tunnels, but doesn't support " +
|
||||||
"Server wanted tunnels, but doesn't support " +
|
|
||||||
"the notunnel type");
|
"the notunnel type");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -916,24 +954,19 @@ RFB.prototype = {
|
||||||
this._rfb_auth_scheme = 2;
|
this._rfb_auth_scheme = 2;
|
||||||
return this._init_msg();
|
return this._init_msg();
|
||||||
default:
|
default:
|
||||||
return this._fail("Unsupported server",
|
return this._fail("Unsupported tiny auth scheme " +
|
||||||
"Unsupported tiny auth scheme: " +
|
"(scheme: " + authType + ")");
|
||||||
authType);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._fail("Unsupported server",
|
return this._fail("No supported sub-auth types!");
|
||||||
"No supported sub-auth types!");
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_negotiate_authentication: function () {
|
_negotiate_authentication: function () {
|
||||||
switch (this._rfb_auth_scheme) {
|
switch (this._rfb_auth_scheme) {
|
||||||
case 0: // connection failed
|
case 0: // connection failed
|
||||||
if (this._sock.rQwait("auth reason", 4)) { return false; }
|
return this._handle_security_failure("authentication scheme");
|
||||||
var strlen = this._sock.rQshift32();
|
|
||||||
var reason = this._sock.rQshiftStr(strlen);
|
|
||||||
return this._fail("Authentication failure", reason);
|
|
||||||
|
|
||||||
case 1: // no auth
|
case 1: // no auth
|
||||||
if (this._rfb_version >= 3.8) {
|
if (this._rfb_version >= 3.8) {
|
||||||
|
@ -953,33 +986,30 @@ RFB.prototype = {
|
||||||
return this._negotiate_tight_auth();
|
return this._negotiate_tight_auth();
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return this._fail("Unsupported server",
|
return this._fail("Unsupported auth scheme (scheme: " +
|
||||||
"Unsupported auth scheme: " +
|
this._rfb_auth_scheme + ")");
|
||||||
this._rfb_auth_scheme);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_handle_security_result: function () {
|
_handle_security_result: function () {
|
||||||
if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
|
if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
|
||||||
switch (this._sock.rQshift32()) {
|
|
||||||
case 0: // OK
|
let status = this._sock.rQshift32();
|
||||||
this._rfb_init_state = 'ClientInitialisation';
|
|
||||||
Log.Debug('Authentication OK');
|
if (status === 0) { // OK
|
||||||
return this._init_msg();
|
this._rfb_init_state = 'ClientInitialisation';
|
||||||
case 1: // failed
|
Log.Debug('Authentication OK');
|
||||||
if (this._rfb_version >= 3.8) {
|
return this._init_msg();
|
||||||
var length = this._sock.rQshift32();
|
} else {
|
||||||
if (this._sock.rQwait("SecurityResult reason", length, 8)) { return false; }
|
if (this._rfb_version >= 3.8) {
|
||||||
var reason = this._sock.rQshiftStr(length);
|
return this._handle_security_failure("security result", status);
|
||||||
return this._fail("Authentication failure", reason);
|
} else {
|
||||||
} else {
|
let event = new CustomEvent("securityfailure",
|
||||||
return this._fail("Authentication failure");
|
{ detail: { status: status } });
|
||||||
}
|
this.dispatchEvent(event);
|
||||||
case 2:
|
|
||||||
return this._fail("Too many authentication attempts");
|
return this._fail("Security handshake failed");
|
||||||
default:
|
}
|
||||||
return this._fail("Unsupported server",
|
|
||||||
"Unknown SecurityResult");
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1158,15 +1188,15 @@ RFB.prototype = {
|
||||||
return this._negotiate_server_init();
|
return this._negotiate_server_init();
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return this._fail("Internal error", "Unknown init state: " +
|
return this._fail("Unknown init state (state: " +
|
||||||
this._rfb_init_state);
|
this._rfb_init_state + ")");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_handle_set_colour_map_msg: function () {
|
_handle_set_colour_map_msg: function () {
|
||||||
Log.Debug("SetColorMapEntries");
|
Log.Debug("SetColorMapEntries");
|
||||||
|
|
||||||
return this._fail("Protocol error", "Unexpected SetColorMapEntries message");
|
return this._fail("Unexpected SetColorMapEntries message");
|
||||||
},
|
},
|
||||||
|
|
||||||
_handle_server_cut_text: function () {
|
_handle_server_cut_text: function () {
|
||||||
|
@ -1215,8 +1245,7 @@ RFB.prototype = {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!(flags & (1<<31))) {
|
if (!(flags & (1<<31))) {
|
||||||
return this._fail("Internal error",
|
return this._fail("Unexpected fence response");
|
||||||
"Unexpected fence response");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter out unsupported flags
|
// Filter out unsupported flags
|
||||||
|
@ -1247,8 +1276,7 @@ RFB.prototype = {
|
||||||
this._setCapability("power", true);
|
this._setCapability("power", true);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
this._fail("Unexpected server message",
|
this._fail("Illegal server XVP message (msg: " + xvp_msg + ")");
|
||||||
"Illegal server XVP message " + xvp_msg);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1306,7 +1334,7 @@ RFB.prototype = {
|
||||||
return this._handle_xvp_msg();
|
return this._handle_xvp_msg();
|
||||||
|
|
||||||
default:
|
default:
|
||||||
this._fail("Unexpected server message", "Type:" + msg_type);
|
this._fail("Unexpected server message (type " + msg_type + ")");
|
||||||
Log.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
|
Log.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1361,9 +1389,8 @@ RFB.prototype = {
|
||||||
(hdr[10] << 8) + hdr[11], 10);
|
(hdr[10] << 8) + hdr[11], 10);
|
||||||
|
|
||||||
if (!this._encHandlers[this._FBU.encoding]) {
|
if (!this._encHandlers[this._FBU.encoding]) {
|
||||||
this._fail("Unexpected server message",
|
this._fail("Unsupported encoding (encoding: " +
|
||||||
"Unsupported encoding " +
|
this._FBU.encoding + ")");
|
||||||
this._FBU.encoding);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1857,8 +1884,8 @@ RFB.encodingHandlers = {
|
||||||
if (this._sock.rQwait("HEXTILE subencoding", this._FBU.bytes)) { return false; }
|
if (this._sock.rQwait("HEXTILE subencoding", this._FBU.bytes)) { return false; }
|
||||||
var subencoding = rQ[rQi]; // Peek
|
var subencoding = rQ[rQi]; // Peek
|
||||||
if (subencoding > 30) { // Raw
|
if (subencoding > 30) { // Raw
|
||||||
this._fail("Unexpected server message",
|
this._fail("Illegal hextile subencoding (subencoding: " +
|
||||||
"Illegal hextile subencoding: " + subencoding);
|
subencoding + ")");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2187,9 +2214,8 @@ RFB.encodingHandlers = {
|
||||||
else if (ctl === 0x0A) cmode = "png";
|
else if (ctl === 0x0A) cmode = "png";
|
||||||
else if (ctl & 0x04) cmode = "filter";
|
else if (ctl & 0x04) cmode = "filter";
|
||||||
else if (ctl < 0x04) cmode = "copy";
|
else if (ctl < 0x04) cmode = "copy";
|
||||||
else return this._fail("Unexpected server message",
|
else return this._fail("Illegal tight compression received (ctl: " +
|
||||||
"Illegal tight compression received, " +
|
ctl + ")");
|
||||||
"ctl: " + ctl);
|
|
||||||
|
|
||||||
switch (cmode) {
|
switch (cmode) {
|
||||||
// fill use depth because TPIXELs drop the padding byte
|
// fill use depth because TPIXELs drop the padding byte
|
||||||
|
@ -2249,9 +2275,8 @@ RFB.encodingHandlers = {
|
||||||
} else {
|
} else {
|
||||||
// Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
|
// Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
|
||||||
// Filter 2, Gradient is valid but not use if jpeg is enabled
|
// Filter 2, Gradient is valid but not use if jpeg is enabled
|
||||||
this._fail("Unexpected server message",
|
this._fail("Unsupported tight subencoding received " +
|
||||||
"Unsupported tight subencoding received, " +
|
"(filter: " + filterId + ")");
|
||||||
"filter: " + filterId);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "copy":
|
case "copy":
|
||||||
|
|
32
docs/API.md
32
docs/API.md
|
@ -74,6 +74,10 @@ protocol stream.
|
||||||
- The `credentialsrequired` event is fired when more credentials must
|
- The `credentialsrequired` event is fired when more credentials must
|
||||||
be given to continue.
|
be given to continue.
|
||||||
|
|
||||||
|
[`securityfailure`](#securityfailure)
|
||||||
|
- The `securityfailure` event is fired when the security negotiation
|
||||||
|
with the server fails.
|
||||||
|
|
||||||
[`clipboard`](#clipboard)
|
[`clipboard`](#clipboard)
|
||||||
- The `clipboard` event is fired when clipboard data is received from
|
- The `clipboard` event is fired when clipboard data is received from
|
||||||
the server.
|
the server.
|
||||||
|
@ -186,10 +190,10 @@ the `RFB` object is ready to recieve graphics updates and to send input.
|
||||||
#### disconnect
|
#### disconnect
|
||||||
|
|
||||||
The `disconnect` event is fired when the connection has been
|
The `disconnect` event is fired when the connection has been
|
||||||
terminated. The `detail` property is an `Object` the optionally
|
terminated. The `detail` property is an `Object` that contains the
|
||||||
contains the property `reason`. `reason` is a `DOMString` specifying
|
property `clean`. `clean` is a `boolean` indicating if the termination
|
||||||
the reason in the event of an unexpected termination. `reason` will be
|
was clean or not. In the event of an unexpected termination or an error
|
||||||
omitted for a clean termination.
|
`clean` will be set to false.
|
||||||
|
|
||||||
#### credentialsrequired
|
#### credentialsrequired
|
||||||
|
|
||||||
|
@ -198,6 +202,26 @@ credentials than were specified to [`RFB()`](#rfb-1). The `detail`
|
||||||
property is an `Object` containing the property `types` which is an
|
property is an `Object` containing the property `types` which is an
|
||||||
`Array` of `DOMString` listing the credentials that are required.
|
`Array` of `DOMString` listing the credentials that are required.
|
||||||
|
|
||||||
|
#### securityfailure
|
||||||
|
|
||||||
|
The `securityfailure` event is fired when the handshaking process with
|
||||||
|
the server fails during the security negotiation step. The `detail`
|
||||||
|
property is an `Object` containing the following properties:
|
||||||
|
|
||||||
|
| Property | Type | Description
|
||||||
|
| -------- | ----------- | -----------
|
||||||
|
| `status` | `long` | The failure status code
|
||||||
|
| `reason` | `DOMString` | The **optional** reason for the failure
|
||||||
|
|
||||||
|
The property `status` corresponds to the
|
||||||
|
[SecurityResult](https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#securityresult)
|
||||||
|
status code in cases of failure. A status of zero will not be sent in
|
||||||
|
this event since that indicates a successful security handshaking
|
||||||
|
process. The optional property `reason` is provided by the server and
|
||||||
|
thus the language of the string is not known. However most servers will
|
||||||
|
probably send English strings. The server can choose to not send a
|
||||||
|
reason and in these cases the `reason` property will be omitted.
|
||||||
|
|
||||||
#### clipboard
|
#### clipboard
|
||||||
|
|
||||||
The `clipboard` event is fired when the server has sent clipboard data.
|
The `clipboard` event is fired when the server has sent clipboard data.
|
||||||
|
|
|
@ -115,13 +115,13 @@ IterationPlayer.prototype = {
|
||||||
this._nextIteration();
|
this._nextIteration();
|
||||||
},
|
},
|
||||||
|
|
||||||
_disconnected: function (rfb, reason, frame) {
|
_disconnected: function (rfb, clean, frame) {
|
||||||
if (reason) {
|
if (!clean) {
|
||||||
this._state = 'failed';
|
this._state = 'failed';
|
||||||
}
|
}
|
||||||
|
|
||||||
var evt = new Event('rfbdisconnected');
|
var evt = new Event('rfbdisconnected');
|
||||||
evt.reason = reason;
|
evt.clean = clean;
|
||||||
evt.frame = frame;
|
evt.frame = frame;
|
||||||
|
|
||||||
this.onrfbdisconnected(evt);
|
this.onrfbdisconnected(evt);
|
||||||
|
|
|
@ -78,7 +78,8 @@ RecordingPlayer.prototype = {
|
||||||
// initialize a new RFB
|
// initialize a new RFB
|
||||||
this._rfb = new RFB(document.getElementById('VNC_canvas'), 'wss://test');
|
this._rfb = new RFB(document.getElementById('VNC_canvas'), 'wss://test');
|
||||||
this._rfb.viewOnly = true;
|
this._rfb.viewOnly = true;
|
||||||
this._rfb.ondisconnected = this._handleDisconnect.bind(this);
|
this._rfb.addEventListener("disconnect",
|
||||||
|
this._handleDisconnect.bind(this));
|
||||||
this._enablePlaybackMode();
|
this._enablePlaybackMode();
|
||||||
|
|
||||||
// reset the frame index and timer
|
// reset the frame index and timer
|
||||||
|
@ -186,8 +187,8 @@ RecordingPlayer.prototype = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_handleDisconnect(rfb, reason) {
|
_handleDisconnect(rfb, clean) {
|
||||||
this._running = false;
|
this._running = false;
|
||||||
this._disconnected(rfb, reason, this._frame_index);
|
this._disconnected(rfb, clean, this._frame_index);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -377,26 +377,21 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
||||||
expect(client._rfb_connection_state).to.equal('disconnected');
|
expect(client._rfb_connection_state).to.equal('disconnected');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set disconnect_reason', function () {
|
it('should set clean_disconnect variable', function () {
|
||||||
|
client._rfb_clean_disconnect = true;
|
||||||
client._rfb_connection_state = 'connected';
|
client._rfb_connection_state = 'connected';
|
||||||
client._fail('a reason');
|
client._fail();
|
||||||
expect(client._rfb_disconnect_reason).to.equal('a reason');
|
expect(client._rfb_clean_disconnect).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not include details in disconnect_reason', function () {
|
it('should result in disconnect event with clean set to false', function () {
|
||||||
client._rfb_connection_state = 'connected';
|
|
||||||
client._fail('a reason', 'details');
|
|
||||||
expect(client._rfb_disconnect_reason).to.equal('a reason');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should result in disconnect callback with message when reason given', function () {
|
|
||||||
client._rfb_connection_state = 'connected';
|
client._rfb_connection_state = 'connected';
|
||||||
var spy = sinon.spy();
|
var spy = sinon.spy();
|
||||||
client.addEventListener("disconnect", spy);
|
client.addEventListener("disconnect", spy);
|
||||||
client._fail('a reason');
|
client._fail();
|
||||||
this.clock.tick(2000);
|
this.clock.tick(2000);
|
||||||
expect(spy).to.have.been.calledOnce;
|
expect(spy).to.have.been.calledOnce;
|
||||||
expect(spy.args[0][0].detail.reason).to.equal('a reason');
|
expect(spy.args[0][0].detail.clean).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -471,17 +466,16 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
||||||
var client;
|
var client;
|
||||||
beforeEach(function () { client = make_rfb(); });
|
beforeEach(function () { client = make_rfb(); });
|
||||||
|
|
||||||
it('should call the disconnect callback if the state is "disconnected"', function () {
|
it('should result in a disconnect event if state becomes "disconnected"', function () {
|
||||||
var spy = sinon.spy();
|
var spy = sinon.spy();
|
||||||
client.addEventListener("disconnect", spy);
|
client.addEventListener("disconnect", spy);
|
||||||
client._rfb_connection_state = 'disconnecting';
|
client._rfb_connection_state = 'disconnecting';
|
||||||
client._rfb_disconnect_reason = "error";
|
|
||||||
client._updateConnectionState('disconnected');
|
client._updateConnectionState('disconnected');
|
||||||
expect(spy).to.have.been.calledOnce;
|
expect(spy).to.have.been.calledOnce;
|
||||||
expect(spy.args[0][0].detail.reason).to.equal("error");
|
expect(spy.args[0][0].detail.clean).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not call the disconnect callback if the state is not "disconnected"', function () {
|
it('should not result in a disconnect event if the state is not "disconnected"', function () {
|
||||||
var spy = sinon.spy();
|
var spy = sinon.spy();
|
||||||
client.addEventListener("disconnect", spy);
|
client.addEventListener("disconnect", spy);
|
||||||
client._sock._websocket.close = function () {}; // explicitly don't call onclose
|
client._sock._websocket.close = function () {}; // explicitly don't call onclose
|
||||||
|
@ -489,7 +483,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
||||||
expect(spy).to.not.have.been.called;
|
expect(spy).to.not.have.been.called;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call the disconnect callback without msg when no reason given', function () {
|
it('should result in a disconnect event without msg when no reason given', function () {
|
||||||
var spy = sinon.spy();
|
var spy = sinon.spy();
|
||||||
client.addEventListener("disconnect", spy);
|
client.addEventListener("disconnect", spy);
|
||||||
client._rfb_connection_state = 'disconnecting';
|
client._rfb_connection_state = 'disconnecting';
|
||||||
|
@ -653,7 +647,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
||||||
|
|
||||||
expect(client._fail).to.have.been.calledOnce;
|
expect(client._fail).to.have.been.calledOnce;
|
||||||
expect(client._fail).to.have.been.calledWith(
|
expect(client._fail).to.have.been.calledWith(
|
||||||
'Error while negotiating with server','Security failure: whoops');
|
'Security negotiation failed on no security types (reason: whoops)');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should transition to the Authentication state and continue on successful negotiation', function () {
|
it('should transition to the Authentication state and continue on successful negotiation', function () {
|
||||||
|
@ -688,7 +682,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
||||||
sinon.spy(client, '_fail');
|
sinon.spy(client, '_fail');
|
||||||
client._sock._websocket._receive_data(new Uint8Array(data));
|
client._sock._websocket._receive_data(new Uint8Array(data));
|
||||||
expect(client._fail).to.have.been.calledWith(
|
expect(client._fail).to.have.been.calledWith(
|
||||||
'Authentication failure', 'Whoopsies');
|
'Security negotiation failed on authentication scheme (reason: Whoopsies)');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should transition straight to SecurityResult on "no auth" (1) for versions >= 3.8', function () {
|
it('should transition straight to SecurityResult on "no auth" (1) for versions >= 3.8', function () {
|
||||||
|
@ -909,14 +903,53 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
||||||
var failure_data = [0, 0, 0, 1, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
|
var failure_data = [0, 0, 0, 1, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
|
||||||
client._sock._websocket._receive_data(new Uint8Array(failure_data));
|
client._sock._websocket._receive_data(new Uint8Array(failure_data));
|
||||||
expect(client._fail).to.have.been.calledWith(
|
expect(client._fail).to.have.been.calledWith(
|
||||||
'Authentication failure', 'whoops');
|
'Security negotiation failed on security result (reason: whoops)');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail on an error code of 1 with a standard message for version < 3.8', function () {
|
it('should fail on an error code of 1 with a standard message for version < 3.8', function () {
|
||||||
sinon.spy(client, '_fail');
|
sinon.spy(client, '_fail');
|
||||||
client._rfb_version = 3.7;
|
client._rfb_version = 3.7;
|
||||||
client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 1]));
|
client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 1]));
|
||||||
expect(client._fail).to.have.been.calledWith('Authentication failure');
|
expect(client._fail).to.have.been.calledWith(
|
||||||
|
'Security handshake failed');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should result in securityfailure event when receiving a non zero status', function () {
|
||||||
|
var spy = sinon.spy();
|
||||||
|
client.addEventListener("securityfailure", spy);
|
||||||
|
client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 2]));
|
||||||
|
expect(spy).to.have.been.calledOnce;
|
||||||
|
expect(spy.args[0][0].detail.status).to.equal(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include reason when provided in securityfailure event', function () {
|
||||||
|
client._rfb_version = 3.8;
|
||||||
|
var spy = sinon.spy();
|
||||||
|
client.addEventListener("securityfailure", spy);
|
||||||
|
var failure_data = [0, 0, 0, 1, 0, 0, 0, 12, 115, 117, 99, 104,
|
||||||
|
32, 102, 97, 105, 108, 117, 114, 101];
|
||||||
|
client._sock._websocket._receive_data(new Uint8Array(failure_data));
|
||||||
|
expect(spy.args[0][0].detail.status).to.equal(1);
|
||||||
|
expect(spy.args[0][0].detail.reason).to.equal('such failure');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not include reason when length is zero in securityfailure event', function () {
|
||||||
|
client._rfb_version = 3.9;
|
||||||
|
var spy = sinon.spy();
|
||||||
|
client.addEventListener("securityfailure", spy);
|
||||||
|
var failure_data = [0, 0, 0, 1, 0, 0, 0, 0];
|
||||||
|
client._sock._websocket._receive_data(new Uint8Array(failure_data));
|
||||||
|
expect(spy.args[0][0].detail.status).to.equal(1);
|
||||||
|
expect('reason' in spy.args[0][0].detail).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not include reason in securityfailure event for version < 3.8', function () {
|
||||||
|
client._rfb_version = 3.6;
|
||||||
|
var spy = sinon.spy();
|
||||||
|
client.addEventListener("securityfailure", spy);
|
||||||
|
client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 2]));
|
||||||
|
expect(spy.args[0][0].detail.status).to.equal(2);
|
||||||
|
expect('reason' in spy.args[0][0].detail).to.be.false;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -162,10 +162,10 @@
|
||||||
function disconnected(e) {
|
function disconnected(e) {
|
||||||
document.getElementById('sendCtrlAltDelButton').disabled = true;
|
document.getElementById('sendCtrlAltDelButton').disabled = true;
|
||||||
updatePowerButtons();
|
updatePowerButtons();
|
||||||
if (typeof(e.detail.reason) !== 'undefined') {
|
if (e.detail.clean) {
|
||||||
status(e.detail.reason, "error");
|
|
||||||
} else {
|
|
||||||
status("Disconnected", "normal");
|
status("Disconnected", "normal");
|
||||||
|
} else {
|
||||||
|
status("Something went wrong, connection is closed", "error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue