summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSamuel Mannehed <samuel@cendio.se>2017-11-11 01:48:47 +0100
committerSamuel Mannehed <samuel@cendio.se>2017-11-14 15:36:12 +0100
commitd472f3f19ef8cb9000ccfdf854d9f40e587fac0a (patch)
tree5b228b6911897efe7482716a627500470e79a5d1
parentee5cae9fee484794211d89de55e9d454abd4d599 (diff)
downloadnovnc-d472f3f19ef8cb9000ccfdf854d9f40e587fac0a.tar.gz
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.
-rw-r--r--app/ui.js20
-rw-r--r--core/rfb.js219
-rw-r--r--docs/API.md32
-rw-r--r--tests/playback-ui.js6
-rw-r--r--tests/playback.js7
-rw-r--r--tests/test.rfb.js75
-rw-r--r--vnc_lite.html6
7 files changed, 232 insertions, 133 deletions
diff --git a/app/ui.js b/app/ui.js
index 0ee0c00..cadf44b 100644
--- a/app/ui.js
+++ b/app/ui.js
@@ -1023,6 +1023,7 @@ var UI = {
UI.rfb.addEventListener("connect", UI.connectFinished);
UI.rfb.addEventListener("disconnect", UI.disconnectFinished);
UI.rfb.addEventListener("credentialsrequired", UI.credentials);
+ UI.rfb.addEventListener("securityfailure", UI.securityFailed);
UI.rfb.addEventListener("capabilities", function () { UI.updatePowerButton(); UI.initialResize(); });
UI.rfb.addEventListener("clipboard", UI.clipboardReceive);
UI.rfb.addEventListener("bell", UI.bell);
@@ -1080,9 +1081,10 @@ var UI = {
// UI.disconnect() won't be used in those cases.
UI.connected = false;
- if (typeof e.detail.reason !== 'undefined') {
- UI.showStatus(e.detail.reason, 'error');
+ if (!e.detail.clean) {
UI.updateVisualState('disconnected');
+ UI.showStatus(_("Something went wrong, connection is closed"),
+ 'error');
} else if (UI.getSetting('reconnect', false) === true && !UI.inhibit_reconnect) {
UI.updateVisualState('reconnecting');
@@ -1098,6 +1100,20 @@ var UI = {
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() {
if (UI.reconnect_callback !== null) {
clearTimeout(UI.reconnect_callback);
diff --git a/core/rfb.js b/core/rfb.js
index dced6b1..aa58c00 100644
--- a/core/rfb.js
+++ b/core/rfb.js
@@ -11,7 +11,6 @@
*/
import * as Log from './util/logging.js';
-import _ from './util/localization.js';
import { decodeUTF8 } from './util/strings.js';
import { browserSupportsCursorURIs, isTouchDevice } from './util/browsers.js';
import EventTargetMixin from './util/eventtarget.js';
@@ -54,7 +53,7 @@ export default function RFB(target, url, options) {
this._rfb_connection_state = '';
this._rfb_init_state = '';
this._rfb_auth_scheme = '';
- this._rfb_disconnect_reason = "";
+ this._rfb_clean_disconnect = true;
// Server capabilities
this._rfb_version = 0;
@@ -190,38 +189,40 @@ export default function RFB(target, url, options) {
this._rfb_init_state = 'ProtocolVersion';
Log.Debug("Starting VNC handshake");
} else {
- this._fail("Unexpected server connection");
+ this._fail("Unexpected server connection while " +
+ this._rfb_connection_state);
}
}.bind(this));
this._sock.on('close', function (e) {
Log.Warn("WebSocket on-close event");
var msg = "";
if (e.code) {
- msg = " (code: " + e.code;
+ msg = "(code: " + e.code;
if (e.reason) {
msg += ", reason: " + e.reason;
}
msg += ")";
}
switch (this._rfb_connection_state) {
- case 'disconnecting':
- this._updateConnectionState('disconnected');
- break;
case 'connecting':
- this._fail('Failed to connect to server', msg);
+ this._fail("Connection closed " + msg);
break;
case 'connected':
// Handle disconnects that were initiated server-side
this._updateConnectionState('disconnecting');
this._updateConnectionState('disconnected');
break;
+ case 'disconnecting':
+ // Normal disconnection path
+ this._updateConnectionState('disconnected');
+ break;
case 'disconnected':
- this._fail("Unexpected server disconnect",
- "Already disconnected: " + msg);
+ this._fail("Unexpected server disconnect " +
+ "when already disconnected " + msg);
break;
default:
- this._fail("Unexpected server disconnect",
- "Not in any state yet: " + msg);
+ this._fail("Unexpected server disconnect before connecting " +
+ msg);
break;
}
this._sock.off('close');
@@ -384,9 +385,9 @@ RFB.prototype = {
this._sock.open(this._url, ['binary']);
} catch (e) {
if (e.name === 'SyntaxError') {
- this._fail("Invalid host or port value given", e);
+ this._fail("Invalid host or port (" + e + ")");
} else {
- this._fail("Error while connecting", e);
+ this._fail("Error when opening socket (" + e + ")");
}
}
@@ -539,19 +540,15 @@ RFB.prototype = {
this._disconnect();
this._disconnTimer = setTimeout(function () {
- this._rfb_disconnect_reason = _("Disconnect timeout");
+ Log.Error("Disconnection timed out.");
this._updateConnectionState('disconnected');
}.bind(this), DISCONNECT_TIMEOUT * 1000);
break;
case 'disconnected':
- if (this._rfb_disconnect_reason !== "") {
- event = new CustomEvent("disconnect",
- { detail: { reason: this._rfb_disconnect_reason } });
- } else {
- // No reason means clean disconnect
- event = new CustomEvent("disconnect", { detail: {} });
- }
+ event = new CustomEvent(
+ "disconnect", { detail:
+ { clean: this._rfb_clean_disconnect } });
this.dispatchEvent(event);
break;
}
@@ -559,29 +556,25 @@ RFB.prototype = {
/* 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.
*/
- _fail: function (msg, details) {
- var fullmsg = msg;
- if (typeof details !== 'undefined') {
- fullmsg = msg + " (" + details + ")";
- }
+ _fail: function (details) {
switch (this._rfb_connection_state) {
case 'disconnecting':
- Log.Error("Failed when disconnecting: " + fullmsg);
+ Log.Error("Failed when disconnecting: " + details);
break;
case 'connected':
- Log.Error("Failed while connected: " + fullmsg);
+ Log.Error("Failed while connected: " + details);
break;
case 'connecting':
- Log.Error("Failed when connecting: " + fullmsg);
+ Log.Error("Failed when connecting: " + details);
break;
default:
- Log.Error("RFB failure: " + fullmsg);
+ Log.Error("RFB failure: " + details);
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
this._updateConnectionState('disconnecting');
@@ -693,8 +686,7 @@ RFB.prototype = {
_negotiate_protocol_version: function () {
if (this._sock.rQlen() < 12) {
- return this._fail("Error while negotiating with server",
- "Incomplete protocol version");
+ return this._fail("Received incomplete protocol version.");
}
var sversion = this._sock.rQshiftStr(12).substr(4, 7);
@@ -719,8 +711,7 @@ RFB.prototype = {
this._rfb_version = 3.8;
break;
default:
- return this._fail("Unsupported server",
- "Invalid server version: " + sversion);
+ return this._fail("Invalid server version " + sversion);
}
if (is_repeater) {
@@ -762,10 +753,7 @@ RFB.prototype = {
if (this._sock.rQwait("security type", num_types, 1)) { return false; }
if (num_types === 0) {
- var strlen = this._sock.rQshift32();
- var reason = this._sock.rQshiftStr(strlen);
- return this._fail("Error while negotiating with server",
- "Security failure: " + reason);
+ return this._handle_security_failure("no security types");
}
var types = this._sock.rQshiftBytes(num_types);
@@ -782,8 +770,7 @@ RFB.prototype = {
} else if (includes(2, types)) {
this._rfb_auth_scheme = 2; // VNC Auth
} else {
- return this._fail("Unsupported server",
- "Unsupported security types: " + types);
+ return this._fail("Unsupported security types (types: " + types + ")");
}
this._sock.send([this._rfb_auth_scheme]);
@@ -799,6 +786,59 @@ RFB.prototype = {
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
_negotiate_xvp_auth: function () {
if (!this._rfb_credentials.username ||
@@ -854,15 +894,13 @@ RFB.prototype = {
if (serverSupportedTunnelTypes[0]) {
if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor ||
serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) {
- return this._fail("Unsupported server",
- "Client's tunnel type had the incorrect " +
+ return this._fail("Client's tunnel type had the incorrect " +
"vendor or signature");
}
this._sock.send([0, 0, 0, 0]); // use NOTUNNEL
return false; // wait until we receive the sub auth count to continue
} else {
- return this._fail("Unsupported server",
- "Server wanted tunnels, but doesn't support " +
+ return this._fail("Server wanted tunnels, but doesn't support " +
"the notunnel type");
}
},
@@ -916,24 +954,19 @@ RFB.prototype = {
this._rfb_auth_scheme = 2;
return this._init_msg();
default:
- return this._fail("Unsupported server",
- "Unsupported tiny auth scheme: " +
- authType);
+ return this._fail("Unsupported tiny auth scheme " +
+ "(scheme: " + authType + ")");
}
}
}
- return this._fail("Unsupported server",
- "No supported sub-auth types!");
+ return this._fail("No supported sub-auth types!");
},
_negotiate_authentication: function () {
switch (this._rfb_auth_scheme) {
case 0: // connection failed
- if (this._sock.rQwait("auth reason", 4)) { return false; }
- var strlen = this._sock.rQshift32();
- var reason = this._sock.rQshiftStr(strlen);
- return this._fail("Authentication failure", reason);
+ return this._handle_security_failure("authentication scheme");
case 1: // no auth
if (this._rfb_version >= 3.8) {
@@ -953,33 +986,30 @@ RFB.prototype = {
return this._negotiate_tight_auth();
default:
- return this._fail("Unsupported server",
- "Unsupported auth scheme: " +
- this._rfb_auth_scheme);
+ return this._fail("Unsupported auth scheme (scheme: " +
+ this._rfb_auth_scheme + ")");
}
},
_handle_security_result: function () {
if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
- switch (this._sock.rQshift32()) {
- case 0: // OK
- this._rfb_init_state = 'ClientInitialisation';
- Log.Debug('Authentication OK');
- return this._init_msg();
- case 1: // failed
- if (this._rfb_version >= 3.8) {
- var length = this._sock.rQshift32();
- if (this._sock.rQwait("SecurityResult reason", length, 8)) { return false; }
- var reason = this._sock.rQshiftStr(length);
- return this._fail("Authentication failure", reason);
- } else {
- return this._fail("Authentication failure");
- }
- case 2:
- return this._fail("Too many authentication attempts");
- default:
- return this._fail("Unsupported server",
- "Unknown SecurityResult");
+
+ let status = this._sock.rQshift32();
+
+ if (status === 0) { // OK
+ this._rfb_init_state = 'ClientInitialisation';
+ Log.Debug('Authentication OK');
+ return this._init_msg();
+ } else {
+ if (this._rfb_version >= 3.8) {
+ return this._handle_security_failure("security result", status);
+ } else {
+ let event = new CustomEvent("securityfailure",
+ { detail: { status: status } });
+ this.dispatchEvent(event);
+
+ return this._fail("Security handshake failed");
+ }
}
},
@@ -1158,15 +1188,15 @@ RFB.prototype = {
return this._negotiate_server_init();
default:
- return this._fail("Internal error", "Unknown init state: " +
- this._rfb_init_state);
+ return this._fail("Unknown init state (state: " +
+ this._rfb_init_state + ")");
}
},
_handle_set_colour_map_msg: function () {
Log.Debug("SetColorMapEntries");
- return this._fail("Protocol error", "Unexpected SetColorMapEntries message");
+ return this._fail("Unexpected SetColorMapEntries message");
},
_handle_server_cut_text: function () {
@@ -1215,8 +1245,7 @@ RFB.prototype = {
*/
if (!(flags & (1<<31))) {
- return this._fail("Internal error",
- "Unexpected fence response");
+ return this._fail("Unexpected fence response");
}
// Filter out unsupported flags
@@ -1247,8 +1276,7 @@ RFB.prototype = {
this._setCapability("power", true);
break;
default:
- this._fail("Unexpected server message",
- "Illegal server XVP message " + xvp_msg);
+ this._fail("Illegal server XVP message (msg: " + xvp_msg + ")");
break;
}
@@ -1306,7 +1334,7 @@ RFB.prototype = {
return this._handle_xvp_msg();
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));
return true;
}
@@ -1361,9 +1389,8 @@ RFB.prototype = {
(hdr[10] << 8) + hdr[11], 10);
if (!this._encHandlers[this._FBU.encoding]) {
- this._fail("Unexpected server message",
- "Unsupported encoding " +
- this._FBU.encoding);
+ this._fail("Unsupported encoding (encoding: " +
+ this._FBU.encoding + ")");
return false;
}
}
@@ -1857,8 +1884,8 @@ RFB.encodingHandlers = {
if (this._sock.rQwait("HEXTILE subencoding", this._FBU.bytes)) { return false; }
var subencoding = rQ[rQi]; // Peek
if (subencoding > 30) { // Raw
- this._fail("Unexpected server message",
- "Illegal hextile subencoding: " + subencoding);
+ this._fail("Illegal hextile subencoding (subencoding: " +
+ subencoding + ")");
return false;
}
@@ -2187,9 +2214,8 @@ RFB.encodingHandlers = {
else if (ctl === 0x0A) cmode = "png";
else if (ctl & 0x04) cmode = "filter";
else if (ctl < 0x04) cmode = "copy";
- else return this._fail("Unexpected server message",
- "Illegal tight compression received, " +
- "ctl: " + ctl);
+ else return this._fail("Illegal tight compression received (ctl: " +
+ ctl + ")");
switch (cmode) {
// fill use depth because TPIXELs drop the padding byte
@@ -2249,9 +2275,8 @@ RFB.encodingHandlers = {
} else {
// 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
- this._fail("Unexpected server message",
- "Unsupported tight subencoding received, " +
- "filter: " + filterId);
+ this._fail("Unsupported tight subencoding received " +
+ "(filter: " + filterId + ")");
}
break;
case "copy":
diff --git a/docs/API.md b/docs/API.md
index e194e96..4d4f4d5 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -74,6 +74,10 @@ protocol stream.
- The `credentialsrequired` event is fired when more credentials must
be given to continue.
+[`securityfailure`](#securityfailure)
+ - The `securityfailure` event is fired when the security negotiation
+ with the server fails.
+
[`clipboard`](#clipboard)
- The `clipboard` event is fired when clipboard data is received from
the server.
@@ -186,10 +190,10 @@ the `RFB` object is ready to recieve graphics updates and to send input.
#### disconnect
The `disconnect` event is fired when the connection has been
-terminated. The `detail` property is an `Object` the optionally
-contains the property `reason`. `reason` is a `DOMString` specifying
-the reason in the event of an unexpected termination. `reason` will be
-omitted for a clean termination.
+terminated. The `detail` property is an `Object` that contains the
+property `clean`. `clean` is a `boolean` indicating if the termination
+was clean or not. In the event of an unexpected termination or an error
+`clean` will be set to false.
#### 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
`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
The `clipboard` event is fired when the server has sent clipboard data.
diff --git a/tests/playback-ui.js b/tests/playback-ui.js
index 03b08fe..01ad241 100644
--- a/tests/playback-ui.js
+++ b/tests/playback-ui.js
@@ -115,13 +115,13 @@ IterationPlayer.prototype = {
this._nextIteration();
},
- _disconnected: function (rfb, reason, frame) {
- if (reason) {
+ _disconnected: function (rfb, clean, frame) {
+ if (!clean) {
this._state = 'failed';
}
var evt = new Event('rfbdisconnected');
- evt.reason = reason;
+ evt.clean = clean;
evt.frame = frame;
this.onrfbdisconnected(evt);
diff --git a/tests/playback.js b/tests/playback.js
index 9199eba..c769e88 100644
--- a/tests/playback.js
+++ b/tests/playback.js
@@ -78,7 +78,8 @@ RecordingPlayer.prototype = {
// initialize a new RFB
this._rfb = new RFB(document.getElementById('VNC_canvas'), 'wss://test');
this._rfb.viewOnly = true;
- this._rfb.ondisconnected = this._handleDisconnect.bind(this);
+ this._rfb.addEventListener("disconnect",
+ this._handleDisconnect.bind(this));
this._enablePlaybackMode();
// reset the frame index and timer
@@ -186,8 +187,8 @@ RecordingPlayer.prototype = {
}
},
- _handleDisconnect(rfb, reason) {
+ _handleDisconnect(rfb, clean) {
this._running = false;
- this._disconnected(rfb, reason, this._frame_index);
+ this._disconnected(rfb, clean, this._frame_index);
}
};
diff --git a/tests/test.rfb.js b/tests/test.rfb.js
index 2ef6124..81ee1dd 100644
--- a/tests/test.rfb.js
+++ b/tests/test.rfb.js
@@ -377,26 +377,21 @@ describe('Remote Frame Buffer Protocol Client', function() {
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._fail('a reason');
- expect(client._rfb_disconnect_reason).to.equal('a reason');
- });
-
- it('should not include details in disconnect_reason', function () {
- client._rfb_connection_state = 'connected';
- client._fail('a reason', 'details');
- expect(client._rfb_disconnect_reason).to.equal('a reason');
+ client._fail();
+ expect(client._rfb_clean_disconnect).to.be.false;
});
- it('should result in disconnect callback with message when reason given', function () {
+ it('should result in disconnect event with clean set to false', function () {
client._rfb_connection_state = 'connected';
var spy = sinon.spy();
client.addEventListener("disconnect", spy);
- client._fail('a reason');
+ client._fail();
this.clock.tick(2000);
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;
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();
client.addEventListener("disconnect", spy);
client._rfb_connection_state = 'disconnecting';
- client._rfb_disconnect_reason = "error";
client._updateConnectionState('disconnected');
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();
client.addEventListener("disconnect", spy);
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;
});
- 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();
client.addEventListener("disconnect", spy);
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.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 () {
@@ -688,7 +682,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
sinon.spy(client, '_fail');
client._sock._websocket._receive_data(new Uint8Array(data));
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 () {
@@ -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];
client._sock._websocket._receive_data(new Uint8Array(failure_data));
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 () {
sinon.spy(client, '_fail');
client._rfb_version = 3.7;
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;
});
});
diff --git a/vnc_lite.html b/vnc_lite.html
index b904b4a..762da01 100644
--- a/vnc_lite.html
+++ b/vnc_lite.html
@@ -162,10 +162,10 @@
function disconnected(e) {
document.getElementById('sendCtrlAltDelButton').disabled = true;
updatePowerButtons();
- if (typeof(e.detail.reason) !== 'undefined') {
- status(e.detail.reason, "error");
- } else {
+ if (e.detail.clean) {
status("Disconnected", "normal");
+ } else {
+ status("Something went wrong, connection is closed", "error");
}
}