85 Commits

Author SHA1 Message Date
Pierre Ossman d3aaf4d5b3 Upgrade base snap to Ubuntu 22.04
Ubuntu 18.04 base snap is no longer supported, so switch to the
currently newest one.
2024-01-10 14:44:44 +01:00
Pierre Ossman 796e924e47 Remove unused npm dependencies
These should have been removed as part of 890cff9.
2024-01-10 14:41:52 +01:00
Pierre Ossman 829725b30e Handle relative paths in novnc_proxy
websockify changes the working directory before it starts looking for
files, so we must give it relative paths for things to work reliably.
2023-12-05 11:33:15 +01:00
Pierre Ossman 9ac632deee Handle immediate connection errors
The browser might throw an exception right away if there is something it
doesn't like with our connect attempt. E.g. using a non-TLS WebSocket
from a TLS web page.
2023-12-05 11:30:30 +01:00
Pierre Ossman 7d2dad0f9e Merge branch 'listen-host' of https://github.com/afbjorklund/noVNC 2023-10-27 12:57:09 +02:00
Anders F Björklund 5ebc297164 Remove comment about websockify command arguments 2023-10-14 09:55:25 +02:00
Anders F Björklund a792b7f39e Document default port applies to all interfaces 2023-10-14 09:55:25 +02:00
Anders F Björklund f1174023c1 Add the possibility to listen on a specific host
For instance, for listening only on "localhost"

That is, bind on 127.0.0.1 instead of 0.0.0.0
2023-10-14 09:55:24 +02:00
Pierre Ossman 85a465288b Merge branch 'qemu_ledstate_pseudo_encoding' of https://github.com/otthou/noVNC 2023-09-29 14:18:02 +02:00
Otto van Houten a0b7c0dac5 Add QEMU Led Pseudo encoding support
Previously, num-lock and caps-lock syncing was performed on a best effort basis by qemu.
Now, the syncing is performed by no-vnc instead. This allows the led state syncing to work
in cases where it previously couldn't, since no-vnc has with this extension knowledge of both
the remote and local capslock and numlock status, which QEMU doesn't have.
2023-09-29 13:58:55 +02:00
Pierre Ossman bf12c24f4c Fix bad indentation 2023-09-07 15:35:20 +02:00
Pierre Ossman 370f21b117 Correctly handle legacy security rejections
The code comment of this code was entirely incorrect, but the commit
message for 5671072 when it was added was correct. I.e. there is a
result, but not a reason.

Adjust the unit tests to make sure this doesn't regress again.
2023-09-07 14:59:36 +02:00
Pierre Ossman 72f6810797 Correctly handle "none" auth on old servers
There is no security result for the "none" authentication until RFB 3.8.
This got broken by mistake in 5671072.
2023-09-07 14:38:04 +02:00
Pierre Ossman e81602d705 Fix zlib level change in clipboard tests
The compression level got changed in 01bb36d4, but the tests weren't
updated to follow this change.
2023-08-29 17:38:44 +02:00
Pierre Ossman b40a45a11b Remove unused argument to inflateInit()
There is just one argument to inflateInit(). It is inflateInit2() that
takes two arguments.

Since this argument was never used, let's just remove it and keep the
existing behaviour.
2023-08-29 17:30:00 +02:00
Pierre Ossman 01bb36d431 Use proper argument to deflateInit()
This was an accidental copy error from inflator.js. The second argument
to deflateInit() is the compression level, not the window bits.

We have not strong opinions on an appropriate level, so stick to the
default.
2023-08-29 17:28:54 +02:00
Samuel Mannehed 295004cabe Merge pull request #1710 from novnc/localStorage
Don't crash if we can't use localStorage
2023-07-18 09:42:01 +02:00
Samuel Mannehed a30f609de4 Don't crash if we can't use localStorage
Our settings are not a fatal requirement, we can fall back on the
default values if they can't be accessed. A scenario where we've seen
this happen is when cookies are disabled in the browser. It seems
localStorage is disabled along with cookies in these settings.

So, lets log a message about the failure and otherwise silently
continue in this case.

Fixes issue #1577.
2023-07-13 14:35:07 +02:00
Pierre Ossman ca6527c1bf Merge branch 'websock' of https://github.com/CendioOssman/noVNC 2023-06-30 18:20:44 +02:00
Pierre Ossman ccef89f556 Implicitly flush Websock if needed
Callers shouldn't have to deal with the internal buffering limits of
Websock, so implicitly flush the buffer if more room is needed.
2023-06-04 22:32:43 +02:00
Pierre Ossman f8b65f9fe1 Add Websock send queue helpers
Callers shouldn't be poking around directly in to the send queue, but
should use accessor functions like for the read queue.
2023-06-04 22:32:43 +02:00
Pierre Ossman 7356d4e60b Move WebSocket queue index reset to receive
It's more robust to do this just before we need the space, rather than
assume when the queue will be read and adjust things right after.
2023-06-04 22:32:43 +02:00
Pierre Ossman 3fc0cb0cb7 Remove Base64 WebSocket remnants
There is no encoding/decoding in modern WebSockets, so let's clean up
some of the old crud that no longer serves a purpose.
2023-06-04 22:32:43 +02:00
Pierre Ossman b146de6d69 Avoid internal variables in recv queue tests
Makes for more robust and realistic tests.
2023-06-04 22:32:43 +02:00
Pierre Ossman b298bf9e90 Don't split large WebSocket data in tests
It takes too much time and can make the tests fail.
2023-06-04 22:32:43 +02:00
Pierre Ossman 2a7db6f647 Make ExtendedDesktopSize tests more realistic
Send real messages and avoid poking around in internals, as we weren't
testing things correctly that way.
2023-06-04 19:12:02 +02:00
Pierre Ossman 45cedea78f Don't send SetDesktopSize too early
We don't know the server layout yet, so we can't preserve the screen id
or flags yet at this point. Move it until after we've parsed everything.
2023-06-04 19:12:02 +02:00
Pierre Ossman 12d2e7832d Properly decode ExtendedDesktopSize fields
We are expected to preserve these and use them in our requests back to
the server. We can't do that if we don't actually decode them correctly.
2023-06-04 19:12:02 +02:00
Pierre Ossman 0ccc679d32 Return unsigned values from rQshift32()
This is what we almost always want, and this makes it consistent with
rQshift8() and rQshift16().
2023-06-04 19:12:02 +02:00
Pierre Ossman d0203a5995 Always return copy of data from socket
We don't know how long the caller will hang on to this data, so we need
to be safe by default and assume it will kept indefinitely. That means
we can't return a reference to the internal buffer, as that will get
overwritten with future messages.

We want to avoid unnecessary copying in performance critical code,
though. So allow code to explicitly ask for a shared buffer, assuming
they know the data needs to be consumed immediately.
2023-06-04 19:12:02 +02:00
Pierre Ossman aaa4eb8c3c Use proper socket helpers for FBU header
Let's not duplicate this stuff when we have convenience functions.
2023-06-04 19:00:33 +02:00
Pierre Ossman e01dd27be4 Remove Websock implicit read length
Callers should be properly aware of how much data they need, as they
need to call rQwait() first to ensure the data is present.
2023-06-04 19:00:33 +02:00
Pierre Ossman 55ffe8fc51 Stop exposing Websock queue length
Callers should be using rQwait() to ensure sufficient data is present,
and not poke around in the internal buffering.
2023-06-04 19:00:33 +02:00
Pierre Ossman 0180bc81c1 Stop direct access to socket buffer
Use proper accessor functions instead of poking around in internal
buffers.
2023-06-04 19:00:33 +02:00
Pierre Ossman fb3c8f64e9 Switch Display.flush() to use a promise
That is the modern way to handle operations that cannot complete
immediately.
2023-06-04 19:00:33 +02:00
Pierre Ossman ae9b042df1 Change rQslice() to rQpeekBytes()
We don't need any full slice functionality, so let's change this to
better march rQpeek8() and rQshiftBytes().
2023-06-04 19:00:33 +02:00
Pierre Ossman 87143b361e Reduce kept state in JPEG decoder
We don't have to keep track of this much data between rects, so
restructure things to make it more simple. This allows the JPEG parsing
code to be a pure function which only depends on the input.
2023-06-04 19:00:33 +02:00
Pierre Ossman e8ad466e45 Merge branch 'testsfix' of github.com:CendioOssman/noVNC 2023-06-04 18:59:38 +02:00
Pierre Ossman eb0ad829d2 Check that decoders consume all data
This is extra important in the tests where we expect no changes to the
display, as otherwise we can't tell the difference between success and a
decoder that is simply waiting for more data.
2023-06-03 15:36:29 +02:00
Pierre Ossman d33f5ce77f Make extended clipboard tests independent
Let's test the full final result instead of assuming specific internal
calls.
2023-05-30 20:48:26 +02:00
Pierre Ossman 8ae789daf0 Add missing tests for message encodings
All of these functions should have units tests, even if they are fairly
minimal.
2023-05-30 20:48:26 +02:00
Pierre Ossman 9e02f4d01d Return a copy of the data from FakeWebSocket
The caller might hang on to the data for multiple calls, so we make sure
the returned buffer might not get overwritten.
2023-05-30 20:48:26 +02:00
Pierre Ossman 9c7576a587 Remove bad Websock mock in tests
This small object will not properly fake a Websock in more complex
cases, so let's avoid it and create a real Websock instead.
2023-05-30 20:48:26 +02:00
Pierre Ossman e07ca6a8e2 Fix Websock send tests
Avoid poking around in the internals and instead test what is actually
sent out on the WebSocket.
2023-05-30 20:48:24 +02:00
Pierre Ossman 336ec86997 Remove internal monitoring from Plain tests
Tests should avoid poking in to the internals and should only look at
external behaviour.
2023-05-30 20:11:51 +02:00
Pierre Ossman 0c80c68e92 Avoid hooking in to RFB._fail for tests
This is an internal function so we should not be examining it in the
tests. Instead use the well defined public APIs to check for correct
behaviour.
2023-05-30 20:11:51 +02:00
Pierre Ossman 13fa6b5908 Fix last rect test
Avoid poking in to internals and instead test that the RFB object
responds correctly to new messages.
2023-05-30 20:11:51 +02:00
Pierre Ossman 549ccc7121 Split RSA-AES test data
Make the tests more clear what data is expected in the different stages
of the handshake.
2023-05-30 20:11:51 +02:00
Pierre Ossman 42bc251eb4 Make RSA-AES tests more asynchronous
The code tested here makes heavy use of promises, so it is easier to
test things also using promise centric code.
2023-05-30 20:11:51 +02:00
Pierre Ossman afbb1da4d5 Remove custom RSA-AES event
We shouldn't add extra, undocumented, API just for the tests. They need
to figure out less invasive way to probe things.
2023-05-30 20:11:51 +02:00
Pierre Ossman 458405e05d Merge RSA-AES tests in to RFB tests
These test the RFB class, so they should be with all other tests for
that class.
2023-05-30 20:11:51 +02:00
Pierre Ossman 0ee0e96f34 Fix ARD authentication test to send real data
Stop bypassing the data handling steps in the test as that means those
parts don't get tested.
2023-05-30 20:11:51 +02:00
Pierre Ossman 71bb6f02cd Fix Plain authentication test checks
We should have constants local for the test function when doing
comparisons or we might have false positives because we compare with
buggy values in the code under test.
2023-05-30 20:11:51 +02:00
Pierre Ossman 79f099108f Split Plain authentication tests to own section
VeNCrypt is a multiplexer for many authentication methods, not just
Plain. So let's split it to its own section, just like other types.
2023-05-30 20:11:51 +02:00
Pierre Ossman 0679c8a801 Test credentials using normal API
Avoid poking around in the internals and instead test things using the
official methods and events. This should give us more realistic and
robust tests.
2023-05-30 20:11:51 +02:00
Pierre Ossman 29a50620ff Avoid touching internals in Tight auth tests
We should test using only external manipulation so we don't assume a
specific implementation.
2023-05-30 20:11:51 +02:00
Pierre Ossman c7c293279b Remove commented out Tight test case
This is not something we intend to implement, so remove this never used
test case.
2023-05-30 20:11:51 +02:00
Pierre Ossman cd231e53ed Don't overwrite methods with spies
Spies should just attach without modifying the real method, or we might
get unwanted side effects.
2023-05-30 20:11:51 +02:00
Pierre Ossman 3ef57d1600 Fix security to authentication state test
The "None" authentication will directly progress past authentication, so
it's not a good type for this test.
2023-05-30 20:11:51 +02:00
Pierre Ossman da75689f4c Fix data for empty RRE rect test
The given data was not a correct RRE rect.
2023-05-30 20:11:51 +02:00
Pierre Ossman 9b115a4485 Send ArrayBuffer, not Uint8Array in tests
This matches the true contents of a WebSocket 'message' event, so should
be a more realistic test.
2023-05-30 20:11:51 +02:00
Pierre Ossman 775ccaa74c Handle immediate responses in RSA-AES authentication
The event handlers might provide the needed response right away, before
even existing the event handler. We should be prepared to handle this
case.
2023-05-30 20:11:51 +02:00
Pierre Ossman 0dd9678e64 Harmonise extended clipboard tests
Let them all follow the same pattern to make things more clear.
2023-05-30 20:11:51 +02:00
Pierre Ossman 91307951d3 Fix cached JPEG test
This test didn't really check anything useful as the end result would be
the same if the second JPEG failed to render.

Fix this by clearing the canvas between the images, so we can tell if
the second image actually rendered or not.
2023-05-30 20:11:51 +02:00
Pierre Ossman c2d6a06d6d Merge branch 'master' of https://github.com/lewayotte/noVNC 2023-05-11 12:20:18 +02:00
Pierre Ossman a565ae559f Merge branches 'l10n' and 'fragment' of github.com:CendioOssman/noVNC 2023-05-10 13:23:34 +02:00
Pierre Ossman 0374b4c0fc Handle translation loading in translation class
Let's try to keep as much as possible of the translation handling in a
single place for clarity.
2023-05-10 13:11:51 +02:00
Pierre Ossman cd1a63b737 Restore history state after tests
We don't want to mess up anything permanent in each test or the tests
might start affecting each other.
2023-05-10 12:25:46 +02:00
Pierre Ossman 05b6d2ad67 Fix typos in query variable comment 2023-05-10 12:25:46 +02:00
Pierre Ossman 2a21bee245 Revert broken Add support for URL fragment parameters
This is a revert of the code changes in commit
f796b05e42 as it served no functional
purpose.

Fragments were already respected for setting parameters, via a different
function. Thus it is unclear what that commit tried to fix. It also
complicated things by mixing the document location with the window
location.

The comment changes are useful, though, so those are kept.
2023-05-10 12:25:46 +02:00
Samuel Mannehed cbbd9ab069 Merge pull request #1777 from nwtgck/patch-1
fix typo
2023-04-30 02:36:05 +02:00
Ryo Ota 2a675b3394 fix typo 2023-04-30 02:04:00 +09:00
Lew Ayotte b16f19f9ce Set _rfbVeNCryptState = 4 not == 4 2023-04-06 15:00:21 -05:00
Pierre Ossman 681632bc9f Avoid running tests on l10n singleton
We want tests to be independent, so we cannot have them modify a shared
state, such as the l10n singleton. Make sure each test instantiates its
own object instead.
2023-04-06 11:11:47 +02:00
Pierre Ossman a4453c9a26 Special case English translation fallback
We should not be listing this in LINGUAS as that gives the impression
that English has en explicit translation. Instead, it is a special case
that the code needs to be explicitly aware of.

This reverts 9a06058 in favour of a more robust fix.
2023-04-05 12:46:17 +02:00
Pierre Ossman 747603c0d5 Also re-enable playback on failures
Allows easier testing by being able to run the test multiple times.
2023-04-04 17:02:57 +02:00
Pierre Ossman c1d2449fb8 Fix playback error message for load failure
We expect the promise to be rejected with a string for display to the
user.
2023-04-04 17:02:21 +02:00
Samuel Mannehed 46292477c8 Merge pull request #1768 from mathis-marcotte/add-english-to-supported-langs
Add english to list of supported languages
2023-04-02 20:16:48 +02:00
Mathis Marcotte 9a06058f66 Added english to list of supported languages 2023-03-27 14:23:09 +00:00
Samuel Mannehed 8decca7353 Use unitless numbers for line-height values
Using <length> type values for line-height can give unexpected
inheritance behaviors. If using <length> values, the inherited
line-height on children is calculated using the font-size of the parent.
What we want is for the line-height of children to be calculated using
it's own font-size.

By instead using a unitless number, we get the behavior we want. Note
that this bug has no effects right now since no children to any of the
related elements have different font-sizes.
2023-03-23 11:06:03 +01:00
Samuel Mannehed 4558104196 Properly center the checkbox checkmark
Using a flexbox we can easily center the checkmark without using hard
coded positions.
2023-03-23 11:06:03 +01:00
NNN1590 6751cc1236 Update Japanese translation 2023-03-21 13:15:26 +09:00
Pierre Ossman 9985950bfa Upgrade to latest websockify in snap package 2023-01-26 10:45:26 +01:00
Pierre Ossman a0e6e7b1d8 Merge branch 'crypto-cleanup-fallback' of https://github.com/pdlan/noVNC 2023-01-20 16:52:32 +01:00
pdlan f974b73137 Cleanup for the cryptographic algorithms that are not supported by SubtleCrypto 2023-01-20 05:54:00 -05:00
50 changed files with 3358 additions and 2181 deletions
+38 -11
View File
@@ -16,13 +16,19 @@ export class Localizer {
this.language = 'en'; this.language = 'en';
// Current dictionary of translations // Current dictionary of translations
this.dictionary = undefined; this._dictionary = undefined;
} }
// Configure suitable language based on user preferences // Configure suitable language based on user preferences
setup(supportedLanguages) { async setup(supportedLanguages, baseURL) {
this.language = 'en'; // Default: US English this.language = 'en'; // Default: US English
this._dictionary = undefined;
this._setupLanguage(supportedLanguages);
await this._setupDictionary(baseURL);
}
_setupLanguage(supportedLanguages) {
/* /*
* Navigator.languages only available in Chrome (32+) and FireFox (32+) * Navigator.languages only available in Chrome (32+) and FireFox (32+)
* Fall back to navigator.language for other browsers * Fall back to navigator.language for other browsers
@@ -40,12 +46,6 @@ export class Localizer {
.replace("_", "-") .replace("_", "-")
.split("-"); .split("-");
// Built-in default?
if ((userLang[0] === 'en') &&
((userLang[1] === undefined) || (userLang[1] === 'us'))) {
return;
}
// First pass: perfect match // First pass: perfect match
for (let j = 0; j < supportedLanguages.length; j++) { for (let j = 0; j < supportedLanguages.length; j++) {
const supLang = supportedLanguages[j] const supLang = supportedLanguages[j]
@@ -64,7 +64,12 @@ export class Localizer {
return; return;
} }
// Second pass: fallback // Second pass: English fallback
if (userLang[0] === 'en') {
return;
}
// Third pass pass: other fallback
for (let j = 0;j < supportedLanguages.length;j++) { for (let j = 0;j < supportedLanguages.length;j++) {
const supLang = supportedLanguages[j] const supLang = supportedLanguages[j]
.toLowerCase() .toLowerCase()
@@ -84,10 +89,32 @@ export class Localizer {
} }
} }
async _setupDictionary(baseURL) {
if (baseURL) {
if (!baseURL.endsWith("/")) {
baseURL = baseURL + "/";
}
} else {
baseURL = "";
}
if (this.language === "en") {
return;
}
let response = await fetch(baseURL + this.language + ".json");
if (!response.ok) {
throw Error("" + response.status + " " + response.statusText);
}
this._dictionary = await response.json();
}
// Retrieve localised text // Retrieve localised text
get(id) { get(id) {
if (typeof this.dictionary !== 'undefined' && this.dictionary[id]) { if (typeof this._dictionary !== 'undefined' &&
return this.dictionary[id]; this._dictionary[id]) {
return this._dictionary[id];
} else { } else {
return id; return id;
} }
+2 -2
View File
@@ -661,7 +661,7 @@ html {
justify-content: center; justify-content: center;
align-content: center; align-content: center;
line-height: 25px; line-height: 1.6;
word-wrap: break-word; word-wrap: break-word;
color: #fff; color: #fff;
@@ -887,7 +887,7 @@ html {
.noVNC_logo { .noVNC_logo {
color:yellow; color:yellow;
font-family: 'Orbitron', 'OrbitronTTF', sans-serif; font-family: 'Orbitron', 'OrbitronTTF', sans-serif;
line-height:90%; line-height: 0.9;
text-shadow: 0.1em 0.1em 0 black; text-shadow: 0.1em 0.1em 0 black;
} }
.noVNC_logo span{ .noVNC_logo span{
+4 -4
View File
@@ -86,6 +86,9 @@ option {
* Checkboxes * Checkboxes
*/ */
input[type=checkbox] { input[type=checkbox] {
display: inline-flex;
justify-content: center;
align-items: center;
background-color: white; background-color: white;
background-image: unset; background-image: unset;
border: 1px solid dimgrey; border: 1px solid dimgrey;
@@ -104,14 +107,11 @@ input[type=checkbox]:checked {
input[type=checkbox]:checked::after { input[type=checkbox]:checked::after {
content: ""; content: "";
display: block; /* width & height doesn't work on inline elements */ display: block; /* width & height doesn't work on inline elements */
position: relative;
top: 0;
left: 3px;
width: 3px; width: 3px;
height: 7px; height: 7px;
border: 1px solid white; border: 1px solid white;
border-width: 0 2px 2px 0; border-width: 0 2px 2px 0;
transform: rotate(40deg); transform: rotate(40deg) translateY(-1px);
} }
/* /*
+15 -19
View File
@@ -1041,10 +1041,18 @@ const UI = {
} }
url += '/' + path; url += '/' + path;
UI.rfb = new RFB(document.getElementById('noVNC_container'), url, try {
{ shared: UI.getSetting('shared'), UI.rfb = new RFB(document.getElementById('noVNC_container'), url,
repeaterID: UI.getSetting('repeaterID'), { shared: UI.getSetting('shared'),
credentials: { password: password } }); repeaterID: UI.getSetting('repeaterID'),
credentials: { password: password } });
} catch (exc) {
Log.Error("Failed to connect to server: " + exc);
UI.updateVisualState('disconnected');
UI.showStatus(_("Failed to connect to server: ") + exc, 'error');
return;
}
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("serververification", UI.serverVerify); UI.rfb.addEventListener("serververification", UI.serverVerify);
@@ -1763,20 +1771,8 @@ const UI = {
// Set up translations // Set up translations
const LINGUAS = ["cs", "de", "el", "es", "fr", "it", "ja", "ko", "nl", "pl", "pt_BR", "ru", "sv", "tr", "zh_CN", "zh_TW"]; const LINGUAS = ["cs", "de", "el", "es", "fr", "it", "ja", "ko", "nl", "pl", "pt_BR", "ru", "sv", "tr", "zh_CN", "zh_TW"];
l10n.setup(LINGUAS); l10n.setup(LINGUAS, "app/locale/")
if (l10n.language === "en" || l10n.dictionary !== undefined) { .catch(err => Log.Error("Failed to load translations: " + err))
UI.prime(); .then(UI.prime);
} else {
fetch('app/locale/' + l10n.language + '.json')
.then((response) => {
if (!response.ok) {
throw Error("" + response.status + " " + response.statusText);
}
return response.json();
})
.then((translations) => { l10n.dictionary = translations; })
.catch(err => Log.Error("Failed to load translations: " + err))
.then(UI.prime);
}
export default UI; export default UI;
+73 -9
View File
@@ -6,16 +6,16 @@
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.
*/ */
import { initLogging as mainInitLogging } from '../core/util/logging.js'; import * as Log from '../core/util/logging.js';
// init log level reading the logging HTTP param // init log level reading the logging HTTP param
export function initLogging(level) { export function initLogging(level) {
"use strict"; "use strict";
if (typeof level !== "undefined") { if (typeof level !== "undefined") {
mainInitLogging(level); Log.initLogging(level);
} else { } else {
const param = document.location.href.match(/logging=([A-Za-z0-9._-]*)/); const param = document.location.href.match(/logging=([A-Za-z0-9._-]*)/);
mainInitLogging(param || undefined); Log.initLogging(param || undefined);
} }
} }
@@ -25,14 +25,14 @@ export function initLogging(level) {
// //
// For privacy (Using a hastag #, the parameters will not be sent to the server) // For privacy (Using a hastag #, the parameters will not be sent to the server)
// the url can be requested in the following way: // the url can be requested in the following way:
// https://www.example.com#myqueryparam=myvalue&password=secreatvalue // https://www.example.com#myqueryparam=myvalue&password=secretvalue
// //
// Even Mixing public and non public parameters will work: // Even Mixing public and non public parameters will work:
// https://www.example.com?nonsecretparam=example.com#password=secreatvalue // https://www.example.com?nonsecretparam=example.com#password=secretvalue
export function getQueryVar(name, defVal) { export function getQueryVar(name, defVal) {
"use strict"; "use strict";
const re = new RegExp('.*[?&]' + name + '=([^&#]*)'), const re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
match = ''.concat(document.location.href, window.location.hash).match(re); match = document.location.href.match(re);
if (typeof defVal === 'undefined') { defVal = null; } if (typeof defVal === 'undefined') { defVal = null; }
if (match) { if (match) {
@@ -146,7 +146,7 @@ export function writeSetting(name, value) {
if (window.chrome && window.chrome.storage) { if (window.chrome && window.chrome.storage) {
window.chrome.storage.sync.set(settings); window.chrome.storage.sync.set(settings);
} else { } else {
localStorage.setItem(name, value); localStorageSet(name, value);
} }
} }
@@ -156,7 +156,7 @@ export function readSetting(name, defaultValue) {
if ((name in settings) || (window.chrome && window.chrome.storage)) { if ((name in settings) || (window.chrome && window.chrome.storage)) {
value = settings[name]; value = settings[name];
} else { } else {
value = localStorage.getItem(name); value = localStorageGet(name);
settings[name] = value; settings[name] = value;
} }
if (typeof value === "undefined") { if (typeof value === "undefined") {
@@ -181,6 +181,70 @@ export function eraseSetting(name) {
if (window.chrome && window.chrome.storage) { if (window.chrome && window.chrome.storage) {
window.chrome.storage.sync.remove(name); window.chrome.storage.sync.remove(name);
} else { } else {
localStorage.removeItem(name); localStorageRemove(name);
}
}
let loggedMsgs = [];
function logOnce(msg, level = "warn") {
if (!loggedMsgs.includes(msg)) {
switch (level) {
case "error":
Log.Error(msg);
break;
case "warn":
Log.Warn(msg);
break;
case "debug":
Log.Debug(msg);
break;
default:
Log.Info(msg);
}
loggedMsgs.push(msg);
}
}
let cookiesMsg = "Couldn't access noVNC settings, are cookies disabled?";
function localStorageGet(name) {
let r;
try {
r = localStorage.getItem(name);
} catch (e) {
if (e instanceof DOMException) {
logOnce(cookiesMsg);
logOnce("'localStorage.getItem(" + name + ")' failed: " + e,
"debug");
} else {
throw e;
}
}
return r;
}
function localStorageSet(name, value) {
try {
localStorage.setItem(name, value);
} catch (e) {
if (e instanceof DOMException) {
logOnce(cookiesMsg);
logOnce("'localStorage.setItem(" + name + "," + value +
")' failed: " + e, "debug");
} else {
throw e;
}
}
}
function localStorageRemove(name) {
try {
localStorage.removeItem(name);
} catch (e) {
if (e instanceof DOMException) {
logOnce(cookiesMsg);
logOnce("'localStorage.removeItem(" + name + ")' failed: " + e,
"debug");
} else {
throw e;
}
} }
} }
+178
View File
@@ -0,0 +1,178 @@
export class AESECBCipher {
constructor() {
this._key = null;
}
get algorithm() {
return { name: "AES-ECB" };
}
static async importKey(key, _algorithm, extractable, keyUsages) {
const cipher = new AESECBCipher;
await cipher._importKey(key, extractable, keyUsages);
return cipher;
}
async _importKey(key, extractable, keyUsages) {
this._key = await window.crypto.subtle.importKey(
"raw", key, {name: "AES-CBC"}, extractable, keyUsages);
}
async encrypt(_algorithm, plaintext) {
const x = new Uint8Array(plaintext);
if (x.length % 16 !== 0 || this._key === null) {
return null;
}
const n = x.length / 16;
for (let i = 0; i < n; i++) {
const y = new Uint8Array(await window.crypto.subtle.encrypt({
name: "AES-CBC",
iv: new Uint8Array(16),
}, this._key, x.slice(i * 16, i * 16 + 16))).slice(0, 16);
x.set(y, i * 16);
}
return x;
}
}
export class AESEAXCipher {
constructor() {
this._rawKey = null;
this._ctrKey = null;
this._cbcKey = null;
this._zeroBlock = new Uint8Array(16);
this._prefixBlock0 = this._zeroBlock;
this._prefixBlock1 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
this._prefixBlock2 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
}
get algorithm() {
return { name: "AES-EAX" };
}
async _encryptBlock(block) {
const encrypted = await window.crypto.subtle.encrypt({
name: "AES-CBC",
iv: this._zeroBlock,
}, this._cbcKey, block);
return new Uint8Array(encrypted).slice(0, 16);
}
async _initCMAC() {
const k1 = await this._encryptBlock(this._zeroBlock);
const k2 = new Uint8Array(16);
const v = k1[0] >>> 6;
for (let i = 0; i < 15; i++) {
k2[i] = (k1[i + 1] >> 6) | (k1[i] << 2);
k1[i] = (k1[i + 1] >> 7) | (k1[i] << 1);
}
const lut = [0x0, 0x87, 0x0e, 0x89];
k2[14] ^= v >>> 1;
k2[15] = (k1[15] << 2) ^ lut[v];
k1[15] = (k1[15] << 1) ^ lut[v >> 1];
this._k1 = k1;
this._k2 = k2;
}
async _encryptCTR(data, counter) {
const encrypted = await window.crypto.subtle.encrypt({
name: "AES-CTR",
counter: counter,
length: 128
}, this._ctrKey, data);
return new Uint8Array(encrypted);
}
async _decryptCTR(data, counter) {
const decrypted = await window.crypto.subtle.decrypt({
name: "AES-CTR",
counter: counter,
length: 128
}, this._ctrKey, data);
return new Uint8Array(decrypted);
}
async _computeCMAC(data, prefixBlock) {
if (prefixBlock.length !== 16) {
return null;
}
const n = Math.floor(data.length / 16);
const m = Math.ceil(data.length / 16);
const r = data.length - n * 16;
const cbcData = new Uint8Array((m + 1) * 16);
cbcData.set(prefixBlock);
cbcData.set(data, 16);
if (r === 0) {
for (let i = 0; i < 16; i++) {
cbcData[n * 16 + i] ^= this._k1[i];
}
} else {
cbcData[(n + 1) * 16 + r] = 0x80;
for (let i = 0; i < 16; i++) {
cbcData[(n + 1) * 16 + i] ^= this._k2[i];
}
}
let cbcEncrypted = await window.crypto.subtle.encrypt({
name: "AES-CBC",
iv: this._zeroBlock,
}, this._cbcKey, cbcData);
cbcEncrypted = new Uint8Array(cbcEncrypted);
const mac = cbcEncrypted.slice(cbcEncrypted.length - 32, cbcEncrypted.length - 16);
return mac;
}
static async importKey(key, _algorithm, _extractable, _keyUsages) {
const cipher = new AESEAXCipher;
await cipher._importKey(key);
return cipher;
}
async _importKey(key) {
this._rawKey = key;
this._ctrKey = await window.crypto.subtle.importKey(
"raw", key, {name: "AES-CTR"}, false, ["encrypt", "decrypt"]);
this._cbcKey = await window.crypto.subtle.importKey(
"raw", key, {name: "AES-CBC"}, false, ["encrypt"]);
await this._initCMAC();
}
async encrypt(algorithm, message) {
const ad = algorithm.additionalData;
const nonce = algorithm.iv;
const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
const encrypted = await this._encryptCTR(message, nCMAC);
const adCMAC = await this._computeCMAC(ad, this._prefixBlock1);
const mac = await this._computeCMAC(encrypted, this._prefixBlock2);
for (let i = 0; i < 16; i++) {
mac[i] ^= nCMAC[i] ^ adCMAC[i];
}
const res = new Uint8Array(16 + encrypted.length);
res.set(encrypted);
res.set(mac, encrypted.length);
return res;
}
async decrypt(algorithm, data) {
const encrypted = data.slice(0, data.length - 16);
const ad = algorithm.additionalData;
const nonce = algorithm.iv;
const mac = data.slice(data.length - 16);
const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
const adCMAC = await this._computeCMAC(ad, this._prefixBlock1);
const computedMac = await this._computeCMAC(encrypted, this._prefixBlock2);
for (let i = 0; i < 16; i++) {
computedMac[i] ^= nCMAC[i] ^ adCMAC[i];
}
if (computedMac.length !== mac.length) {
return null;
}
for (let i = 0; i < mac.length; i++) {
if (computedMac[i] !== mac[i]) {
return null;
}
}
const res = await this._decryptCTR(encrypted, nCMAC);
return res;
}
}
+34
View File
@@ -0,0 +1,34 @@
export function modPow(b, e, m) {
let r = 1n;
b = b % m;
while (e > 0n) {
if ((e & 1n) === 1n) {
r = (r * b) % m;
}
e = e >> 1n;
b = (b * b) % m;
}
return r;
}
export function bigIntToU8Array(bigint, padLength=0) {
let hex = bigint.toString(16);
if (padLength === 0) {
padLength = Math.ceil(hex.length / 2);
}
hex = hex.padStart(padLength * 2, '0');
const length = hex.length / 2;
const arr = new Uint8Array(length);
for (let i = 0; i < length; i++) {
arr[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
}
return arr;
}
export function u8ArrayToBigInt(arr) {
let hex = '0x';
for (let i = 0; i < arr.length; i++) {
hex += arr[i].toString(16).padStart(2, '0');
}
return BigInt(hex);
}
+90
View File
@@ -0,0 +1,90 @@
import { AESECBCipher, AESEAXCipher } from "./aes.js";
import { DESCBCCipher, DESECBCipher } from "./des.js";
import { RSACipher } from "./rsa.js";
import { DHCipher } from "./dh.js";
import { MD5 } from "./md5.js";
// A single interface for the cryptographic algorithms not supported by SubtleCrypto.
// Both synchronous and asynchronous implmentations are allowed.
class LegacyCrypto {
constructor() {
this._algorithms = {
"AES-ECB": AESECBCipher,
"AES-EAX": AESEAXCipher,
"DES-ECB": DESECBCipher,
"DES-CBC": DESCBCCipher,
"RSA-PKCS1-v1_5": RSACipher,
"DH": DHCipher,
"MD5": MD5,
};
}
encrypt(algorithm, key, data) {
if (key.algorithm.name !== algorithm.name) {
throw new Error("algorithm does not match");
}
if (typeof key.encrypt !== "function") {
throw new Error("key does not support encryption");
}
return key.encrypt(algorithm, data);
}
decrypt(algorithm, key, data) {
if (key.algorithm.name !== algorithm.name) {
throw new Error("algorithm does not match");
}
if (typeof key.decrypt !== "function") {
throw new Error("key does not support encryption");
}
return key.decrypt(algorithm, data);
}
importKey(format, keyData, algorithm, extractable, keyUsages) {
if (format !== "raw") {
throw new Error("key format is not supported");
}
const alg = this._algorithms[algorithm.name];
if (typeof alg === "undefined" || typeof alg.importKey !== "function") {
throw new Error("algorithm is not supported");
}
return alg.importKey(keyData, algorithm, extractable, keyUsages);
}
generateKey(algorithm, extractable, keyUsages) {
const alg = this._algorithms[algorithm.name];
if (typeof alg === "undefined" || typeof alg.generateKey !== "function") {
throw new Error("algorithm is not supported");
}
return alg.generateKey(algorithm, extractable, keyUsages);
}
exportKey(format, key) {
if (format !== "raw") {
throw new Error("key format is not supported");
}
if (typeof key.exportKey !== "function") {
throw new Error("key does not support exportKey");
}
return key.exportKey();
}
digest(algorithm, data) {
const alg = this._algorithms[algorithm];
if (typeof alg !== "function") {
throw new Error("algorithm is not supported");
}
return alg(data);
}
deriveBits(algorithm, key, length) {
if (key.algorithm.name !== algorithm.name) {
throw new Error("algorithm does not match");
}
if (typeof key.deriveBits !== "function") {
throw new Error("key does not support deriveBits");
}
return key.deriveBits(algorithm, length);
}
}
export default new LegacyCrypto;
+68 -4
View File
@@ -128,7 +128,7 @@ const SP8 = [b|f,z|e,a|z,c|f,b|z,b|f,z|d,b|z,a|d,c|z,c|f,a|e,c|e,a|f,z|e,z|d,
/* eslint-enable comma-spacing */ /* eslint-enable comma-spacing */
export default class DES { class DES {
constructor(password) { constructor(password) {
this.keys = []; this.keys = [];
@@ -258,9 +258,73 @@ export default class DES {
} }
return b; return b;
} }
}
// Encrypt 16 bytes of text using passwd as key export class DESECBCipher {
encrypt(t) { constructor() {
return this.enc8(t.slice(0, 8)).concat(this.enc8(t.slice(8, 16))); this._cipher = null;
}
get algorithm() {
return { name: "DES-ECB" };
}
static importKey(key, _algorithm, _extractable, _keyUsages) {
const cipher = new DESECBCipher;
cipher._importKey(key);
return cipher;
}
_importKey(key, _extractable, _keyUsages) {
this._cipher = new DES(key);
}
encrypt(_algorithm, plaintext) {
const x = new Uint8Array(plaintext);
if (x.length % 8 !== 0 || this._cipher === null) {
return null;
}
const n = x.length / 8;
for (let i = 0; i < n; i++) {
x.set(this._cipher.enc8(x.slice(i * 8, i * 8 + 8)), i * 8);
}
return x;
}
}
export class DESCBCCipher {
constructor() {
this._cipher = null;
}
get algorithm() {
return { name: "DES-CBC" };
}
static importKey(key, _algorithm, _extractable, _keyUsages) {
const cipher = new DESCBCCipher;
cipher._importKey(key);
return cipher;
}
_importKey(key) {
this._cipher = new DES(key);
}
encrypt(algorithm, plaintext) {
const x = new Uint8Array(plaintext);
let y = new Uint8Array(algorithm.iv);
if (x.length % 8 !== 0 || this._cipher === null) {
return null;
}
const n = x.length / 8;
for (let i = 0; i < n; i++) {
for (let j = 0; j < 8; j++) {
y[j] ^= plaintext[i * 8 + j];
}
y = this._cipher.enc8(y);
x.set(y, i * 8);
}
return x;
} }
} }
+55
View File
@@ -0,0 +1,55 @@
import { modPow, bigIntToU8Array, u8ArrayToBigInt } from "./bigint.js";
class DHPublicKey {
constructor(key) {
this._key = key;
}
get algorithm() {
return { name: "DH" };
}
exportKey() {
return this._key;
}
}
export class DHCipher {
constructor() {
this._g = null;
this._p = null;
this._gBigInt = null;
this._pBigInt = null;
this._privateKey = null;
}
get algorithm() {
return { name: "DH" };
}
static generateKey(algorithm, _extractable) {
const cipher = new DHCipher;
cipher._generateKey(algorithm);
return { privateKey: cipher, publicKey: new DHPublicKey(cipher._publicKey) };
}
_generateKey(algorithm) {
const g = algorithm.g;
const p = algorithm.p;
this._keyBytes = p.length;
this._gBigInt = u8ArrayToBigInt(g);
this._pBigInt = u8ArrayToBigInt(p);
this._privateKey = window.crypto.getRandomValues(new Uint8Array(this._keyBytes));
this._privateKeyBigInt = u8ArrayToBigInt(this._privateKey);
this._publicKey = bigIntToU8Array(modPow(
this._gBigInt, this._privateKeyBigInt, this._pBigInt), this._keyBytes);
}
deriveBits(algorithm, length) {
const bytes = Math.ceil(length / 8);
const pkey = new Uint8Array(algorithm.public);
const len = bytes > this._keyBytes ? bytes : this._keyBytes;
const secret = modPow(u8ArrayToBigInt(pkey), this._privateKeyBigInt, this._pBigInt);
return bigIntToU8Array(secret, len).slice(0, len);
}
}
+7 -4
View File
@@ -7,12 +7,15 @@
*/ */
/* /*
* Performs MD5 hashing on a string of binary characters, returns an array of bytes * Performs MD5 hashing on an array of bytes, returns an array of bytes
*/ */
export function MD5(d) { export async function MD5(d) {
let r = M(V(Y(X(d), 8 * d.length))); let s = "";
return r; for (let i = 0; i < d.length; i++) {
s += String.fromCharCode(d[i]);
}
return M(V(Y(X(s), 8 * s.length)));
} }
function M(d) { function M(d) {
+132
View File
@@ -0,0 +1,132 @@
import Base64 from "../base64.js";
import { modPow, bigIntToU8Array, u8ArrayToBigInt } from "./bigint.js";
export class RSACipher {
constructor() {
this._keyLength = 0;
this._keyBytes = 0;
this._n = null;
this._e = null;
this._d = null;
this._nBigInt = null;
this._eBigInt = null;
this._dBigInt = null;
this._extractable = false;
}
get algorithm() {
return { name: "RSA-PKCS1-v1_5" };
}
_base64urlDecode(data) {
data = data.replace(/-/g, "+").replace(/_/g, "/");
data = data.padEnd(Math.ceil(data.length / 4) * 4, "=");
return Base64.decode(data);
}
_padArray(arr, length) {
const res = new Uint8Array(length);
res.set(arr, length - arr.length);
return res;
}
static async generateKey(algorithm, extractable, _keyUsages) {
const cipher = new RSACipher;
await cipher._generateKey(algorithm, extractable);
return { privateKey: cipher };
}
async _generateKey(algorithm, extractable) {
this._keyLength = algorithm.modulusLength;
this._keyBytes = Math.ceil(this._keyLength / 8);
const key = await window.crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: algorithm.modulusLength,
publicExponent: algorithm.publicExponent,
hash: {name: "SHA-256"},
},
true, ["encrypt", "decrypt"]);
const privateKey = await window.crypto.subtle.exportKey("jwk", key.privateKey);
this._n = this._padArray(this._base64urlDecode(privateKey.n), this._keyBytes);
this._nBigInt = u8ArrayToBigInt(this._n);
this._e = this._padArray(this._base64urlDecode(privateKey.e), this._keyBytes);
this._eBigInt = u8ArrayToBigInt(this._e);
this._d = this._padArray(this._base64urlDecode(privateKey.d), this._keyBytes);
this._dBigInt = u8ArrayToBigInt(this._d);
this._extractable = extractable;
}
static async importKey(key, _algorithm, extractable, keyUsages) {
if (keyUsages.length !== 1 || keyUsages[0] !== "encrypt") {
throw new Error("only support importing RSA public key");
}
const cipher = new RSACipher;
await cipher._importKey(key, extractable);
return cipher;
}
async _importKey(key, extractable) {
const n = key.n;
const e = key.e;
if (n.length !== e.length) {
throw new Error("the sizes of modulus and public exponent do not match");
}
this._keyBytes = n.length;
this._keyLength = this._keyBytes * 8;
this._n = new Uint8Array(this._keyBytes);
this._e = new Uint8Array(this._keyBytes);
this._n.set(n);
this._e.set(e);
this._nBigInt = u8ArrayToBigInt(this._n);
this._eBigInt = u8ArrayToBigInt(this._e);
this._extractable = extractable;
}
async encrypt(_algorithm, message) {
if (message.length > this._keyBytes - 11) {
return null;
}
const ps = new Uint8Array(this._keyBytes - message.length - 3);
window.crypto.getRandomValues(ps);
for (let i = 0; i < ps.length; i++) {
ps[i] = Math.floor(ps[i] * 254 / 255 + 1);
}
const em = new Uint8Array(this._keyBytes);
em[1] = 0x02;
em.set(ps, 2);
em.set(message, ps.length + 3);
const emBigInt = u8ArrayToBigInt(em);
const c = modPow(emBigInt, this._eBigInt, this._nBigInt);
return bigIntToU8Array(c, this._keyBytes);
}
async decrypt(_algorithm, message) {
if (message.length !== this._keyBytes) {
return null;
}
const msgBigInt = u8ArrayToBigInt(message);
const emBigInt = modPow(msgBigInt, this._dBigInt, this._nBigInt);
const em = bigIntToU8Array(emBigInt, this._keyBytes);
if (em[0] !== 0x00 || em[1] !== 0x02) {
return null;
}
let i = 2;
for (; i < em.length; i++) {
if (em[i] === 0x00) {
break;
}
}
if (i === em.length) {
return null;
}
return em.slice(i + 1, em.length);
}
async exportKey() {
if (!this._extractable) {
throw new Error("key is not extractable");
}
return { n: this._n, e: this._e, d: this._d };
}
}
+12 -22
View File
@@ -31,10 +31,7 @@ export default class HextileDecoder {
return false; return false;
} }
let rQ = sock.rQ; let subencoding = sock.rQpeek8();
let rQi = sock.rQi;
let subencoding = rQ[rQi]; // Peek
if (subencoding > 30) { // Raw if (subencoding > 30) { // Raw
throw new Error("Illegal hextile subencoding (subencoding: " + throw new Error("Illegal hextile subencoding (subencoding: " +
subencoding + ")"); subencoding + ")");
@@ -65,7 +62,7 @@ export default class HextileDecoder {
return false; return false;
} }
let subrects = rQ[rQi + bytes - 1]; // Peek let subrects = sock.rQpeekBytes(bytes).at(-1);
if (subencoding & 0x10) { // SubrectsColoured if (subencoding & 0x10) { // SubrectsColoured
bytes += subrects * (4 + 2); bytes += subrects * (4 + 2);
} else { } else {
@@ -79,7 +76,7 @@ export default class HextileDecoder {
} }
// We know the encoding and have a whole tile // We know the encoding and have a whole tile
rQi++; sock.rQshift8();
if (subencoding === 0) { if (subencoding === 0) {
if (this._lastsubencoding & 0x01) { if (this._lastsubencoding & 0x01) {
// Weird: ignore blanks are RAW // Weird: ignore blanks are RAW
@@ -89,42 +86,36 @@ export default class HextileDecoder {
} }
} else if (subencoding & 0x01) { // Raw } else if (subencoding & 0x01) { // Raw
let pixels = tw * th; let pixels = tw * th;
let data = sock.rQshiftBytes(pixels * 4, false);
// Max sure the image is fully opaque // Max sure the image is fully opaque
for (let i = 0;i < pixels;i++) { for (let i = 0;i < pixels;i++) {
rQ[rQi + i * 4 + 3] = 255; data[i * 4 + 3] = 255;
} }
display.blitImage(tx, ty, tw, th, rQ, rQi); display.blitImage(tx, ty, tw, th, data, 0);
rQi += bytes - 1;
} else { } else {
if (subencoding & 0x02) { // Background if (subencoding & 0x02) { // Background
this._background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]]; this._background = new Uint8Array(sock.rQshiftBytes(4));
rQi += 4;
} }
if (subencoding & 0x04) { // Foreground if (subencoding & 0x04) { // Foreground
this._foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]]; this._foreground = new Uint8Array(sock.rQshiftBytes(4));
rQi += 4;
} }
this._startTile(tx, ty, tw, th, this._background); this._startTile(tx, ty, tw, th, this._background);
if (subencoding & 0x08) { // AnySubrects if (subencoding & 0x08) { // AnySubrects
let subrects = rQ[rQi]; let subrects = sock.rQshift8();
rQi++;
for (let s = 0; s < subrects; s++) { for (let s = 0; s < subrects; s++) {
let color; let color;
if (subencoding & 0x10) { // SubrectsColoured if (subencoding & 0x10) { // SubrectsColoured
color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]]; color = sock.rQshiftBytes(4);
rQi += 4;
} else { } else {
color = this._foreground; color = this._foreground;
} }
const xy = rQ[rQi]; const xy = sock.rQshift8();
rQi++;
const sx = (xy >> 4); const sx = (xy >> 4);
const sy = (xy & 0x0f); const sy = (xy & 0x0f);
const wh = rQ[rQi]; const wh = sock.rQshift8();
rQi++;
const sw = (wh >> 4) + 1; const sw = (wh >> 4) + 1;
const sh = (wh & 0x0f) + 1; const sh = (wh & 0x0f) + 1;
@@ -133,7 +124,6 @@ export default class HextileDecoder {
} }
this._finishTile(display); this._finishTile(display);
} }
sock.rQi = rQi;
this._lastsubencoding = subencoding; this._lastsubencoding = subencoding;
this._tiles--; this._tiles--;
} }
+118 -113
View File
@@ -11,131 +11,136 @@ export default class JPEGDecoder {
constructor() { constructor() {
// RealVNC will reuse the quantization tables // RealVNC will reuse the quantization tables
// and Huffman tables, so we need to cache them. // and Huffman tables, so we need to cache them.
this._quantTables = [];
this._huffmanTables = [];
this._cachedQuantTables = []; this._cachedQuantTables = [];
this._cachedHuffmanTables = []; this._cachedHuffmanTables = [];
this._jpegLength = 0;
this._segments = []; this._segments = [];
} }
decodeRect(x, y, width, height, sock, display, depth) { decodeRect(x, y, width, height, sock, display, depth) {
// A rect of JPEG encodings is simply a JPEG file // A rect of JPEG encodings is simply a JPEG file
if (!this._parseJPEG(sock.rQslice(0))) {
return false;
}
const data = sock.rQshiftBytes(this._jpegLength);
if (this._quantTables.length != 0 && this._huffmanTables.length != 0) {
// If there are quantization tables and Huffman tables in the JPEG
// image, we can directly render it.
display.imageRect(x, y, width, height, "image/jpeg", data);
return true;
} else {
// Otherwise we need to insert cached tables.
const sofIndex = this._segments.findIndex(
x => x[1] == 0xC0 || x[1] == 0xC2
);
if (sofIndex == -1) {
throw new Error("Illegal JPEG image without SOF");
}
let segments = this._segments.slice(0, sofIndex);
segments = segments.concat(this._quantTables.length ?
this._quantTables :
this._cachedQuantTables);
segments.push(this._segments[sofIndex]);
segments = segments.concat(this._huffmanTables.length ?
this._huffmanTables :
this._cachedHuffmanTables,
this._segments.slice(sofIndex + 1));
let length = 0;
for (let i = 0; i < segments.length; i++) {
length += segments[i].length;
}
const data = new Uint8Array(length);
length = 0;
for (let i = 0; i < segments.length; i++) {
data.set(segments[i], length);
length += segments[i].length;
}
display.imageRect(x, y, width, height, "image/jpeg", data);
return true;
}
}
_parseJPEG(buffer) {
if (this._quantTables.length != 0) {
this._cachedQuantTables = this._quantTables;
}
if (this._huffmanTables.length != 0) {
this._cachedHuffmanTables = this._huffmanTables;
}
this._quantTables = [];
this._huffmanTables = [];
this._segments = [];
let i = 0;
let bufferLength = buffer.length;
while (true) { while (true) {
let j = i; let segment = this._readSegment(sock);
if (j + 2 > bufferLength) { if (segment === null) {
return false; return false;
} }
if (buffer[j] != 0xFF) {
throw new Error("Illegal JPEG marker received (byte: " +
buffer[j] + ")");
}
const type = buffer[j+1];
j += 2;
if (type == 0xD9) {
this._jpegLength = j;
this._segments.push(buffer.slice(i, j));
return true;
} else if (type == 0xDA) {
// start of scan
let hasFoundEndOfScan = false;
for (let k = j + 3; k + 1 < bufferLength; k++) {
if (buffer[k] == 0xFF && buffer[k+1] != 0x00 &&
!(buffer[k+1] >= 0xD0 && buffer[k+1] <= 0xD7)) {
j = k;
hasFoundEndOfScan = true;
break;
}
}
if (!hasFoundEndOfScan) {
return false;
}
this._segments.push(buffer.slice(i, j));
i = j;
continue;
} else if (type >= 0xD0 && type < 0xD9 || type == 0x01) {
// No length after marker
this._segments.push(buffer.slice(i, j));
i = j;
continue;
}
if (j + 2 > bufferLength) {
return false;
}
const length = (buffer[j] << 8) + buffer[j+1] - 2;
if (length < 0) {
throw new Error("Illegal JPEG length received (length: " +
length + ")");
}
j += 2;
if (j + length > bufferLength) {
return false;
}
j += length;
const segment = buffer.slice(i, j);
if (type == 0xC4) {
// Huffman tables
this._huffmanTables.push(segment);
} else if (type == 0xDB) {
// Quantization tables
this._quantTables.push(segment);
}
this._segments.push(segment); this._segments.push(segment);
i = j; // End of image?
if (segment[1] === 0xD9) {
break;
}
} }
let huffmanTables = [];
let quantTables = [];
for (let segment of this._segments) {
let type = segment[1];
if (type === 0xC4) {
// Huffman tables
huffmanTables.push(segment);
} else if (type === 0xDB) {
// Quantization tables
quantTables.push(segment);
}
}
const sofIndex = this._segments.findIndex(
x => x[1] == 0xC0 || x[1] == 0xC2
);
if (sofIndex == -1) {
throw new Error("Illegal JPEG image without SOF");
}
if (quantTables.length === 0) {
this._segments.splice(sofIndex+1, 0,
...this._cachedQuantTables);
}
if (huffmanTables.length === 0) {
this._segments.splice(sofIndex+1, 0,
...this._cachedHuffmanTables);
}
let length = 0;
for (let segment of this._segments) {
length += segment.length;
}
let data = new Uint8Array(length);
length = 0;
for (let segment of this._segments) {
data.set(segment, length);
length += segment.length;
}
display.imageRect(x, y, width, height, "image/jpeg", data);
if (huffmanTables.length !== 0) {
this._cachedHuffmanTables = huffmanTables;
}
if (quantTables.length !== 0) {
this._cachedQuantTables = quantTables;
}
this._segments = [];
return true;
}
_readSegment(sock) {
if (sock.rQwait("JPEG", 2)) {
return null;
}
let marker = sock.rQshift8();
if (marker != 0xFF) {
throw new Error("Illegal JPEG marker received (byte: " +
marker + ")");
}
let type = sock.rQshift8();
if (type >= 0xD0 && type <= 0xD9 || type == 0x01) {
// No length after marker
return new Uint8Array([marker, type]);
}
if (sock.rQwait("JPEG", 2, 2)) {
return null;
}
let length = sock.rQshift16();
if (length < 2) {
throw new Error("Illegal JPEG length received (length: " +
length + ")");
}
if (sock.rQwait("JPEG", length-2, 4)) {
return null;
}
let extra = 0;
if (type === 0xDA) {
// start of scan
extra += 2;
while (true) {
if (sock.rQwait("JPEG", length-2+extra, 4)) {
return null;
}
let data = sock.rQpeekBytes(length-2+extra, false);
if (data.at(-2) === 0xFF && data.at(-1) !== 0x00 &&
!(data.at(-1) >= 0xD0 && data.at(-1) <= 0xD7)) {
extra -= 2;
break;
}
extra++;
}
}
let segment = new Uint8Array(2 + length + extra);
segment[0] = marker;
segment[1] = type;
segment[2] = length >> 8;
segment[3] = length;
segment.set(sock.rQshiftBytes(length-2+extra, false), 4);
return segment;
} }
} }
+25 -32
View File
@@ -24,41 +24,34 @@ export default class RawDecoder {
const pixelSize = depth == 8 ? 1 : 4; const pixelSize = depth == 8 ? 1 : 4;
const bytesPerLine = width * pixelSize; const bytesPerLine = width * pixelSize;
if (sock.rQwait("RAW", bytesPerLine)) { while (this._lines > 0) {
return false; if (sock.rQwait("RAW", bytesPerLine)) {
} return false;
const curY = y + (height - this._lines);
const currHeight = Math.min(this._lines,
Math.floor(sock.rQlen / bytesPerLine));
const pixels = width * currHeight;
let data = sock.rQ;
let index = sock.rQi;
// Convert data if needed
if (depth == 8) {
const newdata = new Uint8Array(pixels * 4);
for (let i = 0; i < pixels; i++) {
newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3;
newdata[i * 4 + 1] = ((data[index + i] >> 2) & 0x3) * 255 / 3;
newdata[i * 4 + 2] = ((data[index + i] >> 4) & 0x3) * 255 / 3;
newdata[i * 4 + 3] = 255;
} }
data = newdata;
index = 0;
}
// Max sure the image is fully opaque const curY = y + (height - this._lines);
for (let i = 0; i < pixels; i++) {
data[index + i * 4 + 3] = 255;
}
display.blitImage(x, curY, width, currHeight, data, index); let data = sock.rQshiftBytes(bytesPerLine, false);
sock.rQskipBytes(currHeight * bytesPerLine);
this._lines -= currHeight; // Convert data if needed
if (this._lines > 0) { if (depth == 8) {
return false; const newdata = new Uint8Array(width * 4);
for (let i = 0; i < width; i++) {
newdata[i * 4 + 0] = ((data[i] >> 0) & 0x3) * 255 / 3;
newdata[i * 4 + 1] = ((data[i] >> 2) & 0x3) * 255 / 3;
newdata[i * 4 + 2] = ((data[i] >> 4) & 0x3) * 255 / 3;
newdata[i * 4 + 3] = 255;
}
data = newdata;
}
// Max sure the image is fully opaque
for (let i = 0; i < width; i++) {
data[i * 4 + 3] = 255;
}
display.blitImage(x, curY, width, 1, data, 0);
this._lines--;
} }
return true; return true;
+3 -7
View File
@@ -76,12 +76,8 @@ export default class TightDecoder {
return false; return false;
} }
const rQi = sock.rQi; let pixel = sock.rQshiftBytes(3);
const rQ = sock.rQ; display.fillRect(x, y, width, height, pixel, false);
display.fillRect(x, y, width, height,
[rQ[rQi], rQ[rQi + 1], rQ[rQi + 2]], false);
sock.rQskipBytes(3);
return true; return true;
} }
@@ -316,7 +312,7 @@ export default class TightDecoder {
return null; return null;
} }
let data = sock.rQshiftBytes(this._len); let data = sock.rQshiftBytes(this._len, false);
this._len = 0; this._len = 0;
return data; return data;
+1 -1
View File
@@ -32,7 +32,7 @@ export default class ZRLEDecoder {
return false; return false;
} }
const data = sock.rQshiftBytes(this._length); const data = sock.rQshiftBytes(this._length, false);
this._inflator.setInput(data); this._inflator.setInput(data);
+2 -3
View File
@@ -7,7 +7,7 @@
*/ */
import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js"; import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js";
import { Z_FULL_FLUSH } from "../vendor/pako/lib/zlib/deflate.js"; import { Z_FULL_FLUSH, Z_DEFAULT_COMPRESSION } from "../vendor/pako/lib/zlib/deflate.js";
import ZStream from "../vendor/pako/lib/zlib/zstream.js"; import ZStream from "../vendor/pako/lib/zlib/zstream.js";
export default class Deflator { export default class Deflator {
@@ -15,9 +15,8 @@ export default class Deflator {
this.strm = new ZStream(); this.strm = new ZStream();
this.chunkSize = 1024 * 10 * 10; this.chunkSize = 1024 * 10 * 10;
this.outputBuffer = new Uint8Array(this.chunkSize); this.outputBuffer = new Uint8Array(this.chunkSize);
this.windowBits = 5;
deflateInit(this.strm, this.windowBits); deflateInit(this.strm, Z_DEFAULT_COMPRESSION);
} }
deflate(inData) { deflate(inData) {
+13 -10
View File
@@ -15,7 +15,7 @@ export default class Display {
this._drawCtx = null; this._drawCtx = null;
this._renderQ = []; // queue drawing actions for in-oder rendering this._renderQ = []; // queue drawing actions for in-oder rendering
this._flushing = false; this._flushPromise = null;
// the full frame buffer (logical canvas) size // the full frame buffer (logical canvas) size
this._fbWidth = 0; this._fbWidth = 0;
@@ -61,10 +61,6 @@ export default class Display {
this._scale = 1.0; this._scale = 1.0;
this._clipViewport = false; this._clipViewport = false;
// ===== EVENT HANDLERS =====
this.onflush = () => {}; // A flush request has finished
} }
// ===== PROPERTIES ===== // ===== PROPERTIES =====
@@ -306,9 +302,14 @@ export default class Display {
flush() { flush() {
if (this._renderQ.length === 0) { if (this._renderQ.length === 0) {
this.onflush(); return Promise.resolve();
} else { } else {
this._flushing = true; if (this._flushPromise === null) {
this._flushPromise = new Promise((resolve) => {
this._flushResolve = resolve;
});
}
return this._flushPromise;
} }
} }
@@ -517,9 +518,11 @@ export default class Display {
} }
} }
if (this._renderQ.length === 0 && this._flushing) { if (this._renderQ.length === 0 &&
this._flushing = false; this._flushPromise !== null) {
this.onflush(); this._flushResolve();
this._flushPromise = null;
this._flushResolve = null;
} }
} }
} }
+1
View File
@@ -22,6 +22,7 @@ export const encodings = {
pseudoEncodingLastRect: -224, pseudoEncodingLastRect: -224,
pseudoEncodingCursor: -239, pseudoEncodingCursor: -239,
pseudoEncodingQEMUExtendedKeyEvent: -258, pseudoEncodingQEMUExtendedKeyEvent: -258,
pseudoEncodingQEMULedEvent: -261,
pseudoEncodingDesktopName: -307, pseudoEncodingDesktopName: -307,
pseudoEncodingExtendedDesktopSize: -308, pseudoEncodingExtendedDesktopSize: -308,
pseudoEncodingXvp: -309, pseudoEncodingXvp: -309,
+1 -2
View File
@@ -14,9 +14,8 @@ export default class Inflate {
this.strm = new ZStream(); this.strm = new ZStream();
this.chunkSize = 1024 * 10 * 10; this.chunkSize = 1024 * 10 * 10;
this.strm.output = new Uint8Array(this.chunkSize); this.strm.output = new Uint8Array(this.chunkSize);
this.windowBits = 5;
inflateInit(this.strm, this.windowBits); inflateInit(this.strm);
} }
setInput(data) { setInput(data) {
+21 -13
View File
@@ -36,7 +36,7 @@ export default class Keyboard {
// ===== PRIVATE METHODS ===== // ===== PRIVATE METHODS =====
_sendKeyEvent(keysym, code, down) { _sendKeyEvent(keysym, code, down, numlock = null, capslock = null) {
if (down) { if (down) {
this._keyDownList[code] = keysym; this._keyDownList[code] = keysym;
} else { } else {
@@ -48,8 +48,8 @@ export default class Keyboard {
} }
Log.Debug("onkeyevent " + (down ? "down" : "up") + Log.Debug("onkeyevent " + (down ? "down" : "up") +
", keysym: " + keysym, ", code: " + code); ", keysym: " + keysym, ", code: " + code, + ", numlock: " + numlock + ", capslock: " + capslock);
this.onkeyevent(keysym, code, down); this.onkeyevent(keysym, code, down, numlock, capslock);
} }
_getKeyCode(e) { _getKeyCode(e) {
@@ -86,6 +86,14 @@ export default class Keyboard {
_handleKeyDown(e) { _handleKeyDown(e) {
const code = this._getKeyCode(e); const code = this._getKeyCode(e);
let keysym = KeyboardUtil.getKeysym(e); let keysym = KeyboardUtil.getKeysym(e);
let numlock = e.getModifierState('NumLock');
let capslock = e.getModifierState('CapsLock');
// getModifierState for NumLock is not supported on mac and ios and always returns false.
// Set to null to indicate unknown/unsupported instead.
if (browser.isMac() || browser.isIOS()) {
numlock = null;
}
// Windows doesn't have a proper AltGr, but handles it using // Windows doesn't have a proper AltGr, but handles it using
// fake Ctrl+Alt. However the remote end might not be Windows, // fake Ctrl+Alt. However the remote end might not be Windows,
@@ -107,7 +115,7 @@ export default class Keyboard {
// key to "AltGraph". // key to "AltGraph".
keysym = KeyTable.XK_ISO_Level3_Shift; keysym = KeyTable.XK_ISO_Level3_Shift;
} else { } else {
this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true); this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true, numlock, capslock);
} }
} }
@@ -118,8 +126,8 @@ export default class Keyboard {
// If it's a virtual keyboard then it should be // If it's a virtual keyboard then it should be
// sufficient to just send press and release right // sufficient to just send press and release right
// after each other // after each other
this._sendKeyEvent(keysym, code, true); this._sendKeyEvent(keysym, code, true, numlock, capslock);
this._sendKeyEvent(keysym, code, false); this._sendKeyEvent(keysym, code, false, numlock, capslock);
} }
stopEvent(e); stopEvent(e);
@@ -157,8 +165,8 @@ export default class Keyboard {
// while meta is held down // while meta is held down
if ((browser.isMac() || browser.isIOS()) && if ((browser.isMac() || browser.isIOS()) &&
(e.metaKey && code !== 'MetaLeft' && code !== 'MetaRight')) { (e.metaKey && code !== 'MetaLeft' && code !== 'MetaRight')) {
this._sendKeyEvent(keysym, code, true); this._sendKeyEvent(keysym, code, true, numlock, capslock);
this._sendKeyEvent(keysym, code, false); this._sendKeyEvent(keysym, code, false, numlock, capslock);
stopEvent(e); stopEvent(e);
return; return;
} }
@@ -168,8 +176,8 @@ export default class Keyboard {
// which toggles on each press, but not on release. So pretend // which toggles on each press, but not on release. So pretend
// it was a quick press and release of the button. // it was a quick press and release of the button.
if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) { if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) {
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true); this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true, numlock, capslock);
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false); this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false, numlock, capslock);
stopEvent(e); stopEvent(e);
return; return;
} }
@@ -182,8 +190,8 @@ export default class Keyboard {
KeyTable.XK_Hiragana, KeyTable.XK_Hiragana,
KeyTable.XK_Romaji ]; KeyTable.XK_Romaji ];
if (browser.isWindows() && jpBadKeys.includes(keysym)) { if (browser.isWindows() && jpBadKeys.includes(keysym)) {
this._sendKeyEvent(keysym, code, true); this._sendKeyEvent(keysym, code, true, numlock, capslock);
this._sendKeyEvent(keysym, code, false); this._sendKeyEvent(keysym, code, false, numlock, capslock);
stopEvent(e); stopEvent(e);
return; return;
} }
@@ -199,7 +207,7 @@ export default class Keyboard {
return; return;
} }
this._sendKeyEvent(keysym, code, true); this._sendKeyEvent(keysym, code, true, numlock, capslock);
} }
_handleKeyUp(e) { _handleKeyUp(e) {
+45 -300
View File
@@ -1,146 +1,25 @@
import Base64 from './base64.js';
import { encodeUTF8 } from './util/strings.js'; import { encodeUTF8 } from './util/strings.js';
import EventTargetMixin from './util/eventtarget.js'; import EventTargetMixin from './util/eventtarget.js';
import legacyCrypto from './crypto/crypto.js';
export class AESEAXCipher { class RA2Cipher {
constructor() { constructor() {
this._rawKey = null; this._cipher = null;
this._ctrKey = null;
this._cbcKey = null;
this._zeroBlock = new Uint8Array(16);
this._prefixBlock0 = this._zeroBlock;
this._prefixBlock1 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
this._prefixBlock2 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
}
async _encryptBlock(block) {
const encrypted = await window.crypto.subtle.encrypt({
name: "AES-CBC",
iv: this._zeroBlock,
}, this._cbcKey, block);
return new Uint8Array(encrypted).slice(0, 16);
}
async _initCMAC() {
const k1 = await this._encryptBlock(this._zeroBlock);
const k2 = new Uint8Array(16);
const v = k1[0] >>> 6;
for (let i = 0; i < 15; i++) {
k2[i] = (k1[i + 1] >> 6) | (k1[i] << 2);
k1[i] = (k1[i + 1] >> 7) | (k1[i] << 1);
}
const lut = [0x0, 0x87, 0x0e, 0x89];
k2[14] ^= v >>> 1;
k2[15] = (k1[15] << 2) ^ lut[v];
k1[15] = (k1[15] << 1) ^ lut[v >> 1];
this._k1 = k1;
this._k2 = k2;
}
async _encryptCTR(data, counter) {
const encrypted = await window.crypto.subtle.encrypt({
"name": "AES-CTR",
counter: counter,
length: 128
}, this._ctrKey, data);
return new Uint8Array(encrypted);
}
async _decryptCTR(data, counter) {
const decrypted = await window.crypto.subtle.decrypt({
"name": "AES-CTR",
counter: counter,
length: 128
}, this._ctrKey, data);
return new Uint8Array(decrypted);
}
async _computeCMAC(data, prefixBlock) {
if (prefixBlock.length !== 16) {
return null;
}
const n = Math.floor(data.length / 16);
const m = Math.ceil(data.length / 16);
const r = data.length - n * 16;
const cbcData = new Uint8Array((m + 1) * 16);
cbcData.set(prefixBlock);
cbcData.set(data, 16);
if (r === 0) {
for (let i = 0; i < 16; i++) {
cbcData[n * 16 + i] ^= this._k1[i];
}
} else {
cbcData[(n + 1) * 16 + r] = 0x80;
for (let i = 0; i < 16; i++) {
cbcData[(n + 1) * 16 + i] ^= this._k2[i];
}
}
let cbcEncrypted = await window.crypto.subtle.encrypt({
name: "AES-CBC",
iv: this._zeroBlock,
}, this._cbcKey, cbcData);
cbcEncrypted = new Uint8Array(cbcEncrypted);
const mac = cbcEncrypted.slice(cbcEncrypted.length - 32, cbcEncrypted.length - 16);
return mac;
}
async setKey(key) {
this._rawKey = key;
this._ctrKey = await window.crypto.subtle.importKey(
"raw", key, {"name": "AES-CTR"}, false, ["encrypt", "decrypt"]);
this._cbcKey = await window.crypto.subtle.importKey(
"raw", key, {"name": "AES-CBC"}, false, ["encrypt", "decrypt"]);
await this._initCMAC();
}
async encrypt(message, associatedData, nonce) {
const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
const encrypted = await this._encryptCTR(message, nCMAC);
const adCMAC = await this._computeCMAC(associatedData, this._prefixBlock1);
const mac = await this._computeCMAC(encrypted, this._prefixBlock2);
for (let i = 0; i < 16; i++) {
mac[i] ^= nCMAC[i] ^ adCMAC[i];
}
const res = new Uint8Array(16 + encrypted.length);
res.set(encrypted);
res.set(mac, encrypted.length);
return res;
}
async decrypt(encrypted, associatedData, nonce, mac) {
const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
const adCMAC = await this._computeCMAC(associatedData, this._prefixBlock1);
const computedMac = await this._computeCMAC(encrypted, this._prefixBlock2);
for (let i = 0; i < 16; i++) {
computedMac[i] ^= nCMAC[i] ^ adCMAC[i];
}
if (computedMac.length !== mac.length) {
return null;
}
for (let i = 0; i < mac.length; i++) {
if (computedMac[i] !== mac[i]) {
return null;
}
}
const res = await this._decryptCTR(encrypted, nCMAC);
return res;
}
}
export class RA2Cipher {
constructor() {
this._cipher = new AESEAXCipher();
this._counter = new Uint8Array(16); this._counter = new Uint8Array(16);
} }
async setKey(key) { async setKey(key) {
await this._cipher.setKey(key); this._cipher = await legacyCrypto.importKey(
"raw", key, { name: "AES-EAX" }, false, ["encrypt, decrypt"]);
} }
async makeMessage(message) { async makeMessage(message) {
const ad = new Uint8Array([(message.length & 0xff00) >>> 8, message.length & 0xff]); const ad = new Uint8Array([(message.length & 0xff00) >>> 8, message.length & 0xff]);
const encrypted = await this._cipher.encrypt(message, ad, this._counter); const encrypted = await legacyCrypto.encrypt({
name: "AES-EAX",
iv: this._counter,
additionalData: ad,
}, this._cipher, message);
for (let i = 0; i < 16 && this._counter[i]++ === 255; i++); for (let i = 0; i < 16 && this._counter[i]++ === 255; i++);
const res = new Uint8Array(message.length + 2 + 16); const res = new Uint8Array(message.length + 2 + 16);
res.set(ad); res.set(ad);
@@ -148,164 +27,18 @@ export class RA2Cipher {
return res; return res;
} }
async receiveMessage(length, encrypted, mac) { async receiveMessage(length, encrypted) {
const ad = new Uint8Array([(length & 0xff00) >>> 8, length & 0xff]); const ad = new Uint8Array([(length & 0xff00) >>> 8, length & 0xff]);
const res = await this._cipher.decrypt(encrypted, ad, this._counter, mac); const res = await legacyCrypto.decrypt({
name: "AES-EAX",
iv: this._counter,
additionalData: ad,
}, this._cipher, encrypted);
for (let i = 0; i < 16 && this._counter[i]++ === 255; i++); for (let i = 0; i < 16 && this._counter[i]++ === 255; i++);
return res; return res;
} }
} }
export class RSACipher {
constructor(keyLength) {
this._key = null;
this._keyLength = keyLength;
this._keyBytes = Math.ceil(keyLength / 8);
this._n = null;
this._e = null;
this._d = null;
this._nBigInt = null;
this._eBigInt = null;
this._dBigInt = null;
}
_base64urlDecode(data) {
data = data.replace(/-/g, "+").replace(/_/g, "/");
data = data.padEnd(Math.ceil(data.length / 4) * 4, "=");
return Base64.decode(data);
}
_u8ArrayToBigInt(arr) {
let hex = '0x';
for (let i = 0; i < arr.length; i++) {
hex += arr[i].toString(16).padStart(2, '0');
}
return BigInt(hex);
}
_padArray(arr, length) {
const res = new Uint8Array(length);
res.set(arr, length - arr.length);
return res;
}
_bigIntToU8Array(bigint, padLength=0) {
let hex = bigint.toString(16);
if (padLength === 0) {
padLength = Math.ceil(hex.length / 2) * 2;
}
hex = hex.padStart(padLength * 2, '0');
const length = hex.length / 2;
const arr = new Uint8Array(length);
for (let i = 0; i < length; i++) {
arr[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
}
return arr;
}
_modPow(b, e, m) {
if (m === 1n) {
return 0;
}
let r = 1n;
b = b % m;
while (e > 0) {
if (e % 2n === 1n) {
r = (r * b) % m;
}
e = e / 2n;
b = (b * b) % m;
}
return r;
}
async generateKey() {
this._key = await window.crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: this._keyLength,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"},
},
true, ["encrypt", "decrypt"]);
const privateKey = await window.crypto.subtle.exportKey("jwk", this._key.privateKey);
this._n = this._padArray(this._base64urlDecode(privateKey.n), this._keyBytes);
this._nBigInt = this._u8ArrayToBigInt(this._n);
this._e = this._padArray(this._base64urlDecode(privateKey.e), this._keyBytes);
this._eBigInt = this._u8ArrayToBigInt(this._e);
this._d = this._padArray(this._base64urlDecode(privateKey.d), this._keyBytes);
this._dBigInt = this._u8ArrayToBigInt(this._d);
}
setPublicKey(n, e) {
if (n.length !== this._keyBytes || e.length !== this._keyBytes) {
return;
}
this._n = new Uint8Array(this._keyBytes);
this._e = new Uint8Array(this._keyBytes);
this._n.set(n);
this._e.set(e);
this._nBigInt = this._u8ArrayToBigInt(this._n);
this._eBigInt = this._u8ArrayToBigInt(this._e);
}
encrypt(message) {
if (message.length > this._keyBytes - 11) {
return null;
}
const ps = new Uint8Array(this._keyBytes - message.length - 3);
window.crypto.getRandomValues(ps);
for (let i = 0; i < ps.length; i++) {
ps[i] = Math.floor(ps[i] * 254 / 255 + 1);
}
const em = new Uint8Array(this._keyBytes);
em[1] = 0x02;
em.set(ps, 2);
em.set(message, ps.length + 3);
const emBigInt = this._u8ArrayToBigInt(em);
const c = this._modPow(emBigInt, this._eBigInt, this._nBigInt);
return this._bigIntToU8Array(c, this._keyBytes);
}
decrypt(message) {
if (message.length !== this._keyBytes) {
return null;
}
const msgBigInt = this._u8ArrayToBigInt(message);
const emBigInt = this._modPow(msgBigInt, this._dBigInt, this._nBigInt);
const em = this._bigIntToU8Array(emBigInt, this._keyBytes);
if (em[0] !== 0x00 || em[1] !== 0x02) {
return null;
}
let i = 2;
for (; i < em.length; i++) {
if (em[i] === 0x00) {
break;
}
}
if (i === em.length) {
return null;
}
return em.slice(i + 1, em.length);
}
get keyLength() {
return this._keyLength;
}
get n() {
return this._n;
}
get e() {
return this._e;
}
get d() {
return this._d;
}
}
export default class RSAAESAuthenticationState extends EventTargetMixin { export default class RSAAESAuthenticationState extends EventTargetMixin {
constructor(sock, getCredentials) { constructor(sock, getCredentials) {
super(); super();
@@ -406,7 +139,7 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
this._hasStarted = true; this._hasStarted = true;
// 1: Receive server public key // 1: Receive server public key
await this._waitSockAsync(4); await this._waitSockAsync(4);
const serverKeyLengthBuffer = this._sock.rQslice(0, 4); const serverKeyLengthBuffer = this._sock.rQpeekBytes(4);
const serverKeyLength = this._sock.rQshift32(); const serverKeyLength = this._sock.rQshift32();
if (serverKeyLength < 1024) { if (serverKeyLength < 1024) {
throw new Error("RA2: server public key is too short: " + serverKeyLength); throw new Error("RA2: server public key is too short: " + serverKeyLength);
@@ -417,26 +150,31 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
await this._waitSockAsync(serverKeyBytes * 2); await this._waitSockAsync(serverKeyBytes * 2);
const serverN = this._sock.rQshiftBytes(serverKeyBytes); const serverN = this._sock.rQshiftBytes(serverKeyBytes);
const serverE = this._sock.rQshiftBytes(serverKeyBytes); const serverE = this._sock.rQshiftBytes(serverKeyBytes);
const serverRSACipher = new RSACipher(serverKeyLength); const serverRSACipher = await legacyCrypto.importKey(
serverRSACipher.setPublicKey(serverN, serverE); "raw", { n: serverN, e: serverE }, { name: "RSA-PKCS1-v1_5" }, false, ["encrypt"]);
const serverPublickey = new Uint8Array(4 + serverKeyBytes * 2); const serverPublickey = new Uint8Array(4 + serverKeyBytes * 2);
serverPublickey.set(serverKeyLengthBuffer); serverPublickey.set(serverKeyLengthBuffer);
serverPublickey.set(serverN, 4); serverPublickey.set(serverN, 4);
serverPublickey.set(serverE, 4 + serverKeyBytes); serverPublickey.set(serverE, 4 + serverKeyBytes);
// verify server public key // verify server public key
let approveKey = this._waitApproveKeyAsync();
this.dispatchEvent(new CustomEvent("serververification", { this.dispatchEvent(new CustomEvent("serververification", {
detail: { type: "RSA", publickey: serverPublickey } detail: { type: "RSA", publickey: serverPublickey }
})); }));
await this._waitApproveKeyAsync(); await approveKey;
// 2: Send client public key // 2: Send client public key
const clientKeyLength = 2048; const clientKeyLength = 2048;
const clientKeyBytes = Math.ceil(clientKeyLength / 8); const clientKeyBytes = Math.ceil(clientKeyLength / 8);
const clientRSACipher = new RSACipher(clientKeyLength); const clientRSACipher = (await legacyCrypto.generateKey({
await clientRSACipher.generateKey(); name: "RSA-PKCS1-v1_5",
const clientN = clientRSACipher.n; modulusLength: clientKeyLength,
const clientE = clientRSACipher.e; publicExponent: new Uint8Array([1, 0, 1]),
}, true, ["encrypt"])).privateKey;
const clientExportedRSAKey = await legacyCrypto.exportKey("raw", clientRSACipher);
const clientN = clientExportedRSAKey.n;
const clientE = clientExportedRSAKey.e;
const clientPublicKey = new Uint8Array(4 + clientKeyBytes * 2); const clientPublicKey = new Uint8Array(4 + clientKeyBytes * 2);
clientPublicKey[0] = (clientKeyLength & 0xff000000) >>> 24; clientPublicKey[0] = (clientKeyLength & 0xff000000) >>> 24;
clientPublicKey[1] = (clientKeyLength & 0xff0000) >>> 16; clientPublicKey[1] = (clientKeyLength & 0xff0000) >>> 16;
@@ -444,17 +182,20 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
clientPublicKey[3] = clientKeyLength & 0xff; clientPublicKey[3] = clientKeyLength & 0xff;
clientPublicKey.set(clientN, 4); clientPublicKey.set(clientN, 4);
clientPublicKey.set(clientE, 4 + clientKeyBytes); clientPublicKey.set(clientE, 4 + clientKeyBytes);
this._sock.send(clientPublicKey); this._sock.sQpushBytes(clientPublicKey);
this._sock.flush();
// 3: Send client random // 3: Send client random
const clientRandom = new Uint8Array(16); const clientRandom = new Uint8Array(16);
window.crypto.getRandomValues(clientRandom); window.crypto.getRandomValues(clientRandom);
const clientEncryptedRandom = serverRSACipher.encrypt(clientRandom); const clientEncryptedRandom = await legacyCrypto.encrypt(
{ name: "RSA-PKCS1-v1_5" }, serverRSACipher, clientRandom);
const clientRandomMessage = new Uint8Array(2 + serverKeyBytes); const clientRandomMessage = new Uint8Array(2 + serverKeyBytes);
clientRandomMessage[0] = (serverKeyBytes & 0xff00) >>> 8; clientRandomMessage[0] = (serverKeyBytes & 0xff00) >>> 8;
clientRandomMessage[1] = serverKeyBytes & 0xff; clientRandomMessage[1] = serverKeyBytes & 0xff;
clientRandomMessage.set(clientEncryptedRandom, 2); clientRandomMessage.set(clientEncryptedRandom, 2);
this._sock.send(clientRandomMessage); this._sock.sQpushBytes(clientRandomMessage);
this._sock.flush();
// 4: Receive server random // 4: Receive server random
await this._waitSockAsync(2); await this._waitSockAsync(2);
@@ -462,7 +203,8 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
throw new Error("RA2: wrong encrypted message length"); throw new Error("RA2: wrong encrypted message length");
} }
const serverEncryptedRandom = this._sock.rQshiftBytes(clientKeyBytes); const serverEncryptedRandom = this._sock.rQshiftBytes(clientKeyBytes);
const serverRandom = clientRSACipher.decrypt(serverEncryptedRandom); const serverRandom = await legacyCrypto.decrypt(
{ name: "RSA-PKCS1-v1_5" }, clientRSACipher, serverEncryptedRandom);
if (serverRandom === null || serverRandom.length !== 16) { if (serverRandom === null || serverRandom.length !== 16) {
throw new Error("RA2: corrupted server encrypted random"); throw new Error("RA2: corrupted server encrypted random");
} }
@@ -494,13 +236,14 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
clientHash = await window.crypto.subtle.digest("SHA-1", clientHash); clientHash = await window.crypto.subtle.digest("SHA-1", clientHash);
serverHash = new Uint8Array(serverHash); serverHash = new Uint8Array(serverHash);
clientHash = new Uint8Array(clientHash); clientHash = new Uint8Array(clientHash);
this._sock.send(await clientCipher.makeMessage(clientHash)); this._sock.sQpushBytes(await clientCipher.makeMessage(clientHash));
this._sock.flush();
await this._waitSockAsync(2 + 20 + 16); await this._waitSockAsync(2 + 20 + 16);
if (this._sock.rQshift16() !== 20) { if (this._sock.rQshift16() !== 20) {
throw new Error("RA2: wrong server hash"); throw new Error("RA2: wrong server hash");
} }
const serverHashReceived = await serverCipher.receiveMessage( const serverHashReceived = await serverCipher.receiveMessage(
20, this._sock.rQshiftBytes(20), this._sock.rQshiftBytes(16)); 20, this._sock.rQshiftBytes(20 + 16));
if (serverHashReceived === null) { if (serverHashReceived === null) {
throw new Error("RA2: failed to authenticate the message"); throw new Error("RA2: failed to authenticate the message");
} }
@@ -516,11 +259,12 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
throw new Error("RA2: wrong subtype"); throw new Error("RA2: wrong subtype");
} }
let subtype = (await serverCipher.receiveMessage( let subtype = (await serverCipher.receiveMessage(
1, this._sock.rQshiftBytes(1), this._sock.rQshiftBytes(16))); 1, this._sock.rQshiftBytes(1 + 16)));
if (subtype === null) { if (subtype === null) {
throw new Error("RA2: failed to authenticate the message"); throw new Error("RA2: failed to authenticate the message");
} }
subtype = subtype[0]; subtype = subtype[0];
let waitCredentials = this._waitCredentialsAsync(subtype);
if (subtype === 1) { if (subtype === 1) {
if (this._getCredentials().username === undefined || if (this._getCredentials().username === undefined ||
this._getCredentials().password === undefined) { this._getCredentials().password === undefined) {
@@ -537,7 +281,7 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
} else { } else {
throw new Error("RA2: wrong subtype"); throw new Error("RA2: wrong subtype");
} }
await this._waitCredentialsAsync(subtype); await waitCredentials;
let username; let username;
if (subtype === 1) { if (subtype === 1) {
username = encodeUTF8(this._getCredentials().username).slice(0, 255); username = encodeUTF8(this._getCredentials().username).slice(0, 255);
@@ -554,7 +298,8 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
for (let i = 0; i < password.length; i++) { for (let i = 0; i < password.length; i++) {
credentials[username.length + 2 + i] = password.charCodeAt(i); credentials[username.length + 2 + i] = password.charCodeAt(i);
} }
this._sock.send(await clientCipher.makeMessage(credentials)); this._sock.sQpushBytes(await clientCipher.makeMessage(credentials));
this._sock.flush();
} }
get hasStarted() { get hasStarted() {
+245 -369
View File
@@ -21,12 +21,11 @@ import Keyboard from "./input/keyboard.js";
import GestureHandler from "./input/gesturehandler.js"; import GestureHandler from "./input/gesturehandler.js";
import Cursor from "./util/cursor.js"; import Cursor from "./util/cursor.js";
import Websock from "./websock.js"; import Websock from "./websock.js";
import DES from "./des.js";
import KeyTable from "./input/keysym.js"; import KeyTable from "./input/keysym.js";
import XtScancode from "./input/xtscancodes.js"; import XtScancode from "./input/xtscancodes.js";
import { encodings } from "./encodings.js"; import { encodings } from "./encodings.js";
import RSAAESAuthenticationState from "./ra2.js"; import RSAAESAuthenticationState from "./ra2.js";
import { MD5 } from "./util/md5.js"; import legacyCrypto from "./crypto/crypto.js";
import RawDecoder from "./decoders/raw.js"; import RawDecoder from "./decoders/raw.js";
import CopyRectDecoder from "./decoders/copyrect.js"; import CopyRectDecoder from "./decoders/copyrect.js";
@@ -258,10 +257,11 @@ export default class RFB extends EventTargetMixin {
Log.Error("Display exception: " + exc); Log.Error("Display exception: " + exc);
throw exc; throw exc;
} }
this._display.onflush = this._onFlush.bind(this);
this._keyboard = new Keyboard(this._canvas); this._keyboard = new Keyboard(this._canvas);
this._keyboard.onkeyevent = this._handleKeyEvent.bind(this); this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
this._remoteCapsLock = null; // Null indicates unknown or irrelevant
this._remoteNumLock = null;
this._gestures = new GestureHandler(); this._gestures = new GestureHandler();
@@ -960,7 +960,7 @@ export default class RFB extends EventTargetMixin {
} }
_handleMessage() { _handleMessage() {
if (this._sock.rQlen === 0) { if (this._sock.rQwait("message", 1)) {
Log.Warn("handleMessage called on an empty receive queue"); Log.Warn("handleMessage called on an empty receive queue");
return; return;
} }
@@ -977,7 +977,7 @@ export default class RFB extends EventTargetMixin {
if (!this._normalMsg()) { if (!this._normalMsg()) {
break; break;
} }
if (this._sock.rQlen === 0) { if (this._sock.rQwait("message", 1)) {
break; break;
} }
} }
@@ -995,7 +995,35 @@ export default class RFB extends EventTargetMixin {
} }
} }
_handleKeyEvent(keysym, code, down) { _handleKeyEvent(keysym, code, down, numlock, capslock) {
// If remote state of capslock is known, and it doesn't match the local led state of
// the keyboard, we send a capslock keypress first to bring it into sync.
// If we just pressed CapsLock, or we toggled it remotely due to it being out of sync
// we clear the remote state so that we don't send duplicate or spurious fixes,
// since it may take some time to receive the new remote CapsLock state.
if (code == 'CapsLock' && down) {
this._remoteCapsLock = null;
}
if (this._remoteCapsLock !== null && capslock !== null && this._remoteCapsLock !== capslock && down) {
Log.Debug("Fixing remote caps lock");
this.sendKey(KeyTable.XK_Caps_Lock, 'CapsLock', true);
this.sendKey(KeyTable.XK_Caps_Lock, 'CapsLock', false);
// We clear the remote capsLock state when we do this to prevent issues with doing this twice
// before we receive an update of the the remote state.
this._remoteCapsLock = null;
}
// Logic for numlock is exactly the same.
if (code == 'NumLock' && down) {
this._remoteNumLock = null;
}
if (this._remoteNumLock !== null && numlock !== null && this._remoteNumLock !== numlock && down) {
Log.Debug("Fixing remote num lock");
this.sendKey(KeyTable.XK_Num_Lock, 'NumLock', true);
this.sendKey(KeyTable.XK_Num_Lock, 'NumLock', false);
this._remoteNumLock = null;
}
this.sendKey(keysym, code, down); this.sendKey(keysym, code, down);
} }
@@ -1383,7 +1411,8 @@ export default class RFB extends EventTargetMixin {
while (repeaterID.length < 250) { while (repeaterID.length < 250) {
repeaterID += "\0"; repeaterID += "\0";
} }
this._sock.sendString(repeaterID); this._sock.sQpushString(repeaterID);
this._sock.flush();
return true; return true;
} }
@@ -1393,7 +1422,8 @@ export default class RFB extends EventTargetMixin {
const cversion = "00" + parseInt(this._rfbVersion, 10) + const cversion = "00" + parseInt(this._rfbVersion, 10) +
".00" + ((this._rfbVersion * 10) % 10); ".00" + ((this._rfbVersion * 10) % 10);
this._sock.sendString("RFB " + cversion + "\n"); this._sock.sQpushString("RFB " + cversion + "\n");
this._sock.flush();
Log.Debug('Sent ProtocolVersion: ' + cversion); Log.Debug('Sent ProtocolVersion: ' + cversion);
this._rfbInitState = 'Security'; this._rfbInitState = 'Security';
@@ -1445,7 +1475,8 @@ export default class RFB extends EventTargetMixin {
return this._fail("Unsupported security types (types: " + types + ")"); return this._fail("Unsupported security types (types: " + types + ")");
} }
this._sock.send([this._rfbAuthScheme]); this._sock.sQpush8(this._rfbAuthScheme);
this._sock.flush();
} else { } else {
// Server decides // Server decides
if (this._sock.rQwait("security scheme", 4)) { return false; } if (this._sock.rQwait("security scheme", 4)) { return false; }
@@ -1507,12 +1538,15 @@ export default class RFB extends EventTargetMixin {
return false; return false;
} }
const xvpAuthStr = String.fromCharCode(this._rfbCredentials.username.length) + this._sock.sQpush8(this._rfbCredentials.username.length);
String.fromCharCode(this._rfbCredentials.target.length) + this._sock.sQpush8(this._rfbCredentials.target.length);
this._rfbCredentials.username + this._sock.sQpushString(this._rfbCredentials.username);
this._rfbCredentials.target; this._sock.sQpushString(this._rfbCredentials.target);
this._sock.sendString(xvpAuthStr);
this._sock.flush();
this._rfbAuthScheme = securityTypeVNCAuth; this._rfbAuthScheme = securityTypeVNCAuth;
return this._negotiateAuthentication(); return this._negotiateAuthentication();
} }
@@ -1530,7 +1564,9 @@ export default class RFB extends EventTargetMixin {
return this._fail("Unsupported VeNCrypt version " + major + "." + minor); return this._fail("Unsupported VeNCrypt version " + major + "." + minor);
} }
this._sock.send([0, 2]); this._sock.sQpush8(0);
this._sock.sQpush8(2);
this._sock.flush();
this._rfbVeNCryptState = 1; this._rfbVeNCryptState = 1;
} }
@@ -1589,12 +1625,10 @@ export default class RFB extends EventTargetMixin {
return this._fail("Unsupported security types (types: " + subtypes + ")"); return this._fail("Unsupported security types (types: " + subtypes + ")");
} }
this._sock.send([this._rfbAuthScheme >> 24, this._sock.sQpush32(this._rfbAuthScheme);
this._rfbAuthScheme >> 16, this._sock.flush();
this._rfbAuthScheme >> 8,
this._rfbAuthScheme]);
this._rfbVeNCryptState == 4; this._rfbVeNCryptState = 4;
return true; return true;
} }
} }
@@ -1611,20 +1645,11 @@ export default class RFB extends EventTargetMixin {
const user = encodeUTF8(this._rfbCredentials.username); const user = encodeUTF8(this._rfbCredentials.username);
const pass = encodeUTF8(this._rfbCredentials.password); const pass = encodeUTF8(this._rfbCredentials.password);
this._sock.send([ this._sock.sQpush32(user.length);
(user.length >> 24) & 0xFF, this._sock.sQpush32(pass.length);
(user.length >> 16) & 0xFF, this._sock.sQpushString(user);
(user.length >> 8) & 0xFF, this._sock.sQpushString(pass);
user.length & 0xFF this._sock.flush();
]);
this._sock.send([
(pass.length >> 24) & 0xFF,
(pass.length >> 16) & 0xFF,
(pass.length >> 8) & 0xFF,
pass.length & 0xFF
]);
this._sock.sendString(user);
this._sock.sendString(pass);
this._rfbInitState = "SecurityResult"; this._rfbInitState = "SecurityResult";
return true; return true;
@@ -1643,7 +1668,8 @@ export default class RFB extends EventTargetMixin {
// TODO(directxman12): make genDES not require an Array // TODO(directxman12): make genDES not require an Array
const challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16)); const challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));
const response = RFB.genDES(this._rfbCredentials.password, challenge); const response = RFB.genDES(this._rfbCredentials.password, challenge);
this._sock.send(response); this._sock.sQpushBytes(response);
this._sock.flush();
this._rfbInitState = "SecurityResult"; this._rfbInitState = "SecurityResult";
return true; return true;
} }
@@ -1661,8 +1687,9 @@ export default class RFB extends EventTargetMixin {
if (this._rfbCredentials.ardPublicKey != undefined && if (this._rfbCredentials.ardPublicKey != undefined &&
this._rfbCredentials.ardCredentials != undefined) { this._rfbCredentials.ardCredentials != undefined) {
// if the async web crypto is done return the results // if the async web crypto is done return the results
this._sock.send(this._rfbCredentials.ardCredentials); this._sock.sQpushBytes(this._rfbCredentials.ardCredentials);
this._sock.send(this._rfbCredentials.ardPublicKey); this._sock.sQpushBytes(this._rfbCredentials.ardPublicKey);
this._sock.flush();
this._rfbCredentials.ardCredentials = null; this._rfbCredentials.ardCredentials = null;
this._rfbCredentials.ardPublicKey = null; this._rfbCredentials.ardPublicKey = null;
this._rfbInitState = "SecurityResult"; this._rfbInitState = "SecurityResult";
@@ -1681,77 +1708,35 @@ export default class RFB extends EventTargetMixin {
let prime = this._sock.rQshiftBytes(keyLength); // predetermined prime modulus let prime = this._sock.rQshiftBytes(keyLength); // predetermined prime modulus
let serverPublicKey = this._sock.rQshiftBytes(keyLength); // other party's public key let serverPublicKey = this._sock.rQshiftBytes(keyLength); // other party's public key
let clientPrivateKey = window.crypto.getRandomValues(new Uint8Array(keyLength)); let clientKey = legacyCrypto.generateKey(
let padding = Array.from(window.crypto.getRandomValues(new Uint8Array(64)), byte => String.fromCharCode(65+byte%26)).join(''); { name: "DH", g: generator, p: prime }, false, ["deriveBits"]);
this._negotiateARDAuthAsync(keyLength, serverPublicKey, clientKey);
this._negotiateARDAuthAsync(generator, keyLength, prime, serverPublicKey, clientPrivateKey, padding);
return false; return false;
} }
_modPow(base, exponent, modulus) { async _negotiateARDAuthAsync(keyLength, serverPublicKey, clientKey) {
const clientPublicKey = legacyCrypto.exportKey("raw", clientKey.publicKey);
const sharedKey = legacyCrypto.deriveBits(
{ name: "DH", public: serverPublicKey }, clientKey.privateKey, keyLength * 8);
let baseHex = "0x"+Array.from(base, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join(''); const username = encodeUTF8(this._rfbCredentials.username).substring(0, 63);
let exponentHex = "0x"+Array.from(exponent, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join(''); const password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);
let modulusHex = "0x"+Array.from(modulus, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
let b = BigInt(baseHex); const credentials = window.crypto.getRandomValues(new Uint8Array(128));
let e = BigInt(exponentHex); for (let i = 0; i < username.length; i++) {
let m = BigInt(modulusHex); credentials[i] = username.charCodeAt(i);
let r = 1n;
b = b % m;
while (e > 0) {
if (e % 2n === 1n) {
r = (r * b) % m;
}
e = e / 2n;
b = (b * b) % m;
} }
let hexResult = r.toString(16); credentials[username.length] = 0;
for (let i = 0; i < password.length; i++) {
while (hexResult.length/2<exponent.length || (hexResult.length%2 != 0)) { credentials[64 + i] = password.charCodeAt(i);
hexResult = "0"+hexResult;
} }
credentials[64 + password.length] = 0;
let bytesResult = []; const key = await legacyCrypto.digest("MD5", sharedKey);
for (let c = 0; c < hexResult.length; c += 2) { const cipher = await legacyCrypto.importKey(
bytesResult.push(parseInt(hexResult.substr(c, 2), 16)); "raw", key, { name: "AES-ECB" }, false, ["encrypt"]);
} const encrypted = await legacyCrypto.encrypt({ name: "AES-ECB" }, cipher, credentials);
return bytesResult;
}
async _aesEcbEncrypt(string, key) {
// perform AES-ECB blocks
let keyString = Array.from(key, byte => String.fromCharCode(byte)).join('');
let aesKey = await window.crypto.subtle.importKey("raw", MD5(keyString), {name: "AES-CBC"}, false, ["encrypt"]);
let data = new Uint8Array(string.length);
for (let i = 0; i < string.length; ++i) {
data[i] = string.charCodeAt(i);
}
let encrypted = new Uint8Array(data.length);
for (let i=0;i<data.length;i+=16) {
let block = data.slice(i, i+16);
let encryptedBlock = await window.crypto.subtle.encrypt({name: "AES-CBC", iv: block},
aesKey, new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
);
encrypted.set((new Uint8Array(encryptedBlock)).slice(0, 16), i);
}
return encrypted;
}
async _negotiateARDAuthAsync(generator, keyLength, prime, serverPublicKey, clientPrivateKey, padding) {
// calculate the DH keys
let clientPublicKey = this._modPow(generator, clientPrivateKey, prime);
let sharedKey = this._modPow(serverPublicKey, clientPrivateKey, prime);
let username = encodeUTF8(this._rfbCredentials.username).substring(0, 63);
let password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);
let paddedUsername = username + '\0' + padding.substring(0, 63);
let paddedPassword = password + '\0' + padding.substring(0, 63);
let credentials = paddedUsername.substring(0, 64) + paddedPassword.substring(0, 64);
let encrypted = await this._aesEcbEncrypt(credentials, sharedKey);
this._rfbCredentials.ardCredentials = encrypted; this._rfbCredentials.ardCredentials = encrypted;
this._rfbCredentials.ardPublicKey = clientPublicKey; this._rfbCredentials.ardPublicKey = clientPublicKey;
@@ -1768,10 +1753,12 @@ export default class RFB extends EventTargetMixin {
return false; return false;
} }
this._sock.send([0, 0, 0, this._rfbCredentials.username.length]); this._sock.sQpush32(this._rfbCredentials.username.length);
this._sock.send([0, 0, 0, this._rfbCredentials.password.length]); this._sock.sQpush32(this._rfbCredentials.password.length);
this._sock.sendString(this._rfbCredentials.username); this._sock.sQpushString(this._rfbCredentials.username);
this._sock.sendString(this._rfbCredentials.password); this._sock.sQpushString(this._rfbCredentials.password);
this._sock.flush();
this._rfbInitState = "SecurityResult"; this._rfbInitState = "SecurityResult";
return true; return true;
} }
@@ -1809,7 +1796,8 @@ export default class RFB extends EventTargetMixin {
"vendor or signature"); "vendor or signature");
} }
Log.Debug("Selected tunnel type: " + clientSupportedTunnelTypes[0]); Log.Debug("Selected tunnel type: " + clientSupportedTunnelTypes[0]);
this._sock.send([0, 0, 0, 0]); // use NOTUNNEL this._sock.sQpush32(0); // use NOTUNNEL
this._sock.flush();
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("Server wanted tunnels, but doesn't support " + return this._fail("Server wanted tunnels, but doesn't support " +
@@ -1859,7 +1847,8 @@ export default class RFB extends EventTargetMixin {
for (let authType in clientSupportedTypes) { for (let authType in clientSupportedTypes) {
if (serverSupportedTypes.indexOf(authType) != -1) { if (serverSupportedTypes.indexOf(authType) != -1) {
this._sock.send([0, 0, 0, clientSupportedTypes[authType]]); this._sock.sQpush32(clientSupportedTypes[authType]);
this._sock.flush();
Log.Debug("Selected authentication type: " + authType); Log.Debug("Selected authentication type: " + authType);
switch (authType) { switch (authType) {
@@ -1905,8 +1894,8 @@ export default class RFB extends EventTargetMixin {
if (e.message !== "disconnect normally") { if (e.message !== "disconnect normally") {
this._fail(e.message); this._fail(e.message);
} }
}).then(() => { })
this.dispatchEvent(new CustomEvent('securityresult')); .then(() => {
this._rfbInitState = "SecurityResult"; this._rfbInitState = "SecurityResult";
return true; return true;
}).finally(() => { }).finally(() => {
@@ -1934,15 +1923,15 @@ export default class RFB extends EventTargetMixin {
const g = this._sock.rQshiftBytes(8); const g = this._sock.rQshiftBytes(8);
const p = this._sock.rQshiftBytes(8); const p = this._sock.rQshiftBytes(8);
const A = this._sock.rQshiftBytes(8); const A = this._sock.rQshiftBytes(8);
const b = window.crypto.getRandomValues(new Uint8Array(8)); const dhKey = legacyCrypto.generateKey({ name: "DH", g: g, p: p }, true, ["deriveBits"]);
const B = new Uint8Array(this._modPow(g, b, p)); const B = legacyCrypto.exportKey("raw", dhKey.publicKey);
const secret = new Uint8Array(this._modPow(A, b, p)); const secret = legacyCrypto.deriveBits({ name: "DH", public: A }, dhKey.privateKey, 64);
const des = new DES(secret); const key = legacyCrypto.importKey("raw", secret, { name: "DES-CBC" }, false, ["encrypt"]);
const username = encodeUTF8(this._rfbCredentials.username).substring(0, 255); const username = encodeUTF8(this._rfbCredentials.username).substring(0, 255);
const password = encodeUTF8(this._rfbCredentials.password).substring(0, 63); const password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);
const usernameBytes = new Uint8Array(256); let usernameBytes = new Uint8Array(256);
const passwordBytes = new Uint8Array(64); let passwordBytes = new Uint8Array(64);
window.crypto.getRandomValues(usernameBytes); window.crypto.getRandomValues(usernameBytes);
window.crypto.getRandomValues(passwordBytes); window.crypto.getRandomValues(passwordBytes);
for (let i = 0; i < username.length; i++) { for (let i = 0; i < username.length; i++) {
@@ -1953,25 +1942,12 @@ export default class RFB extends EventTargetMixin {
passwordBytes[i] = password.charCodeAt(i); passwordBytes[i] = password.charCodeAt(i);
} }
passwordBytes[password.length] = 0; passwordBytes[password.length] = 0;
let x = new Uint8Array(secret); usernameBytes = legacyCrypto.encrypt({ name: "DES-CBC", iv: secret }, key, usernameBytes);
for (let i = 0; i < 32; i++) { passwordBytes = legacyCrypto.encrypt({ name: "DES-CBC", iv: secret }, key, passwordBytes);
for (let j = 0; j < 8; j++) { this._sock.sQpushBytes(B);
x[j] ^= usernameBytes[i * 8 + j]; this._sock.sQpushBytes(usernameBytes);
} this._sock.sQpushBytes(passwordBytes);
x = des.enc8(x); this._sock.flush();
usernameBytes.set(x, i * 8);
}
x = new Uint8Array(secret);
for (let i = 0; i < 8; i++) {
for (let j = 0; j < 8; j++) {
x[j] ^= passwordBytes[i * 8 + j];
}
x = des.enc8(x);
passwordBytes.set(x, i * 8);
}
this._sock.send(B);
this._sock.send(usernameBytes);
this._sock.send(passwordBytes);
this._rfbInitState = "SecurityResult"; this._rfbInitState = "SecurityResult";
return true; return true;
} }
@@ -1979,7 +1955,11 @@ export default class RFB extends EventTargetMixin {
_negotiateAuthentication() { _negotiateAuthentication() {
switch (this._rfbAuthScheme) { switch (this._rfbAuthScheme) {
case securityTypeNone: case securityTypeNone:
this._rfbInitState = 'SecurityResult'; if (this._rfbVersion >= 3.8) {
this._rfbInitState = 'SecurityResult';
} else {
this._rfbInitState = 'ClientInitialisation';
}
return true; return true;
case securityTypeXVP: case securityTypeXVP:
@@ -2016,13 +1996,6 @@ export default class RFB extends EventTargetMixin {
} }
_handleSecurityResult() { _handleSecurityResult() {
// There is no security choice, and hence no security result
// until RFB 3.7
if (this._rfbVersion < 3.7) {
this._rfbInitState = 'ClientInitialisation';
return true;
}
if (this._sock.rQwait('VNC auth response ', 4)) { return false; } if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
const status = this._sock.rQshift32(); const status = this._sock.rQshift32();
@@ -2158,6 +2131,7 @@ export default class RFB extends EventTargetMixin {
encs.push(encodings.pseudoEncodingDesktopSize); encs.push(encodings.pseudoEncodingDesktopSize);
encs.push(encodings.pseudoEncodingLastRect); encs.push(encodings.pseudoEncodingLastRect);
encs.push(encodings.pseudoEncodingQEMUExtendedKeyEvent); encs.push(encodings.pseudoEncodingQEMUExtendedKeyEvent);
encs.push(encodings.pseudoEncodingQEMULedEvent);
encs.push(encodings.pseudoEncodingExtendedDesktopSize); encs.push(encodings.pseudoEncodingExtendedDesktopSize);
encs.push(encodings.pseudoEncodingXvp); encs.push(encodings.pseudoEncodingXvp);
encs.push(encodings.pseudoEncodingFence); encs.push(encodings.pseudoEncodingFence);
@@ -2199,7 +2173,8 @@ export default class RFB extends EventTargetMixin {
return this._handleSecurityReason(); return this._handleSecurityReason();
case 'ClientInitialisation': case 'ClientInitialisation':
this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation this._sock.sQpush8(this._shared ? 1 : 0); // ClientInitialisation
this._sock.flush();
this._rfbInitState = 'ServerInitialisation'; this._rfbInitState = 'ServerInitialisation';
return true; return true;
@@ -2512,19 +2487,11 @@ export default class RFB extends EventTargetMixin {
default: default:
this._fail("Unexpected server message (type " + msgType + ")"); this._fail("Unexpected server message (type " + msgType + ")");
Log.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30)); Log.Debug("sock.rQpeekBytes(30): " + this._sock.rQpeekBytes(30));
return true; return true;
} }
} }
_onFlush() {
this._flushing = false;
// Resume processing
if (this._sock.rQlen > 0) {
this._handleMessage();
}
}
_framebufferUpdate() { _framebufferUpdate() {
if (this._FBU.rects === 0) { if (this._FBU.rects === 0) {
if (this._sock.rQwait("FBU header", 3, 1)) { return false; } if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
@@ -2535,7 +2502,14 @@ export default class RFB extends EventTargetMixin {
// to avoid building up an excessive queue // to avoid building up an excessive queue
if (this._display.pending()) { if (this._display.pending()) {
this._flushing = true; this._flushing = true;
this._display.flush(); this._display.flush()
.then(() => {
this._flushing = false;
// Resume processing
if (!this._sock.rQwait("message", 1)) {
this._handleMessage();
}
});
return false; return false;
} }
} }
@@ -2545,13 +2519,13 @@ export default class RFB extends EventTargetMixin {
if (this._sock.rQwait("rect header", 12)) { return false; } if (this._sock.rQwait("rect header", 12)) { return false; }
/* New FramebufferUpdate */ /* New FramebufferUpdate */
const hdr = this._sock.rQshiftBytes(12); this._FBU.x = this._sock.rQshift16();
this._FBU.x = (hdr[0] << 8) + hdr[1]; this._FBU.y = this._sock.rQshift16();
this._FBU.y = (hdr[2] << 8) + hdr[3]; this._FBU.width = this._sock.rQshift16();
this._FBU.width = (hdr[4] << 8) + hdr[5]; this._FBU.height = this._sock.rQshift16();
this._FBU.height = (hdr[6] << 8) + hdr[7]; this._FBU.encoding = this._sock.rQshift32();
this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) + /* Encodings are signed */
(hdr[10] << 8) + hdr[11], 10); this._FBU.encoding >>= 0;
} }
if (!this._handleRect()) { if (!this._handleRect()) {
@@ -2593,6 +2567,9 @@ export default class RFB extends EventTargetMixin {
case encodings.pseudoEncodingExtendedDesktopSize: case encodings.pseudoEncodingExtendedDesktopSize:
return this._handleExtendedDesktopSize(); return this._handleExtendedDesktopSize();
case encodings.pseudoEncodingQEMULedEvent:
return this._handleLedEvent();
default: default:
return this._handleDataRect(); return this._handleDataRect();
} }
@@ -2770,6 +2747,21 @@ export default class RFB extends EventTargetMixin {
return true; return true;
} }
_handleLedEvent() {
if (this._sock.rQwait("LED Status", 1)) {
return false;
}
let data = this._sock.rQshift8();
// ScrollLock state can be retrieved with data & 1. This is currently not needed.
let numLock = data & 2 ? true : false;
let capsLock = data & 4 ? true : false;
this._remoteCapsLock = capsLock;
this._remoteNumLock = numLock;
return true;
}
_handleExtendedDesktopSize() { _handleExtendedDesktopSize() {
if (this._sock.rQwait("ExtendedDesktopSize", 4)) { if (this._sock.rQwait("ExtendedDesktopSize", 4)) {
return false; return false;
@@ -2785,26 +2777,18 @@ export default class RFB extends EventTargetMixin {
const firstUpdate = !this._supportsSetDesktopSize; const firstUpdate = !this._supportsSetDesktopSize;
this._supportsSetDesktopSize = true; this._supportsSetDesktopSize = true;
// Normally we only apply the current resize mode after a
// window resize event. However there is no such trigger on the
// initial connect. And we don't know if the server supports
// resizing until we've gotten here.
if (firstUpdate) {
this._requestRemoteResize();
}
this._sock.rQskipBytes(1); // number-of-screens this._sock.rQskipBytes(1); // number-of-screens
this._sock.rQskipBytes(3); // padding this._sock.rQskipBytes(3); // padding
for (let i = 0; i < numberOfScreens; i += 1) { for (let i = 0; i < numberOfScreens; i += 1) {
// Save the id and flags of the first screen // Save the id and flags of the first screen
if (i === 0) { if (i === 0) {
this._screenID = this._sock.rQshiftBytes(4); // id this._screenID = this._sock.rQshift32(); // id
this._sock.rQskipBytes(2); // x-position this._sock.rQskipBytes(2); // x-position
this._sock.rQskipBytes(2); // y-position this._sock.rQskipBytes(2); // y-position
this._sock.rQskipBytes(2); // width this._sock.rQskipBytes(2); // width
this._sock.rQskipBytes(2); // height this._sock.rQskipBytes(2); // height
this._screenFlags = this._sock.rQshiftBytes(4); // flags this._screenFlags = this._sock.rQshift32(); // flags
} else { } else {
this._sock.rQskipBytes(16); this._sock.rQskipBytes(16);
} }
@@ -2842,6 +2826,14 @@ export default class RFB extends EventTargetMixin {
this._resize(this._FBU.width, this._FBU.height); this._resize(this._FBU.width, this._FBU.height);
} }
// Normally we only apply the current resize mode after a
// window resize event. However there is no such trigger on the
// initial connect. And we don't know if the server supports
// resizing until we've gotten here.
if (firstUpdate) {
this._requestRemoteResize();
}
return true; return true;
} }
@@ -2937,28 +2929,22 @@ export default class RFB extends EventTargetMixin {
static genDES(password, challenge) { static genDES(password, challenge) {
const passwordChars = password.split('').map(c => c.charCodeAt(0)); const passwordChars = password.split('').map(c => c.charCodeAt(0));
return (new DES(passwordChars)).encrypt(challenge); const key = legacyCrypto.importKey(
"raw", passwordChars, { name: "DES-ECB" }, false, ["encrypt"]);
return legacyCrypto.encrypt({ name: "DES-ECB" }, key, challenge);
} }
} }
// Class Methods // Class Methods
RFB.messages = { RFB.messages = {
keyEvent(sock, keysym, down) { keyEvent(sock, keysym, down) {
const buff = sock._sQ; sock.sQpush8(4); // msg-type
const offset = sock._sQlen; sock.sQpush8(down);
buff[offset] = 4; // msg-type sock.sQpush16(0);
buff[offset + 1] = down;
buff[offset + 2] = 0; sock.sQpush32(keysym);
buff[offset + 3] = 0;
buff[offset + 4] = (keysym >> 24);
buff[offset + 5] = (keysym >> 16);
buff[offset + 6] = (keysym >> 8);
buff[offset + 7] = keysym;
sock._sQlen += 8;
sock.flush(); sock.flush();
}, },
@@ -2972,46 +2958,28 @@ RFB.messages = {
return xtScanCode; return xtScanCode;
} }
const buff = sock._sQ; sock.sQpush8(255); // msg-type
const offset = sock._sQlen; sock.sQpush8(0); // sub msg-type
buff[offset] = 255; // msg-type sock.sQpush16(down);
buff[offset + 1] = 0; // sub msg-type
buff[offset + 2] = (down >> 8); sock.sQpush32(keysym);
buff[offset + 3] = down;
buff[offset + 4] = (keysym >> 24);
buff[offset + 5] = (keysym >> 16);
buff[offset + 6] = (keysym >> 8);
buff[offset + 7] = keysym;
const RFBkeycode = getRFBkeycode(keycode); const RFBkeycode = getRFBkeycode(keycode);
buff[offset + 8] = (RFBkeycode >> 24); sock.sQpush32(RFBkeycode);
buff[offset + 9] = (RFBkeycode >> 16);
buff[offset + 10] = (RFBkeycode >> 8);
buff[offset + 11] = RFBkeycode;
sock._sQlen += 12;
sock.flush(); sock.flush();
}, },
pointerEvent(sock, x, y, mask) { pointerEvent(sock, x, y, mask) {
const buff = sock._sQ; sock.sQpush8(5); // msg-type
const offset = sock._sQlen;
buff[offset] = 5; // msg-type sock.sQpush8(mask);
buff[offset + 1] = mask; sock.sQpush16(x);
sock.sQpush16(y);
buff[offset + 2] = x >> 8;
buff[offset + 3] = x;
buff[offset + 4] = y >> 8;
buff[offset + 5] = y;
sock._sQlen += 6;
sock.flush(); sock.flush();
}, },
@@ -3111,14 +3079,11 @@ RFB.messages = {
}, },
clientCutText(sock, data, extended = false) { clientCutText(sock, data, extended = false) {
const buff = sock._sQ; sock.sQpush8(6); // msg-type
const offset = sock._sQlen;
buff[offset] = 6; // msg-type sock.sQpush8(0); // padding
sock.sQpush8(0); // padding
buff[offset + 1] = 0; // padding sock.sQpush8(0); // padding
buff[offset + 2] = 0; // padding
buff[offset + 3] = 0; // padding
let length; let length;
if (extended) { if (extended) {
@@ -3127,121 +3092,63 @@ RFB.messages = {
length = data.length; length = data.length;
} }
buff[offset + 4] = length >> 24; sock.sQpush32(length);
buff[offset + 5] = length >> 16; sock.sQpushBytes(data);
buff[offset + 6] = length >> 8; sock.flush();
buff[offset + 7] = length;
sock._sQlen += 8;
// We have to keep track of from where in the data we begin creating the
// buffer for the flush in the next iteration.
let dataOffset = 0;
let remaining = data.length;
while (remaining > 0) {
let flushSize = Math.min(remaining, (sock._sQbufferSize - sock._sQlen));
for (let i = 0; i < flushSize; i++) {
buff[sock._sQlen + i] = data[dataOffset + i];
}
sock._sQlen += flushSize;
sock.flush();
remaining -= flushSize;
dataOffset += flushSize;
}
}, },
setDesktopSize(sock, width, height, id, flags) { setDesktopSize(sock, width, height, id, flags) {
const buff = sock._sQ; sock.sQpush8(251); // msg-type
const offset = sock._sQlen;
buff[offset] = 251; // msg-type sock.sQpush8(0); // padding
buff[offset + 1] = 0; // padding
buff[offset + 2] = width >> 8; // width
buff[offset + 3] = width;
buff[offset + 4] = height >> 8; // height
buff[offset + 5] = height;
buff[offset + 6] = 1; // number-of-screens sock.sQpush16(width);
buff[offset + 7] = 0; // padding sock.sQpush16(height);
sock.sQpush8(1); // number-of-screens
sock.sQpush8(0); // padding
// screen array // screen array
buff[offset + 8] = id >> 24; // id sock.sQpush32(id);
buff[offset + 9] = id >> 16; sock.sQpush16(0); // x-position
buff[offset + 10] = id >> 8; sock.sQpush16(0); // y-position
buff[offset + 11] = id; sock.sQpush16(width);
buff[offset + 12] = 0; // x-position sock.sQpush16(height);
buff[offset + 13] = 0; sock.sQpush32(flags);
buff[offset + 14] = 0; // y-position
buff[offset + 15] = 0;
buff[offset + 16] = width >> 8; // width
buff[offset + 17] = width;
buff[offset + 18] = height >> 8; // height
buff[offset + 19] = height;
buff[offset + 20] = flags >> 24; // flags
buff[offset + 21] = flags >> 16;
buff[offset + 22] = flags >> 8;
buff[offset + 23] = flags;
sock._sQlen += 24;
sock.flush(); sock.flush();
}, },
clientFence(sock, flags, payload) { clientFence(sock, flags, payload) {
const buff = sock._sQ; sock.sQpush8(248); // msg-type
const offset = sock._sQlen;
buff[offset] = 248; // msg-type sock.sQpush8(0); // padding
sock.sQpush8(0); // padding
sock.sQpush8(0); // padding
buff[offset + 1] = 0; // padding sock.sQpush32(flags);
buff[offset + 2] = 0; // padding
buff[offset + 3] = 0; // padding
buff[offset + 4] = flags >> 24; // flags sock.sQpush8(payload.length);
buff[offset + 5] = flags >> 16; sock.sQpushString(payload);
buff[offset + 6] = flags >> 8;
buff[offset + 7] = flags;
const n = payload.length;
buff[offset + 8] = n; // length
for (let i = 0; i < n; i++) {
buff[offset + 9 + i] = payload.charCodeAt(i);
}
sock._sQlen += 9 + n;
sock.flush(); sock.flush();
}, },
enableContinuousUpdates(sock, enable, x, y, width, height) { enableContinuousUpdates(sock, enable, x, y, width, height) {
const buff = sock._sQ; sock.sQpush8(150); // msg-type
const offset = sock._sQlen;
buff[offset] = 150; // msg-type sock.sQpush8(enable);
buff[offset + 1] = enable; // enable-flag
buff[offset + 2] = x >> 8; // x sock.sQpush16(x);
buff[offset + 3] = x; sock.sQpush16(y);
buff[offset + 4] = y >> 8; // y sock.sQpush16(width);
buff[offset + 5] = y; sock.sQpush16(height);
buff[offset + 6] = width >> 8; // width
buff[offset + 7] = width;
buff[offset + 8] = height >> 8; // height
buff[offset + 9] = height;
sock._sQlen += 10;
sock.flush(); sock.flush();
}, },
pixelFormat(sock, depth, trueColor) { pixelFormat(sock, depth, trueColor) {
const buff = sock._sQ;
const offset = sock._sQlen;
let bpp; let bpp;
if (depth > 16) { if (depth > 16) {
@@ -3254,100 +3161,69 @@ RFB.messages = {
const bits = Math.floor(depth/3); const bits = Math.floor(depth/3);
buff[offset] = 0; // msg-type sock.sQpush8(0); // msg-type
buff[offset + 1] = 0; // padding sock.sQpush8(0); // padding
buff[offset + 2] = 0; // padding sock.sQpush8(0); // padding
buff[offset + 3] = 0; // padding sock.sQpush8(0); // padding
buff[offset + 4] = bpp; // bits-per-pixel sock.sQpush8(bpp);
buff[offset + 5] = depth; // depth sock.sQpush8(depth);
buff[offset + 6] = 0; // little-endian sock.sQpush8(0); // little-endian
buff[offset + 7] = trueColor ? 1 : 0; // true-color sock.sQpush8(trueColor ? 1 : 0);
buff[offset + 8] = 0; // red-max sock.sQpush16((1 << bits) - 1); // red-max
buff[offset + 9] = (1 << bits) - 1; // red-max sock.sQpush16((1 << bits) - 1); // green-max
sock.sQpush16((1 << bits) - 1); // blue-max
buff[offset + 10] = 0; // green-max sock.sQpush8(bits * 0); // red-shift
buff[offset + 11] = (1 << bits) - 1; // green-max sock.sQpush8(bits * 1); // green-shift
sock.sQpush8(bits * 2); // blue-shift
buff[offset + 12] = 0; // blue-max sock.sQpush8(0); // padding
buff[offset + 13] = (1 << bits) - 1; // blue-max sock.sQpush8(0); // padding
sock.sQpush8(0); // padding
buff[offset + 14] = bits * 0; // red-shift
buff[offset + 15] = bits * 1; // green-shift
buff[offset + 16] = bits * 2; // blue-shift
buff[offset + 17] = 0; // padding
buff[offset + 18] = 0; // padding
buff[offset + 19] = 0; // padding
sock._sQlen += 20;
sock.flush(); sock.flush();
}, },
clientEncodings(sock, encodings) { clientEncodings(sock, encodings) {
const buff = sock._sQ; sock.sQpush8(2); // msg-type
const offset = sock._sQlen;
buff[offset] = 2; // msg-type sock.sQpush8(0); // padding
buff[offset + 1] = 0; // padding
buff[offset + 2] = encodings.length >> 8; sock.sQpush16(encodings.length);
buff[offset + 3] = encodings.length;
let j = offset + 4;
for (let i = 0; i < encodings.length; i++) { for (let i = 0; i < encodings.length; i++) {
const enc = encodings[i]; sock.sQpush32(encodings[i]);
buff[j] = enc >> 24;
buff[j + 1] = enc >> 16;
buff[j + 2] = enc >> 8;
buff[j + 3] = enc;
j += 4;
} }
sock._sQlen += j - offset;
sock.flush(); sock.flush();
}, },
fbUpdateRequest(sock, incremental, x, y, w, h) { fbUpdateRequest(sock, incremental, x, y, w, h) {
const buff = sock._sQ;
const offset = sock._sQlen;
if (typeof(x) === "undefined") { x = 0; } if (typeof(x) === "undefined") { x = 0; }
if (typeof(y) === "undefined") { y = 0; } if (typeof(y) === "undefined") { y = 0; }
buff[offset] = 3; // msg-type sock.sQpush8(3); // msg-type
buff[offset + 1] = incremental ? 1 : 0;
buff[offset + 2] = (x >> 8) & 0xFF; sock.sQpush8(incremental ? 1 : 0);
buff[offset + 3] = x & 0xFF;
buff[offset + 4] = (y >> 8) & 0xFF; sock.sQpush16(x);
buff[offset + 5] = y & 0xFF; sock.sQpush16(y);
sock.sQpush16(w);
sock.sQpush16(h);
buff[offset + 6] = (w >> 8) & 0xFF;
buff[offset + 7] = w & 0xFF;
buff[offset + 8] = (h >> 8) & 0xFF;
buff[offset + 9] = h & 0xFF;
sock._sQlen += 10;
sock.flush(); sock.flush();
}, },
xvpOp(sock, ver, op) { xvpOp(sock, ver, op) {
const buff = sock._sQ; sock.sQpush8(250); // msg-type
const offset = sock._sQlen;
buff[offset] = 250; // msg-type sock.sQpush8(0); // padding
buff[offset + 1] = 0; // padding
buff[offset + 2] = ver; sock.sQpush8(ver);
buff[offset + 3] = op; sock.sQpush8(op);
sock._sQlen += 4;
sock.flush(); sock.flush();
} }
}; };
+70 -58
View File
@@ -94,27 +94,7 @@ export default class Websock {
return "unknown"; return "unknown";
} }
get sQ() {
return this._sQ;
}
get rQ() {
return this._rQ;
}
get rQi() {
return this._rQi;
}
set rQi(val) {
this._rQi = val;
}
// Receive Queue // Receive Queue
get rQlen() {
return this._rQlen - this._rQi;
}
rQpeek8() { rQpeek8() {
return this._rQ[this._rQi]; return this._rQ[this._rQi];
} }
@@ -141,42 +121,47 @@ export default class Websock {
for (let byte = bytes - 1; byte >= 0; byte--) { for (let byte = bytes - 1; byte >= 0; byte--) {
res += this._rQ[this._rQi++] << (byte * 8); res += this._rQ[this._rQi++] << (byte * 8);
} }
return res; return res >>> 0;
} }
rQshiftStr(len) { rQshiftStr(len) {
if (typeof(len) === 'undefined') { len = this.rQlen; }
let str = ""; let str = "";
// Handle large arrays in steps to avoid long strings on the stack // Handle large arrays in steps to avoid long strings on the stack
for (let i = 0; i < len; i += 4096) { for (let i = 0; i < len; i += 4096) {
let part = this.rQshiftBytes(Math.min(4096, len - i)); let part = this.rQshiftBytes(Math.min(4096, len - i), false);
str += String.fromCharCode.apply(null, part); str += String.fromCharCode.apply(null, part);
} }
return str; return str;
} }
rQshiftBytes(len) { rQshiftBytes(len, copy=true) {
if (typeof(len) === 'undefined') { len = this.rQlen; }
this._rQi += len; this._rQi += len;
return new Uint8Array(this._rQ.buffer, this._rQi - len, len); if (copy) {
return this._rQ.slice(this._rQi - len, this._rQi);
} else {
return this._rQ.subarray(this._rQi - len, this._rQi);
}
} }
rQshiftTo(target, len) { rQshiftTo(target, len) {
if (len === undefined) { len = this.rQlen; }
// TODO: make this just use set with views when using a ArrayBuffer to store the rQ // TODO: make this just use set with views when using a ArrayBuffer to store the rQ
target.set(new Uint8Array(this._rQ.buffer, this._rQi, len)); target.set(new Uint8Array(this._rQ.buffer, this._rQi, len));
this._rQi += len; this._rQi += len;
} }
rQslice(start, end = this.rQlen) { rQpeekBytes(len, copy=true) {
return new Uint8Array(this._rQ.buffer, this._rQi + start, end - start); if (copy) {
return this._rQ.slice(this._rQi, this._rQi + len);
} else {
return this._rQ.subarray(this._rQi, this._rQi + len);
}
} }
// Check to see if we must wait for 'num' bytes (default to FBU.bytes) // Check to see if we must wait for 'num' bytes (default to FBU.bytes)
// to be available in the receive queue. Return true if we need to // to be available in the receive queue. Return true if we need to
// wait (and possibly print a debug message), otherwise false. // wait (and possibly print a debug message), otherwise false.
rQwait(msg, num, goback) { rQwait(msg, num, goback) {
if (this.rQlen < num) { if (this._rQlen - this._rQi < num) {
if (goback) { if (goback) {
if (this._rQi < goback) { if (this._rQi < goback) {
throw new Error("rQwait cannot backup " + goback + " bytes"); throw new Error("rQwait cannot backup " + goback + " bytes");
@@ -190,21 +175,56 @@ export default class Websock {
// Send Queue // Send Queue
sQpush8(num) {
this._sQensureSpace(1);
this._sQ[this._sQlen++] = num;
}
sQpush16(num) {
this._sQensureSpace(2);
this._sQ[this._sQlen++] = (num >> 8) & 0xff;
this._sQ[this._sQlen++] = (num >> 0) & 0xff;
}
sQpush32(num) {
this._sQensureSpace(4);
this._sQ[this._sQlen++] = (num >> 24) & 0xff;
this._sQ[this._sQlen++] = (num >> 16) & 0xff;
this._sQ[this._sQlen++] = (num >> 8) & 0xff;
this._sQ[this._sQlen++] = (num >> 0) & 0xff;
}
sQpushString(str) {
let bytes = str.split('').map(chr => chr.charCodeAt(0));
this.sQpushBytes(new Uint8Array(bytes));
}
sQpushBytes(bytes) {
for (let offset = 0;offset < bytes.length;) {
this._sQensureSpace(1);
let chunkSize = this._sQbufferSize - this._sQlen;
if (chunkSize > bytes.length - offset) {
chunkSize = bytes.length - offset;
}
this._sQ.set(bytes.subarray(offset, chunkSize), this._sQlen);
this._sQlen += chunkSize;
offset += chunkSize;
}
}
flush() { flush() {
if (this._sQlen > 0 && this.readyState === 'open') { if (this._sQlen > 0 && this.readyState === 'open') {
this._websocket.send(this._encodeMessage()); this._websocket.send(new Uint8Array(this._sQ.buffer, 0, this._sQlen));
this._sQlen = 0; this._sQlen = 0;
} }
} }
send(arr) { _sQensureSpace(bytes) {
this._sQ.set(arr, this._sQlen); if (this._sQbufferSize - this._sQlen < bytes) {
this._sQlen += arr.length; this.flush();
this.flush(); }
}
sendString(str) {
this.send(str.split('').map(chr => chr.charCodeAt(0)));
} }
// Event Handlers // Event Handlers
@@ -283,17 +303,12 @@ export default class Websock {
} }
// private methods // private methods
_encodeMessage() {
// Put in a binary arraybuffer
// according to the spec, you can send ArrayBufferViews with the send method
return new Uint8Array(this._sQ.buffer, 0, this._sQlen);
}
// We want to move all the unread data to the start of the queue, // We want to move all the unread data to the start of the queue,
// e.g. compacting. // e.g. compacting.
// The function also expands the receive que if needed, and for // The function also expands the receive que if needed, and for
// performance reasons we combine these two actions to avoid // performance reasons we combine these two actions to avoid
// unneccessary copying. // unnecessary copying.
_expandCompactRQ(minFit) { _expandCompactRQ(minFit) {
// if we're using less than 1/8th of the buffer even with the incoming bytes, compact in place // if we're using less than 1/8th of the buffer even with the incoming bytes, compact in place
// instead of resizing // instead of resizing
@@ -309,7 +324,7 @@ export default class Websock {
// we don't want to grow unboundedly // we don't want to grow unboundedly
if (this._rQbufferSize > MAX_RQ_GROW_SIZE) { if (this._rQbufferSize > MAX_RQ_GROW_SIZE) {
this._rQbufferSize = MAX_RQ_GROW_SIZE; this._rQbufferSize = MAX_RQ_GROW_SIZE;
if (this._rQbufferSize - this.rQlen < minFit) { if (this._rQbufferSize - (this._rQlen - this._rQi) < minFit) {
throw new Error("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit"); throw new Error("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit");
} }
} }
@@ -327,25 +342,22 @@ export default class Websock {
} }
// push arraybuffer values onto the end of the receive que // push arraybuffer values onto the end of the receive que
_DecodeMessage(data) { _recvMessage(e) {
const u8 = new Uint8Array(data); if (this._rQlen == this._rQi) {
// All data has now been processed, this means we
// can reset the receive queue.
this._rQlen = 0;
this._rQi = 0;
}
const u8 = new Uint8Array(e.data);
if (u8.length > this._rQbufferSize - this._rQlen) { if (u8.length > this._rQbufferSize - this._rQlen) {
this._expandCompactRQ(u8.length); this._expandCompactRQ(u8.length);
} }
this._rQ.set(u8, this._rQlen); this._rQ.set(u8, this._rQlen);
this._rQlen += u8.length; this._rQlen += u8.length;
}
_recvMessage(e) { if (this._rQlen - this._rQi > 0) {
this._DecodeMessage(e.data);
if (this.rQlen > 0) {
this._eventHandlers.message(); this._eventHandlers.message();
if (this._rQlen == this._rQi) {
// All data has now been processed, this means we
// can reset the receive queue.
this._rQlen = 0;
this._rQi = 0;
}
} else { } else {
Log.Debug("Ignoring empty message"); Log.Debug("Ignoring empty message");
} }
-6
View File
@@ -81,9 +81,3 @@ None
| blitImage | (x, y, width, height, arr, offset, from_queue) | Blit pixels (of R,G,B,A) to the display | blitImage | (x, y, width, height, arr, offset, from_queue) | Blit pixels (of R,G,B,A) to the display
| drawImage | (img, x, y) | Draw image and track damage | drawImage | (img, x, y) | Draw image and track damage
| autoscale | (containerWidth, containerHeight) | Scale the display | autoscale | (containerWidth, containerHeight) | Scale the display
### 2.2.3 Callbacks
| name | parameters | description
| ------- | ---------- | ------------
| onflush | () | A display flush has been requested and we are now ready to resume FBU processing
+3 -3
View File
@@ -3,13 +3,13 @@
.SH NAME .SH NAME
novnc_proxy - noVNC proxy server novnc_proxy - noVNC proxy server
.SH SYNOPSIS .SH SYNOPSIS
.B novnc_proxy [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only] .B novnc_proxy [--listen [HOST:]PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only]
Starts the WebSockets proxy and a mini-webserver and Starts the WebSockets proxy and a mini-webserver and
provides a cut-and-paste URL to go to. provides a cut-and-paste URL to go to.
--listen PORT Port for proxy/webserver to listen on --listen [HOST:]PORT Port for proxy/webserver to listen on
Default: 6080 Default: 6080 (on all interfaces)
--vnc VNC_HOST:PORT VNC server host:port proxy target --vnc VNC_HOST:PORT VNC server host:port proxy target
Default: localhost:5900 Default: localhost:5900
--cert CERT Path to combined cert/key file, or just --cert CERT Path to combined cert/key file, or just
-6
View File
@@ -39,17 +39,11 @@
"homepage": "https://github.com/novnc/noVNC", "homepage": "https://github.com/novnc/noVNC",
"devDependencies": { "devDependencies": {
"@babel/core": "latest", "@babel/core": "latest",
"@babel/plugin-syntax-dynamic-import": "latest",
"@babel/plugin-transform-modules-commonjs": "latest",
"@babel/preset-env": "latest", "@babel/preset-env": "latest",
"@babel/cli": "latest",
"babel-plugin-import-redirect": "latest", "babel-plugin-import-redirect": "latest",
"browserify": "latest", "browserify": "latest",
"babelify": "latest",
"core-js": "latest",
"chai": "latest", "chai": "latest",
"commander": "latest", "commander": "latest",
"es-module-loader": "latest",
"eslint": "latest", "eslint": "latest",
"fs-extra": "latest", "fs-extra": "latest",
"jsdom": "latest", "jsdom": "latest",
+119 -80
View File
@@ -8,8 +8,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: noVNC 1.1.0\n" "Project-Id-Version: noVNC 1.1.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n" "Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2020-07-03 16:11+0200\n" "POT-Creation-Date: 2022-12-27 15:24+0100\n"
"PO-Revision-Date: 2021-01-15 12:37+0900\n" "PO-Revision-Date: 2023-03-21 12:42+0900\n"
"Last-Translator: nnn1590 <nnn1590@nnn1590.org>\n" "Last-Translator: nnn1590 <nnn1590@nnn1590.org>\n"
"Language-Team: Japanese\n" "Language-Team: Japanese\n"
"Language: ja\n" "Language: ja\n"
@@ -19,286 +19,325 @@ msgstr ""
"Plural-Forms: nplurals=1; plural=0;\n" "Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Poedit 2.3\n" "X-Generator: Poedit 2.3\n"
#: ../app/ui.js:394 #: ../app/ui.js:69
msgid "HTTPS is required for full functionality"
msgstr "すべての機能を使用するにはHTTPS接続が必要です"
#: ../app/ui.js:410
msgid "Connecting..." msgid "Connecting..."
msgstr "接続しています..." msgstr "接続しています..."
#: ../app/ui.js:401 #: ../app/ui.js:417
msgid "Disconnecting..." msgid "Disconnecting..."
msgstr "切断しています..." msgstr "切断しています..."
#: ../app/ui.js:407 #: ../app/ui.js:423
msgid "Reconnecting..." msgid "Reconnecting..."
msgstr "再接続しています..." msgstr "再接続しています..."
#: ../app/ui.js:412 #: ../app/ui.js:428
msgid "Internal error" msgid "Internal error"
msgstr "内部エラー" msgstr "内部エラー"
#: ../app/ui.js:1008 #: ../app/ui.js:1026
msgid "Must set host" msgid "Must set host"
msgstr "ホストを設定する必要があります" msgstr "ホストを設定する必要があります"
#: ../app/ui.js:1090 #: ../app/ui.js:1110
msgid "Connected (encrypted) to " msgid "Connected (encrypted) to "
msgstr "接続しました (暗号化済み): " msgstr "接続しました (暗号化済み): "
#: ../app/ui.js:1092 #: ../app/ui.js:1112
msgid "Connected (unencrypted) to " msgid "Connected (unencrypted) to "
msgstr "接続しました (暗号化されていません): " msgstr "接続しました (暗号化されていません): "
#: ../app/ui.js:1115 #: ../app/ui.js:1135
msgid "Something went wrong, connection is closed" msgid "Something went wrong, connection is closed"
msgstr "何らかの問題で、接続が閉じられました" msgstr "何らかの問題で、接続が閉じられました"
#: ../app/ui.js:1118 #: ../app/ui.js:1138
msgid "Failed to connect to server" msgid "Failed to connect to server"
msgstr "サーバーへの接続に失敗しました" msgstr "サーバーへの接続に失敗しました"
#: ../app/ui.js:1128 #: ../app/ui.js:1150
msgid "Disconnected" msgid "Disconnected"
msgstr "切断しました" msgstr "切断しました"
#: ../app/ui.js:1143 #: ../app/ui.js:1165
msgid "New connection has been rejected with reason: " msgid "New connection has been rejected with reason: "
msgstr "新規接続は次の理由で拒否されました: " msgstr "新規接続は次の理由で拒否されました: "
#: ../app/ui.js:1146 #: ../app/ui.js:1168
msgid "New connection has been rejected" msgid "New connection has been rejected"
msgstr "新規接続は拒否されました" msgstr "新規接続は拒否されました"
#: ../app/ui.js:1181 #: ../app/ui.js:1234
msgid "Credentials are required" msgid "Credentials are required"
msgstr "資格情報が必要です" msgstr "資格情報が必要です"
#: ../vnc.html:74 #: ../vnc.html:57
msgid "noVNC encountered an error:" msgid "noVNC encountered an error:"
msgstr "noVNC でエラーが発生しました:" msgstr "noVNC でエラーが発生しました:"
#: ../vnc.html:84 #: ../vnc.html:67
msgid "Hide/Show the control bar" msgid "Hide/Show the control bar"
msgstr "コントロールバーを隠す/表示する" msgstr "コントロールバーを隠す/表示する"
#: ../vnc.html:91 #: ../vnc.html:76
msgid "Drag" msgid "Drag"
msgstr "ドラッグ" msgstr "ドラッグ"
#: ../vnc.html:91 #: ../vnc.html:76
msgid "Move/Drag Viewport" msgid "Move/Drag Viewport"
msgstr "ビューポートを移動/ドラッグ" msgstr "ビューポートを移動/ドラッグ"
#: ../vnc.html:97 #: ../vnc.html:82
msgid "Keyboard" msgid "Keyboard"
msgstr "キーボード" msgstr "キーボード"
#: ../vnc.html:97 #: ../vnc.html:82
msgid "Show Keyboard" msgid "Show Keyboard"
msgstr "キーボードを表示" msgstr "キーボードを表示"
#: ../vnc.html:102 #: ../vnc.html:87
msgid "Extra keys" msgid "Extra keys"
msgstr "追加キー" msgstr "追加キー"
#: ../vnc.html:102 #: ../vnc.html:87
msgid "Show Extra Keys" msgid "Show Extra Keys"
msgstr "追加キーを表示" msgstr "追加キーを表示"
#: ../vnc.html:107 #: ../vnc.html:92
msgid "Ctrl" msgid "Ctrl"
msgstr "Ctrl" msgstr "Ctrl"
#: ../vnc.html:107 #: ../vnc.html:92
msgid "Toggle Ctrl" msgid "Toggle Ctrl"
msgstr "Ctrl キーを切り替え" msgstr "Ctrl キーをトグル"
#: ../vnc.html:110 #: ../vnc.html:95
msgid "Alt" msgid "Alt"
msgstr "Alt" msgstr "Alt"
#: ../vnc.html:110 #: ../vnc.html:95
msgid "Toggle Alt" msgid "Toggle Alt"
msgstr "Alt キーを切り替え" msgstr "Alt キーをトグル"
#: ../vnc.html:113 #: ../vnc.html:98
msgid "Toggle Windows" msgid "Toggle Windows"
msgstr "Windows キーを切り替え" msgstr "Windows キーをトグル"
#: ../vnc.html:113 #: ../vnc.html:98
msgid "Windows" msgid "Windows"
msgstr "Windows" msgstr "Windows"
#: ../vnc.html:116 #: ../vnc.html:101
msgid "Send Tab" msgid "Send Tab"
msgstr "Tab キーを送信" msgstr "Tab キーを送信"
#: ../vnc.html:116 #: ../vnc.html:101
msgid "Tab" msgid "Tab"
msgstr "Tab" msgstr "Tab"
#: ../vnc.html:119 #: ../vnc.html:104
msgid "Esc" msgid "Esc"
msgstr "Esc" msgstr "Esc"
#: ../vnc.html:119 #: ../vnc.html:104
msgid "Send Escape" msgid "Send Escape"
msgstr "Escape キーを送信" msgstr "Escape キーを送信"
#: ../vnc.html:122 #: ../vnc.html:107
msgid "Ctrl+Alt+Del" msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Del" msgstr "Ctrl+Alt+Del"
#: ../vnc.html:122 #: ../vnc.html:107
msgid "Send Ctrl-Alt-Del" msgid "Send Ctrl-Alt-Del"
msgstr "Ctrl-Alt-Del を送信" msgstr "Ctrl-Alt-Del を送信"
#: ../vnc.html:129 #: ../vnc.html:114
msgid "Shutdown/Reboot" msgid "Shutdown/Reboot"
msgstr "シャットダウン/再起動" msgstr "シャットダウン/再起動"
#: ../vnc.html:129 #: ../vnc.html:114
msgid "Shutdown/Reboot..." msgid "Shutdown/Reboot..."
msgstr "シャットダウン/再起動..." msgstr "シャットダウン/再起動..."
#: ../vnc.html:135 #: ../vnc.html:120
msgid "Power" msgid "Power"
msgstr "電源" msgstr "電源"
#: ../vnc.html:137 #: ../vnc.html:122
msgid "Shutdown" msgid "Shutdown"
msgstr "シャットダウン" msgstr "シャットダウン"
#: ../vnc.html:138 #: ../vnc.html:123
msgid "Reboot" msgid "Reboot"
msgstr "再起動" msgstr "再起動"
#: ../vnc.html:139 #: ../vnc.html:124
msgid "Reset" msgid "Reset"
msgstr "リセット" msgstr "リセット"
#: ../vnc.html:144 ../vnc.html:150 #: ../vnc.html:129 ../vnc.html:135
msgid "Clipboard" msgid "Clipboard"
msgstr "クリップボード" msgstr "クリップボード"
#: ../vnc.html:154 #: ../vnc.html:137
msgid "Clear" msgid "Edit clipboard content in the textarea below."
msgstr "クリア" msgstr "以下の入力欄からクリップボードの内容を編集できます。"
#: ../vnc.html:160 #: ../vnc.html:145
msgid "Fullscreen" msgid "Full Screen"
msgstr "全画面表示" msgstr "全画面表示"
#: ../vnc.html:165 ../vnc.html:172 #: ../vnc.html:150 ../vnc.html:156
msgid "Settings" msgid "Settings"
msgstr "設定" msgstr "設定"
#: ../vnc.html:175 #: ../vnc.html:160
msgid "Shared Mode" msgid "Shared Mode"
msgstr "共有モード" msgstr "共有モード"
#: ../vnc.html:178 #: ../vnc.html:163
msgid "View Only" msgid "View Only"
msgstr "表示のみ" msgstr "表示専用"
#: ../vnc.html:182 #: ../vnc.html:167
msgid "Clip to Window" msgid "Clip to Window"
msgstr "ウィンドウにクリップ" msgstr "ウィンドウにクリップ"
#: ../vnc.html:185 #: ../vnc.html:170
msgid "Scaling Mode:" msgid "Scaling Mode:"
msgstr "スケーリングモード:" msgstr "スケーリングモード:"
#: ../vnc.html:187 #: ../vnc.html:172
msgid "None" msgid "None"
msgstr "なし" msgstr "なし"
#: ../vnc.html:188 #: ../vnc.html:173
msgid "Local Scaling" msgid "Local Scaling"
msgstr "ローカルスケーリング" msgstr "ローカルスケーリング"
#: ../vnc.html:189 #: ../vnc.html:174
msgid "Remote Resizing" msgid "Remote Resizing"
msgstr "リモートでリサイズ" msgstr "リモートでリサイズ"
#: ../vnc.html:194 #: ../vnc.html:179
msgid "Advanced" msgid "Advanced"
msgstr "高度" msgstr "高度"
#: ../vnc.html:197 #: ../vnc.html:182
msgid "Quality:" msgid "Quality:"
msgstr "品質:" msgstr "品質:"
#: ../vnc.html:201 #: ../vnc.html:186
msgid "Compression level:" msgid "Compression level:"
msgstr "圧縮レベル:" msgstr "圧縮レベル:"
#: ../vnc.html:206 #: ../vnc.html:191
msgid "Repeater ID:" msgid "Repeater ID:"
msgstr "リピーター ID:" msgstr "リピーター ID:"
#: ../vnc.html:210 #: ../vnc.html:195
msgid "WebSocket" msgid "WebSocket"
msgstr "WebSocket" msgstr "WebSocket"
#: ../vnc.html:213 #: ../vnc.html:198
msgid "Encrypt" msgid "Encrypt"
msgstr "暗号化" msgstr "暗号化"
#: ../vnc.html:216 #: ../vnc.html:201
msgid "Host:" msgid "Host:"
msgstr "ホスト:" msgstr "ホスト:"
#: ../vnc.html:220 #: ../vnc.html:205
msgid "Port:" msgid "Port:"
msgstr "ポート:" msgstr "ポート:"
#: ../vnc.html:224 #: ../vnc.html:209
msgid "Path:" msgid "Path:"
msgstr "パス:" msgstr "パス:"
#: ../vnc.html:231 #: ../vnc.html:216
msgid "Automatic Reconnect" msgid "Automatic Reconnect"
msgstr "自動再接続" msgstr "自動再接続"
#: ../vnc.html:234 #: ../vnc.html:219
msgid "Reconnect Delay (ms):" msgid "Reconnect Delay (ms):"
msgstr "再接続する遅延 (ミリ秒):" msgstr "再接続する遅延 (ミリ秒):"
#: ../vnc.html:239 #: ../vnc.html:224
msgid "Show Dot when No Cursor" msgid "Show Dot when No Cursor"
msgstr "カーソルがないときにドットを表示" msgstr "カーソルがないときにドットを表示する"
#: ../vnc.html:244 #: ../vnc.html:229
msgid "Logging:" msgid "Logging:"
msgstr "ロギング:" msgstr "ロギング:"
#: ../vnc.html:253 #: ../vnc.html:238
msgid "Version:" msgid "Version:"
msgstr "バージョン:" msgstr "バージョン:"
#: ../vnc.html:261 #: ../vnc.html:246
msgid "Disconnect" msgid "Disconnect"
msgstr "切断" msgstr "切断"
#: ../vnc.html:280 #: ../vnc.html:269
msgid "Connect" msgid "Connect"
msgstr "接続" msgstr "接続"
#: ../vnc.html:290 #: ../vnc.html:278
msgid "Server identity"
msgstr "サーバーの識別情報"
#: ../vnc.html:281
msgid "The server has provided the following identifying information:"
msgstr "サーバーは以下の識別情報を提供しています:"
#: ../vnc.html:285
msgid "Fingerprint:"
msgstr "フィンガープリント:"
#: ../vnc.html:288
msgid ""
"Please verify that the information is correct and press \"Approve\". "
"Otherwise press \"Reject\"."
msgstr ""
"この情報が正しい場合は「承認」を、そうでない場合は「拒否」を押してく"
"ださい。"
#: ../vnc.html:293
msgid "Approve"
msgstr "承認"
#: ../vnc.html:294
msgid "Reject"
msgstr "拒否"
#: ../vnc.html:302
msgid "Credentials"
msgstr "資格情報"
#: ../vnc.html:306
msgid "Username:" msgid "Username:"
msgstr "ユーザー名:" msgstr "ユーザー名:"
#: ../vnc.html:294 #: ../vnc.html:310
msgid "Password:" msgid "Password:"
msgstr "パスワード:" msgstr "パスワード:"
#: ../vnc.html:298 #: ../vnc.html:314
msgid "Send Credentials" msgid "Send Credentials"
msgstr "資格情報を送信" msgstr "資格情報を送信"
#: ../vnc.html:308 #: ../vnc.html:323
msgid "Cancel" msgid "Cancel"
msgstr "キャンセル" msgstr "キャンセル"
#~ msgid "Clear"
#~ msgstr "クリア"
#~ msgid "Password is required" #~ msgid "Password is required"
#~ msgstr "パスワードが必要です" #~ msgstr "パスワードが必要です"
+2 -2
View File
@@ -1,5 +1,5 @@
name: novnc name: novnc
base: core18 # the base snap is the execution environment for this snap base: core22 # the base snap is the execution environment for this snap
version: git version: git
summary: Open Source VNC client using HTML5 (WebSockets, Canvas) summary: Open Source VNC client using HTML5 (WebSockets, Canvas)
description: | description: |
@@ -42,7 +42,7 @@ parts:
- jq - jq
websockify: websockify:
source: https://github.com/novnc/websockify/archive/v0.9.0.tar.gz source: https://github.com/novnc/websockify/archive/v0.11.0.tar.gz
plugin: python plugin: python
stage-packages: stage-packages:
- python3-numpy - python3-numpy
+10 -6
View File
@@ -42,7 +42,7 @@ export default class FakeWebSocket {
} }
_getSentData() { _getSentData() {
const res = new Uint8Array(this._sendQueue.buffer, 0, this.bufferedAmount); const res = this._sendQueue.slice(0, this.bufferedAmount);
this.bufferedAmount = 0; this.bufferedAmount = 0;
return res; return res;
} }
@@ -55,11 +55,15 @@ export default class FakeWebSocket {
} }
_receiveData(data) { _receiveData(data) {
// Break apart the data to expose bugs where we assume data is if (data.length < 4096) {
// neatly packaged // Break apart the data to expose bugs where we assume data is
for (let i = 0;i < data.length;i++) { // neatly packaged
let buf = data.subarray(i, i+1); for (let i = 0;i < data.length;i++) {
this.onmessage(new MessageEvent("message", { 'data': buf })); let buf = data.slice(i, i+1);
this.onmessage(new MessageEvent("message", { 'data': buf.buffer }));
}
} else {
this.onmessage(new MessageEvent("message", { 'data': data.buffer }));
} }
} }
} }
+4 -1
View File
@@ -24,7 +24,7 @@ function loadFile() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const script = document.createElement("script"); const script = document.createElement("script");
script.onload = resolve; script.onload = resolve;
script.onerror = reject; script.onerror = () => { reject("Failed to load " + fname); };
document.body.appendChild(script); document.body.appendChild(script);
script.src = "../recordings/" + fname; script.src = "../recordings/" + fname;
}); });
@@ -200,6 +200,9 @@ function start() {
player.onrfbdisconnected = (evt) => { player.onrfbdisconnected = (evt) => {
if (!evt.detail.clean) { if (!evt.detail.clean) {
message(`noVNC sent disconnected during iteration ${evt.detail.iteration} frame ${evt.detail.frame}`); message(`noVNC sent disconnected during iteration ${evt.detail.iteration} frame ${evt.detail.frame}`);
document.getElementById('startButton').disabled = false;
document.getElementById('startButton').value = "Start";
} }
}; };
player.onfinish = (evt) => { player.onfinish = (evt) => {
+6 -13
View File
@@ -131,12 +131,10 @@ export default class RecordingPlayer {
_doPacket() { _doPacket() {
// Avoid having excessive queue buildup in non-realtime mode // Avoid having excessive queue buildup in non-realtime mode
if (this._trafficManagement && this._rfb._flushing) { if (this._trafficManagement && this._rfb._flushing) {
const orig = this._rfb._display.onflush; this._rfb.flush()
this._rfb._display.onflush = () => { .then(() => {
this._rfb._display.onflush = orig; this._doPacket();
this._rfb._onFlush(); });
this._doPacket();
};
return; return;
} }
@@ -150,13 +148,8 @@ export default class RecordingPlayer {
_finish() { _finish() {
if (this._rfb._display.pending()) { if (this._rfb._display.pending()) {
this._rfb._display.onflush = () => { this._rfb._display.flush()
if (this._rfb._flushing) { .then(() => { this._finish(); });
this._rfb._onFlush();
}
this._finish();
};
this._rfb._display.flush();
} else { } else {
this._running = false; this._running = false;
this._ws.onclose({code: 1000, reason: ""}); this._ws.onclose({code: 1000, reason: ""});
+18 -9
View File
@@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) { function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock; let sock;
let done = false;
sock = new Websock; sock = new Websock;
sock.open("ws://example.com"); sock.open("ws://example.com");
sock.on('message', () => { sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth); done = decoder.decodeRect(x, y, width, height, sock, display, depth);
}); });
// Empty messages are filtered at multiple layers, so we need to // Empty messages are filtered at multiple layers, so we need to
// do a direct call // do a direct call
if (data.length === 0) { if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth); done = decoder.decodeRect(x, y, width, height, sock, display, depth);
} else { } else {
sock._websocket._receiveData(new Uint8Array(data)); sock._websocket._receiveData(new Uint8Array(data));
} }
display.flip(); display.flip();
return done;
} }
describe('CopyRect Decoder', function () { describe('CopyRect Decoder', function () {
@@ -47,12 +50,15 @@ describe('CopyRect Decoder', function () {
display.fillRect(0, 0, 2, 2, [ 0x00, 0x00, 0xff ]); display.fillRect(0, 0, 2, 2, [ 0x00, 0x00, 0xff ]);
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]); display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 0, 2, 2, 2, let done;
[0x00, 0x02, 0x00, 0x00], done = testDecodeRect(decoder, 0, 2, 2, 2,
display, 24); [0x00, 0x02, 0x00, 0x00],
testDecodeRect(decoder, 2, 2, 2, 2, display, 24);
[0x00, 0x00, 0x00, 0x00], expect(done).to.be.true;
display, 24); done = testDecodeRect(decoder, 2, 2, 2, 2,
[0x00, 0x00, 0x00, 0x00],
display, 24);
expect(done).to.be.true;
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -69,7 +75,9 @@ describe('CopyRect Decoder', function () {
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]); display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]); display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, [0x00, 0x00, 0x00, 0x00], display, 24); let done = testDecodeRect(decoder, 1, 2, 0, 0,
[0x00, 0x00, 0x00, 0x00],
display, 24);
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -78,6 +86,7 @@ describe('CopyRect Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]); ]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData); expect(display).to.have.displayed(targetData);
}); });
}); });
+7 -11
View File
@@ -298,14 +298,11 @@ describe('Display/Canvas Helper', function () {
expect(display).to.have.displayed(checkedData); expect(display).to.have.displayed(checkedData);
}); });
it('should support drawing images via #imageRect', function (done) { it('should support drawing images via #imageRect', async function () {
display.imageRect(0, 0, 4, 4, "image/png", makeImagePng(checkedData, 4, 4)); display.imageRect(0, 0, 4, 4, "image/png", makeImagePng(checkedData, 4, 4));
display.flip(); display.flip();
display.onflush = () => { await display.flush();
expect(display).to.have.displayed(checkedData); expect(display).to.have.displayed(checkedData);
done();
};
display.flush();
}); });
it('should support blit images with true color via #blitImage', function () { it('should support blit images with true color via #blitImage', function () {
@@ -360,12 +357,11 @@ describe('Display/Canvas Helper', function () {
expect(img.addEventListener).to.have.been.calledOnce; expect(img.addEventListener).to.have.been.calledOnce;
}); });
it('should call callback when queue is flushed', function () { it('should resolve promise when queue is flushed', async function () {
display.onflush = sinon.spy();
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]); display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
expect(display.onflush).to.not.have.been.called; let promise = display.flush();
display.flush(); expect(promise).to.be.an.instanceOf(Promise);
expect(display.onflush).to.have.been.calledOnce; await promise;
}); });
it('should draw a blit image on type "blit"', function () { it('should draw a blit image on type "blit"', function () {
+19 -9
View File
@@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) { function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock; let sock;
let done = false;
sock = new Websock; sock = new Websock;
sock.open("ws://example.com"); sock.open("ws://example.com");
sock.on('message', () => { sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth); done = decoder.decodeRect(x, y, width, height, sock, display, depth);
}); });
// Empty messages are filtered at multiple layers, so we need to // Empty messages are filtered at multiple layers, so we need to
// do a direct call // do a direct call
if (data.length === 0) { if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth); done = decoder.decodeRect(x, y, width, height, sock, display, depth);
} else { } else {
sock._websocket._receiveData(new Uint8Array(data)); sock._websocket._receiveData(new Uint8Array(data));
} }
display.flip(); display.flip();
return done;
} }
function push32(arr, num) { function push32(arr, num) {
@@ -62,7 +65,7 @@ describe('Hextile Decoder', function () {
data.push(2 | (2 << 4)); // x: 2, y: 2 data.push(2 | (2 << 4)); // x: 2, y: 2
data.push(1 | (1 << 4)); // width: 2, height: 2 data.push(1 | (1 << 4)); // width: 2, height: 2
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24); let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -71,6 +74,7 @@ describe('Hextile Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]); ]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData); expect(display).to.have.displayed(targetData);
}); });
@@ -92,8 +96,9 @@ describe('Hextile Decoder', function () {
data.push(0); data.push(0);
} }
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24); let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData); expect(display).to.have.displayed(targetData);
}); });
@@ -102,13 +107,14 @@ describe('Hextile Decoder', function () {
data.push(0x02); data.push(0x02);
push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24); let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let expected = []; let expected = [];
for (let i = 0; i < 16; i++) { for (let i = 0; i < 16; i++) {
push32(expected, 0x00ff00ff); push32(expected, 0x00ff00ff);
} }
expect(done).to.be.true;
expect(display).to.have.displayed(new Uint8Array(expected)); expect(display).to.have.displayed(new Uint8Array(expected));
}); });
@@ -125,7 +131,7 @@ describe('Hextile Decoder', function () {
// send an empty frame // send an empty frame
data.push(0x00); data.push(0x00);
testDecodeRect(decoder, 0, 0, 32, 4, data, display, 24); let done = testDecodeRect(decoder, 0, 0, 32, 4, data, display, 24);
let expected = []; let expected = [];
for (let i = 0; i < 16; i++) { for (let i = 0; i < 16; i++) {
@@ -135,6 +141,7 @@ describe('Hextile Decoder', function () {
push32(expected, 0x00ff00ff); // rect 2: same bkground color push32(expected, 0x00ff00ff); // rect 2: same bkground color
} }
expect(done).to.be.true;
expect(display).to.have.displayed(new Uint8Array(expected)); expect(display).to.have.displayed(new Uint8Array(expected));
}); });
@@ -156,7 +163,7 @@ describe('Hextile Decoder', function () {
data.push(2 | (2 << 4)); // x: 2, y: 2 data.push(2 | (2 << 4)); // x: 2, y: 2
data.push(1 | (1 << 4)); // width: 2, height: 2 data.push(1 | (1 << 4)); // width: 2, height: 2
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24); let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -165,6 +172,7 @@ describe('Hextile Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]); ]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData); expect(display).to.have.displayed(targetData);
}); });
@@ -190,7 +198,7 @@ describe('Hextile Decoder', function () {
data.push(0); // x: 0, y: 0 data.push(0); // x: 0, y: 0
data.push(1 | (1 << 4)); // width: 2, height: 2 data.push(1 | (1 << 4)); // width: 2, height: 2
testDecodeRect(decoder, 0, 0, 4, 17, data, display, 24); let done = testDecodeRect(decoder, 0, 0, 4, 17, data, display, 24);
let targetData = [ let targetData = [
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -205,6 +213,7 @@ describe('Hextile Decoder', function () {
} }
expected = expected.concat(targetData.slice(0, 16)); expected = expected.concat(targetData.slice(0, 16));
expect(done).to.be.true;
expect(display).to.have.displayed(new Uint8Array(expected)); expect(display).to.have.displayed(new Uint8Array(expected));
}); });
@@ -218,7 +227,7 @@ describe('Hextile Decoder', function () {
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]); display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]); display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, [], display, 24); let done = testDecodeRect(decoder, 1, 2, 0, 0, [], display, 24);
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -227,6 +236,7 @@ describe('Hextile Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]); ]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData); expect(display).to.have.displayed(targetData);
}); });
}); });
+21 -17
View File
@@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) { function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock; let sock;
let done = false;
sock = new Websock; sock = new Websock;
sock.open("ws://example.com"); sock.open("ws://example.com");
sock.on('message', () => { sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth); done = decoder.decodeRect(x, y, width, height, sock, display, depth);
}); });
// Empty messages are filtered at multiple layers, so we need to // Empty messages are filtered at multiple layers, so we need to
// do a direct call // do a direct call
if (data.length === 0) { if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth); done = decoder.decodeRect(x, y, width, height, sock, display, depth);
} else { } else {
sock._websocket._receiveData(new Uint8Array(data)); sock._websocket._receiveData(new Uint8Array(data));
} }
display.flip(); display.flip();
return done;
} }
describe('JPEG Decoder', function () { describe('JPEG Decoder', function () {
@@ -41,7 +44,7 @@ describe('JPEG Decoder', function () {
display.resize(4, 4); display.resize(4, 4);
}); });
it('should handle JPEG rects', function (done) { it('should handle JPEG rects', async function () {
let data = [ let data = [
// JPEG data // JPEG data
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46,
@@ -131,7 +134,8 @@ describe('JPEG Decoder', function () {
0xff, 0xd9, 0xff, 0xd9,
]; ];
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24); let decodeDone = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
expect(decodeDone).to.be.true;
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255,
@@ -147,14 +151,11 @@ describe('JPEG Decoder', function () {
return diff < 5; return diff < 5;
} }
display.onflush = () => { await display.flush();
expect(display).to.have.displayed(targetData, almost); expect(display).to.have.displayed(targetData, almost);
done();
};
display.flush();
}); });
it('should handle JPEG rects without Huffman and quantification tables', function (done) { it('should handle JPEG rects without Huffman and quantification tables', async function () {
let data1 = [ let data1 = [
// JPEG data // JPEG data
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46,
@@ -244,7 +245,12 @@ describe('JPEG Decoder', function () {
0xff, 0xd9, 0xff, 0xd9,
]; ];
testDecodeRect(decoder, 0, 0, 4, 4, data1, display, 24); let decodeDone;
decodeDone = testDecodeRect(decoder, 0, 0, 4, 4, data1, display, 24);
expect(decodeDone).to.be.true;
display.fillRect(0, 0, 4, 4, [128, 128, 128, 255]);
let data2 = [ let data2 = [
// JPEG data // JPEG data
@@ -263,7 +269,8 @@ describe('JPEG Decoder', function () {
0xcf, 0xff, 0x00, 0x0b, 0xab, 0x1f, 0xff, 0xd9, 0xcf, 0xff, 0x00, 0x0b, 0xab, 0x1f, 0xff, 0xd9,
]; ];
testDecodeRect(decoder, 0, 0, 4, 4, data2, display, 24); decodeDone = testDecodeRect(decoder, 0, 0, 4, 4, data2, display, 24);
expect(decodeDone).to.be.true;
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255,
@@ -279,10 +286,7 @@ describe('JPEG Decoder', function () {
return diff < 5; return diff < 5;
} }
display.onflush = () => { await display.flush();
expect(display).to.have.displayed(targetData, almost); expect(display).to.have.displayed(targetData, almost);
done();
};
display.flush();
}); });
}); });
+48
View File
@@ -14,6 +14,10 @@ describe('Key Event Handling', function () {
} }
e.stopPropagation = sinon.spy(); e.stopPropagation = sinon.spy();
e.preventDefault = sinon.spy(); e.preventDefault = sinon.spy();
e.getModifierState = function (key) {
return e[key];
};
return e; return e;
} }
@@ -310,6 +314,50 @@ describe('Key Event Handling', function () {
}); });
}); });
describe('Modifier status info', function () {
let origNavigator;
beforeEach(function () {
// window.navigator is a protected read-only property in many
// environments, so we need to redefine it whilst running these
// tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
Object.defineProperty(window, "navigator", {value: {}});
});
afterEach(function () {
Object.defineProperty(window, "navigator", origNavigator);
});
it('should provide caps lock state', function () {
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'A', NumLock: false, CapsLock: true}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x41, "KeyA", true, false, true);
});
it('should provide num lock state', function () {
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'A', NumLock: true, CapsLock: false}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x41, "KeyA", true, true, false);
});
it('should have no num lock state on mac', function () {
window.navigator.platform = "Mac";
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'A', NumLock: false, CapsLock: true}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x41, "KeyA", true, null, true);
});
});
describe('Japanese IM keys on Windows', function () { describe('Japanese IM keys on Windows', function () {
let origNavigator; let origNavigator;
beforeEach(function () { beforeEach(function () {
+122 -37
View File
@@ -1,61 +1,146 @@
const expect = chai.expect; const expect = chai.expect;
import { l10n } from '../app/localization.js'; import _, { Localizer, l10n } from '../app/localization.js';
describe('Localization', function () { describe('Localization', function () {
"use strict"; "use strict";
let origNavigator;
let fetch;
beforeEach(function () {
// window.navigator is a protected read-only property in many
// environments, so we need to redefine it whilst running these
// tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
Object.defineProperty(window, "navigator", {value: {}});
window.navigator.languages = [];
fetch = sinon.stub(window, "fetch");
fetch.resolves(new Response("{}"));
});
afterEach(function () {
fetch.restore();
Object.defineProperty(window, "navigator", origNavigator);
});
describe('Singleton', function () {
it('should export a singleton object', function () {
expect(l10n).to.be.instanceOf(Localizer);
});
it('should export a singleton translation function', async function () {
// FIXME: Can we use some spy instead?
window.navigator.languages = ["de"];
fetch.resolves(new Response(JSON.stringify({ "Foobar": "gazonk" })));
await l10n.setup(["de"]);
expect(_("Foobar")).to.equal("gazonk");
});
});
describe('language selection', function () { describe('language selection', function () {
let origNavigator;
beforeEach(function () {
// window.navigator is a protected read-only property in many
// environments, so we need to redefine it whilst running these
// tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
Object.defineProperty(window, "navigator", {value: {}});
window.navigator.languages = [];
});
afterEach(function () {
Object.defineProperty(window, "navigator", origNavigator);
});
it('should use English by default', function () { it('should use English by default', function () {
expect(l10n.language).to.equal('en'); let lclz = new Localizer();
expect(lclz.language).to.equal('en');
}); });
it('should use English if no user language matches', function () { it('should use English if no user language matches', async function () {
window.navigator.languages = ["nl", "de"]; window.navigator.languages = ["nl", "de"];
l10n.setup(["es", "fr"]); let lclz = new Localizer();
expect(l10n.language).to.equal('en'); await lclz.setup(["es", "fr"]);
expect(lclz.language).to.equal('en');
}); });
it('should use the most preferred user language', function () { it('should fall back to generic English for other English', async function () {
window.navigator.languages = ["en-AU", "de"];
let lclz = new Localizer();
await lclz.setup(["de", "fr", "en-GB"]);
expect(lclz.language).to.equal('en');
});
it('should prefer specific English over generic', async function () {
window.navigator.languages = ["en-GB", "de"];
let lclz = new Localizer();
await lclz.setup(["de", "en-AU", "en-GB"]);
expect(lclz.language).to.equal('en-GB');
});
it('should use the most preferred user language', async function () {
window.navigator.languages = ["nl", "de", "fr"]; window.navigator.languages = ["nl", "de", "fr"];
l10n.setup(["es", "fr", "de"]); let lclz = new Localizer();
expect(l10n.language).to.equal('de'); await lclz.setup(["es", "fr", "de"]);
expect(lclz.language).to.equal('de');
}); });
it('should prefer sub-languages languages', function () { it('should prefer sub-languages languages', async function () {
window.navigator.languages = ["pt-BR"]; window.navigator.languages = ["pt-BR"];
l10n.setup(["pt", "pt-BR"]); let lclz = new Localizer();
expect(l10n.language).to.equal('pt-BR'); await lclz.setup(["pt", "pt-BR"]);
expect(lclz.language).to.equal('pt-BR');
}); });
it('should fall back to language "parents"', function () { it('should fall back to language "parents"', async function () {
window.navigator.languages = ["pt-BR"]; window.navigator.languages = ["pt-BR"];
l10n.setup(["fr", "pt", "de"]); let lclz = new Localizer();
expect(l10n.language).to.equal('pt'); await lclz.setup(["fr", "pt", "de"]);
expect(lclz.language).to.equal('pt');
}); });
it('should not use specific language when user asks for a generic language', function () { it('should not use specific language when user asks for a generic language', async function () {
window.navigator.languages = ["pt", "de"]; window.navigator.languages = ["pt", "de"];
l10n.setup(["fr", "pt-BR", "de"]); let lclz = new Localizer();
expect(l10n.language).to.equal('de'); await lclz.setup(["fr", "pt-BR", "de"]);
expect(lclz.language).to.equal('de');
}); });
it('should handle underscore as a separator', function () { it('should handle underscore as a separator', async function () {
window.navigator.languages = ["pt-BR"]; window.navigator.languages = ["pt-BR"];
l10n.setup(["pt_BR"]); let lclz = new Localizer();
expect(l10n.language).to.equal('pt_BR'); await lclz.setup(["pt_BR"]);
expect(lclz.language).to.equal('pt_BR');
}); });
it('should handle difference in case', function () { it('should handle difference in case', async function () {
window.navigator.languages = ["pt-br"]; window.navigator.languages = ["pt-br"];
l10n.setup(["pt-BR"]); let lclz = new Localizer();
expect(l10n.language).to.equal('pt-BR'); await lclz.setup(["pt-BR"]);
expect(lclz.language).to.equal('pt-BR');
});
});
describe('Translation loading', function () {
it('should not fetch a translation for English', async function () {
window.navigator.languages = [];
let lclz = new Localizer();
await lclz.setup([]);
expect(fetch).to.not.have.been.called;
});
it('should fetch dictionary relative base URL', async function () {
window.navigator.languages = ["de", "fr"];
fetch.resolves(new Response('{ "Foobar": "gazonk" }'));
let lclz = new Localizer();
await lclz.setup(["ru", "fr"], "/some/path/");
expect(fetch).to.have.been.calledOnceWith("/some/path/fr.json");
expect(lclz.get("Foobar")).to.equal("gazonk");
});
it('should handle base URL without trailing slash', async function () {
window.navigator.languages = ["de", "fr"];
fetch.resolves(new Response('{ "Foobar": "gazonk" }'));
let lclz = new Localizer();
await lclz.setup(["ru", "fr"], "/some/path");
expect(fetch).to.have.been.calledOnceWith("/some/path/fr.json");
expect(lclz.get("Foobar")).to.equal("gazonk");
});
it('should handle current base URL', async function () {
window.navigator.languages = ["de", "fr"];
fetch.resolves(new Response('{ "Foobar": "gazonk" }'));
let lclz = new Localizer();
await lclz.setup(["ru", "fr"]);
expect(fetch).to.have.been.calledOnceWith("fr.json");
expect(lclz.get("Foobar")).to.equal("gazonk");
});
it('should fail if dictionary cannot be found', async function () {
window.navigator.languages = ["de", "fr"];
fetch.resolves(new Response('{}', { status: 404 }));
let lclz = new Localizer();
let ok = false;
try {
await lclz.setup(["ru", "fr"], "/some/path/");
} catch (e) {
ok = true;
}
expect(ok).to.be.true;
}); });
}); });
}); });
-357
View File
@@ -1,357 +0,0 @@
const expect = chai.expect;
import RFB from '../core/rfb.js';
import FakeWebSocket from './fake.websocket.js';
function fakeGetRandomValues(arr) {
if (arr.length === 16) {
arr.set(new Uint8Array([
0x1c, 0x08, 0xfe, 0x21, 0x78, 0xef, 0x4e, 0xf9,
0x3f, 0x05, 0xec, 0xea, 0xd4, 0x6b, 0xa5, 0xd5,
]));
} else {
arr.set(new Uint8Array([
0xee, 0xe2, 0xf1, 0x5a, 0x3c, 0xa7, 0xbe, 0x95,
0x6f, 0x2a, 0x75, 0xfd, 0x62, 0x01, 0xcb, 0xbf,
0x43, 0x74, 0xca, 0x47, 0x4d, 0xfb, 0x0f, 0xcf,
0x3a, 0x6d, 0x55, 0x6b, 0x59, 0x3a, 0xf6, 0x87,
0xcb, 0x03, 0xb7, 0x28, 0x35, 0x7b, 0x15, 0x8e,
0xb6, 0xc8, 0x8f, 0x2d, 0x5e, 0x7b, 0x1c, 0x9a,
0x32, 0x55, 0xe7, 0x64, 0x36, 0x25, 0x7b, 0xa3,
0xe9, 0x4f, 0x6f, 0x97, 0xdc, 0xa4, 0xd4, 0x62,
0x6d, 0x7f, 0xab, 0x02, 0x6b, 0x13, 0x56, 0x69,
0xfb, 0xd0, 0xd4, 0x13, 0x76, 0xcd, 0x0d, 0xd0,
0x1f, 0xd1, 0x0c, 0x63, 0x3a, 0x34, 0x20, 0x6c,
0xbb, 0x60, 0x45, 0x82, 0x23, 0xfd, 0x7c, 0x77,
0x6d, 0xcc, 0x5e, 0xaa, 0xc3, 0x0c, 0x43, 0xb7,
0x8d, 0xc0, 0x27, 0x6e, 0xeb, 0x1d, 0x6c, 0x5f,
0xd8, 0x1c, 0x3c, 0x1c, 0x60, 0x2e, 0x82, 0x15,
0xfd, 0x2e, 0x5f, 0x3a, 0x15, 0x53, 0x14, 0x70,
0x4f, 0xe1, 0x65, 0x68, 0x35, 0x6d, 0xc7, 0x64,
0xdb, 0xdd, 0x09, 0x31, 0x4f, 0x7b, 0x6d, 0x6c,
0x77, 0x59, 0x5e, 0x1e, 0xfa, 0x4b, 0x06, 0x14,
0xbe, 0xdc, 0x9c, 0x3d, 0x7b, 0xed, 0xf3, 0x2b,
0x19, 0x26, 0x11, 0x8e, 0x3f, 0xab, 0x73, 0x9a,
0x0a, 0x3a, 0xaa, 0x85, 0x06, 0xd5, 0xca, 0x3f,
0xc3, 0xe2, 0x33, 0x7f, 0x97, 0x74, 0x98, 0x8f,
0x2f, 0xa5, 0xfc, 0x7e, 0xb1, 0x77, 0x71, 0x58,
0xf0, 0xbc, 0x04, 0x59, 0xbb, 0xb4, 0xc6, 0xcc,
0x0f, 0x06, 0xcd, 0xa2, 0xd5, 0x01, 0x2f, 0xb2,
0x22, 0x0b, 0xfc, 0x1e, 0x59, 0x9f, 0xd3, 0x4f,
0x30, 0x95, 0xc6, 0x80, 0x0f, 0x69, 0xf3, 0x4a,
0xd4, 0x36, 0xb6, 0x5a, 0x0b, 0x16, 0x0d, 0x81,
0x31, 0xb0, 0x69, 0xd4, 0x4e,
]));
}
}
async function fakeGeneratekey() {
let key = JSON.parse('{"alg":"RSA-OAEP-256","d":"B7QR2yI8sXjo8vQhJpX9odqqR\
6wIuPrTM1B1JJEKVeSrr7OYcc1FRJ52Vap9LIAU-ezigs9QDvWMxknB8motLnG69Wck37nt9_z4s8l\
FQp0nROA-oaR92HW34KNL1b2fEVWGI0N86h730MvTJC5O2cmKeMezIG-oNqbbfFyP8AW-WLdDlgZm1\
1-FjzhbVpb0Bc7nRSgBPSV-EY6Sl-LuglxDx4LaTdQW7QE_WXoRUt-GYGfTseuFQQK5WeoyX3yBtQy\
dpauW6rrgyWdtP4hDFIoZsX6w1i-UMWMMwlIB5FdnUSi26igVGADGpV_vGMP36bv-EHp0bY-Qp0gpI\
fLfgQ","dp":"Z1v5UceFfV2bhmbG19eGYb30jFxqoRBq36PKNY7IunMs1keYy0FpLbyGhtgMZ1Ymm\
c8wEzGYsCPEP-ykcun_rlyu7YxmcnyC9YQqTqLyqvO-7rUqDvk9TMfdqWFP6heADRhKZmEbmcau6_m\
2MwwK9kOkMKWvpqp8_TpJMnAH7zE","dq":"OBacRE15aY3NtCR4cvP5os3sT70JbDdDLHT3IHZM6r\
E35CYNpLDia2chm_wnMcYvKFW9zC2ajRZ15i9c_VXQzS7ZlTaQYBFyMt7kVhxMEMFsPv1crD6t3uEI\
j0LNuNYyy0jkon_LPZKQFK654CiL-L2YaNXOH4HbHP02dWeVQIE","e":"AQAB","ext":true,"ke\
y_ops":["decrypt"],"kty":"RSA","n":"m1c92ZFk9ZI6l_O4YFiNxbv0Ng94SB3yThy1P_mcqr\
GDQkRiGVdcTxAk38T9PgLztmspF-6U5TAHO-gSmmW88AC9m6f1Mspps6r7zl-M_OG-TwvGzf3BDz8z\
Eg1FPbZV7whO1M4TCAZ0PqwG7qCc6nK1WiAhaKrSpzuPdL1igfNBsX7qu5wgw4ZTTGSLbVC_LfULQ5\
FADgFTRXUSaxm1F8C_Lwy6a2e4nTcXilmtN2IHUjHegzm-Tq2HizmR3ARdWJpESYIW5-AXoiqj29tD\
rqCmu2WPkB2psVp83IzZfaQNQzjNfvA8GpimkcDCkP5VMRrtKCcG4ZAFnO-A3NBX_Q","p":"2Q_lN\
L7vCOBzAppYzCZo3WSh0hX-MOZyPUznks5U2TjmfdNZoL6_FJRiGyyLvwSiZFdEAAvpAyESFfFigng\
AqMLSf448nPg15VUGj533CotsEM0WpoEr1JCgqdUbgDAfJQIBcwOmegBqd7lWm7uzEnRCvouB70ybk\
JfpdprhkVE","q":"tzTt-F3g2u_3Ctj26Ho9iN_wC_W0lXGzslLt5nLmss8JqdLoDDrijjU-gjeRh\
7lgiuHdUc3dorfFKbaMNOjoW3QKqt9oZ1JM0HKeRw0X2PnWW_0WK6DK5ASWDTXbMq2sUZqJvYEyL74\
H2Zrt0RPAux7XQLEVgND6ROdXnMJ70O0","qi":"qfl4cXQkz4BNqa2De0-PfdU-8d1w3onnaGqx1D\
s2fHzD_SJ4cNghn2TksoT9Qo64b3pUjH9igi2pyEjomk6D12N6FG0e10u7vFKv3W5YqUOgTpYdbcWH\
dZ2qZWJU0XQZIrF8jLGTOO4GYP6_9sJ5R7Wk_0MdqQy8qvixWD4zLcY"}');
key = await window.crypto.subtle.importKey("jwk", key, {
name: "RSA-OAEP",
hash: {name: "SHA-256"}
}, true, ["decrypt"]);
return {privateKey: key};
}
const receiveData = new Uint8Array([
// server public key
0x00, 0x00, 0x08, 0x00, 0xac, 0x1a, 0xbc, 0x42,
0x8a, 0x2a, 0x69, 0x65, 0x54, 0xf8, 0x9a, 0xe6,
0x43, 0xaa, 0xf7, 0x27, 0xf6, 0x2a, 0xf8, 0x8f,
0x36, 0xd4, 0xae, 0x54, 0x0f, 0x16, 0x28, 0x08,
0xc2, 0x5b, 0xca, 0x23, 0xdc, 0x27, 0x88, 0x1a,
0x12, 0x82, 0xa8, 0x54, 0xea, 0x00, 0x99, 0x8d,
0x02, 0x1d, 0x77, 0x4a, 0xeb, 0xd0, 0x93, 0x40,
0x79, 0x86, 0xcb, 0x37, 0xd4, 0xb2, 0xc7, 0xcd,
0x93, 0xe1, 0x00, 0x4d, 0x86, 0xff, 0x97, 0x33,
0x0c, 0xad, 0x51, 0x47, 0x45, 0x85, 0x56, 0x07,
0x65, 0x21, 0x7c, 0x57, 0x6d, 0x68, 0x7d, 0xd7,
0x00, 0x43, 0x0c, 0x9d, 0x3b, 0xa1, 0x5a, 0x11,
0xed, 0x51, 0x77, 0xf9, 0xd1, 0x5b, 0x33, 0xd7,
0x1a, 0xeb, 0x65, 0x57, 0xc0, 0x01, 0x51, 0xff,
0x9b, 0x82, 0xb3, 0xeb, 0x82, 0xc2, 0x1f, 0xca,
0x47, 0xc0, 0x6a, 0x09, 0xe0, 0xf7, 0xda, 0x39,
0x85, 0x12, 0xe7, 0x45, 0x8d, 0xb4, 0x1a, 0xda,
0xcb, 0x86, 0x58, 0x52, 0x37, 0x66, 0x9d, 0x8a,
0xce, 0xf2, 0x18, 0x78, 0x7d, 0x7f, 0xf0, 0x07,
0x94, 0x8e, 0x6b, 0x17, 0xd9, 0x00, 0x2a, 0x3a,
0xb9, 0xd4, 0x77, 0xde, 0x70, 0x85, 0xc4, 0x3a,
0x62, 0x10, 0x02, 0xee, 0xba, 0xd8, 0xc0, 0x62,
0xd0, 0x8e, 0xc1, 0x98, 0x19, 0x8e, 0x39, 0x0f,
0x3e, 0x1d, 0x61, 0xb1, 0x93, 0x13, 0x59, 0x39,
0xcb, 0x96, 0xf2, 0x17, 0xc9, 0xe1, 0x41, 0xd3,
0x20, 0xdd, 0x62, 0x5e, 0x7d, 0x53, 0xd6, 0xb7,
0x1d, 0xfe, 0x02, 0x18, 0x1f, 0xe0, 0xef, 0x3d,
0x94, 0xe3, 0x0a, 0x9c, 0x59, 0x54, 0xd8, 0x98,
0x16, 0x9c, 0x31, 0xda, 0x41, 0x0f, 0x2e, 0x71,
0x68, 0xe0, 0xa2, 0x62, 0x3e, 0xe5, 0x25, 0x31,
0xcf, 0xfc, 0x67, 0x63, 0xc3, 0xb0, 0xda, 0x3f,
0x7b, 0x59, 0xbe, 0x7e, 0x9e, 0xa8, 0xd0, 0x01,
0x4f, 0x43, 0x7f, 0x8d, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x01,
// server random
0x01, 0x00, 0x5b, 0x58, 0x2a, 0x96, 0x2d, 0xbb,
0x88, 0xec, 0xc3, 0x54, 0x00, 0xf3, 0xbb, 0xbe,
0x17, 0xa3, 0x84, 0xd3, 0xef, 0xd8, 0x4a, 0x31,
0x09, 0x20, 0xdd, 0xbc, 0x16, 0x9d, 0xc9, 0x5b,
0x99, 0x62, 0x86, 0xfe, 0x0b, 0x28, 0x4b, 0xfe,
0x5b, 0x56, 0x2d, 0xcb, 0x6e, 0x6f, 0xec, 0xf0,
0x53, 0x0c, 0x33, 0x84, 0x93, 0xc9, 0xbf, 0x79,
0xde, 0xb3, 0xb9, 0x29, 0x60, 0x78, 0xde, 0xe6,
0x1d, 0xa7, 0x89, 0x48, 0x3f, 0xd1, 0x58, 0x66,
0x27, 0x9c, 0xd4, 0x6e, 0x72, 0x9c, 0x6e, 0x4a,
0xc0, 0x69, 0x79, 0x6f, 0x79, 0x0f, 0x13, 0xc4,
0x20, 0xcf, 0xa6, 0xbb, 0xce, 0x18, 0x6d, 0xd5,
0x9e, 0xd9, 0x67, 0xbe, 0x61, 0x43, 0x67, 0x11,
0x76, 0x2f, 0xfd, 0x78, 0x75, 0x2b, 0x89, 0x35,
0xdd, 0x0f, 0x13, 0x7f, 0xee, 0x78, 0xad, 0x32,
0x56, 0x21, 0x81, 0x08, 0x1f, 0xcf, 0x4c, 0x29,
0xa3, 0xeb, 0x89, 0x2d, 0xbe, 0xba, 0x8d, 0xe4,
0x69, 0x28, 0xba, 0x53, 0x82, 0xce, 0x5c, 0xf6,
0x5e, 0x5e, 0xa5, 0xb3, 0x88, 0xd8, 0x3d, 0xab,
0xf4, 0x24, 0x9e, 0x3f, 0x04, 0xaf, 0xdc, 0x48,
0x90, 0x53, 0x37, 0xe6, 0x82, 0x1d, 0xe0, 0x15,
0x91, 0xa1, 0xc6, 0xa9, 0x54, 0xe5, 0x2a, 0xb5,
0x64, 0x2d, 0x93, 0xc0, 0xc0, 0xe1, 0x0f, 0x6a,
0x4b, 0xdb, 0x77, 0xf8, 0x4a, 0x0f, 0x83, 0x36,
0xdd, 0x5e, 0x1e, 0xdd, 0x39, 0x65, 0xa2, 0x11,
0xc2, 0xcf, 0x56, 0x1e, 0xa1, 0x29, 0xae, 0x11,
0x9f, 0x3a, 0x82, 0xc7, 0xbd, 0x89, 0x6e, 0x59,
0xb8, 0x59, 0x17, 0xcb, 0x65, 0xa0, 0x4b, 0x4d,
0xbe, 0x33, 0x32, 0x85, 0x9c, 0xca, 0x5e, 0x95,
0xc2, 0x5a, 0xd0, 0xc9, 0x8b, 0xf1, 0xf5, 0x14,
0xcf, 0x76, 0x80, 0xc2, 0x24, 0x0a, 0x39, 0x7e,
0x60, 0x64, 0xce, 0xd9, 0xb8, 0xad, 0x24, 0xa8,
0xdf, 0xcb,
// server hash
0x00, 0x14, 0x39, 0x30, 0x66, 0xb5, 0x66, 0x8a,
0xcd, 0xb9, 0xda, 0xe0, 0xde, 0xcb, 0xf6, 0x47,
0x5f, 0x54, 0x66, 0xe0, 0xbc, 0x49, 0x37, 0x01,
0xf2, 0x9e, 0xef, 0xcc, 0xcd, 0x4d, 0x6c, 0x0e,
0xc6, 0xab, 0x28, 0xd4, 0x7b, 0x13,
// subtype
0x00, 0x01, 0x30, 0x2a, 0xc3, 0x0b, 0xc2, 0x1c,
0xeb, 0x02, 0x44, 0x92, 0x5d, 0xfd, 0xf9, 0xa7,
0x94, 0xd0, 0x19,
]);
const sendData = new Uint8Array([
// client public key
0x00, 0x00, 0x08, 0x00, 0x9b, 0x57, 0x3d, 0xd9,
0x91, 0x64, 0xf5, 0x92, 0x3a, 0x97, 0xf3, 0xb8,
0x60, 0x58, 0x8d, 0xc5, 0xbb, 0xf4, 0x36, 0x0f,
0x78, 0x48, 0x1d, 0xf2, 0x4e, 0x1c, 0xb5, 0x3f,
0xf9, 0x9c, 0xaa, 0xb1, 0x83, 0x42, 0x44, 0x62,
0x19, 0x57, 0x5c, 0x4f, 0x10, 0x24, 0xdf, 0xc4,
0xfd, 0x3e, 0x02, 0xf3, 0xb6, 0x6b, 0x29, 0x17,
0xee, 0x94, 0xe5, 0x30, 0x07, 0x3b, 0xe8, 0x12,
0x9a, 0x65, 0xbc, 0xf0, 0x00, 0xbd, 0x9b, 0xa7,
0xf5, 0x32, 0xca, 0x69, 0xb3, 0xaa, 0xfb, 0xce,
0x5f, 0x8c, 0xfc, 0xe1, 0xbe, 0x4f, 0x0b, 0xc6,
0xcd, 0xfd, 0xc1, 0x0f, 0x3f, 0x33, 0x12, 0x0d,
0x45, 0x3d, 0xb6, 0x55, 0xef, 0x08, 0x4e, 0xd4,
0xce, 0x13, 0x08, 0x06, 0x74, 0x3e, 0xac, 0x06,
0xee, 0xa0, 0x9c, 0xea, 0x72, 0xb5, 0x5a, 0x20,
0x21, 0x68, 0xaa, 0xd2, 0xa7, 0x3b, 0x8f, 0x74,
0xbd, 0x62, 0x81, 0xf3, 0x41, 0xb1, 0x7e, 0xea,
0xbb, 0x9c, 0x20, 0xc3, 0x86, 0x53, 0x4c, 0x64,
0x8b, 0x6d, 0x50, 0xbf, 0x2d, 0xf5, 0x0b, 0x43,
0x91, 0x40, 0x0e, 0x01, 0x53, 0x45, 0x75, 0x12,
0x6b, 0x19, 0xb5, 0x17, 0xc0, 0xbf, 0x2f, 0x0c,
0xba, 0x6b, 0x67, 0xb8, 0x9d, 0x37, 0x17, 0x8a,
0x59, 0xad, 0x37, 0x62, 0x07, 0x52, 0x31, 0xde,
0x83, 0x39, 0xbe, 0x4e, 0xad, 0x87, 0x8b, 0x39,
0x91, 0xdc, 0x04, 0x5d, 0x58, 0x9a, 0x44, 0x49,
0x82, 0x16, 0xe7, 0xe0, 0x17, 0xa2, 0x2a, 0xa3,
0xdb, 0xdb, 0x43, 0xae, 0xa0, 0xa6, 0xbb, 0x65,
0x8f, 0x90, 0x1d, 0xa9, 0xb1, 0x5a, 0x7c, 0xdc,
0x8c, 0xd9, 0x7d, 0xa4, 0x0d, 0x43, 0x38, 0xcd,
0x7e, 0xf0, 0x3c, 0x1a, 0x98, 0xa6, 0x91, 0xc0,
0xc2, 0x90, 0xfe, 0x55, 0x31, 0x1a, 0xed, 0x28,
0x27, 0x06, 0xe1, 0x90, 0x05, 0x9c, 0xef, 0x80,
0xdc, 0xd0, 0x57, 0xfd, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x01,
// client random
0x01, 0x00, 0x84, 0x7f, 0x26, 0x54, 0x74, 0xf6,
0x47, 0xaf, 0x33, 0x64, 0x0d, 0xa6, 0xe5, 0x30,
0xba, 0xe6, 0xe4, 0x8e, 0x50, 0x40, 0x71, 0x1c,
0x0e, 0x06, 0x63, 0xf5, 0x07, 0x2a, 0x26, 0x68,
0xd6, 0xcf, 0xa6, 0x80, 0x84, 0x5e, 0x64, 0xd4,
0x5e, 0x62, 0x31, 0xfe, 0x44, 0x51, 0x0b, 0x7c,
0x4d, 0x55, 0xc5, 0x4a, 0x7e, 0x0d, 0x4d, 0x9b,
0x84, 0xb4, 0x32, 0x2b, 0x4d, 0x8a, 0x34, 0x8d,
0xc8, 0xcf, 0x19, 0x3b, 0x64, 0x82, 0x27, 0x9e,
0xa7, 0x70, 0x2a, 0xc1, 0xb8, 0xf3, 0x6a, 0x3a,
0xf2, 0x75, 0x6e, 0x1d, 0xeb, 0xb6, 0x70, 0x7a,
0x15, 0x18, 0x38, 0x00, 0xb4, 0x4f, 0x55, 0xb5,
0xd8, 0x03, 0x4e, 0xb8, 0x53, 0xff, 0x80, 0x62,
0xf1, 0x9d, 0x27, 0xe8, 0x2a, 0x3d, 0x98, 0x19,
0x32, 0x09, 0x7e, 0x9a, 0xb0, 0xc7, 0x46, 0x23,
0x10, 0x85, 0x35, 0x00, 0x96, 0xce, 0xb3, 0x2c,
0x84, 0x8d, 0xf4, 0x9e, 0xa8, 0x42, 0x67, 0xed,
0x09, 0xa6, 0x09, 0x97, 0xb3, 0x64, 0x26, 0xfb,
0x71, 0x11, 0x9b, 0x3f, 0xbb, 0x57, 0xb8, 0x5b,
0x2e, 0xc5, 0x2d, 0x8c, 0x5c, 0xf7, 0xef, 0x27,
0x25, 0x88, 0x42, 0x45, 0x43, 0xa4, 0xe7, 0xde,
0xea, 0xf9, 0x15, 0x7b, 0x5d, 0x66, 0x24, 0xce,
0xf7, 0xc8, 0x2f, 0xc5, 0xc0, 0x3d, 0xcd, 0xf2,
0x62, 0xfc, 0x1a, 0x5e, 0xec, 0xff, 0xf1, 0x1b,
0xc8, 0xdb, 0xc1, 0x0f, 0x54, 0x66, 0x9e, 0xfd,
0x99, 0x9b, 0x23, 0x70, 0x62, 0x37, 0x80, 0xad,
0x91, 0x6b, 0x84, 0x85, 0x6a, 0x4c, 0x80, 0x9e,
0x60, 0x8a, 0x93, 0xa3, 0xc8, 0x8e, 0xc4, 0x4b,
0x4d, 0xb4, 0x8e, 0x3e, 0xaf, 0xce, 0xcd, 0x83,
0xe5, 0x21, 0x90, 0x95, 0x20, 0x3c, 0x82, 0xb4,
0x7c, 0xab, 0x63, 0x9c, 0xae, 0xc3, 0xc9, 0x71,
0x1a, 0xec, 0x34, 0x18, 0x47, 0xec, 0x5c, 0x4d,
0xed, 0x84,
// client hash
0x00, 0x14, 0x9c, 0x91, 0x9e, 0x76, 0xcf, 0x1e,
0x66, 0x87, 0x5e, 0x29, 0xf1, 0x13, 0x80, 0xea,
0x7d, 0xec, 0xae, 0xf9, 0x60, 0x01, 0xd3, 0x6f,
0xb7, 0x9e, 0xb2, 0xcd, 0x2d, 0xc8, 0xf8, 0x84,
0xb2, 0x9f, 0xc3, 0x7e, 0xb4, 0xbe,
// credentials
0x00, 0x08, 0x9d, 0xc8, 0x3a, 0xb8, 0x80, 0x4f,
0xe3, 0x52, 0xdb, 0x62, 0x9e, 0x97, 0x64, 0x82,
0xa8, 0xa1, 0x6b, 0x7e, 0x4d, 0x68, 0x8c, 0x29,
0x91, 0x38,
]);
describe('RA2 handshake', function () {
let sock;
let rfb;
let sentData;
before(() => {
FakeWebSocket.replace();
sinon.stub(window.crypto, "getRandomValues").callsFake(fakeGetRandomValues);
sinon.stub(window.crypto.subtle, "generateKey").callsFake(fakeGeneratekey);
});
after(() => {
FakeWebSocket.restore();
window.crypto.getRandomValues.restore();
window.crypto.subtle.generateKey.restore();
});
it('should fire the serververification event', function (done) {
sentData = new Uint8Array();
rfb = new RFB(document.createElement('div'), "ws://example.com");
sock = rfb._sock;
sock.send = (data) => {
let res = new Uint8Array(sentData.length + data.length);
res.set(sentData);
res.set(data, sentData.length);
sentData = res;
};
rfb._rfbInitState = "Security";
rfb._rfbVersion = 3.8;
sock._websocket._receiveData(new Uint8Array([1, 6]));
rfb.addEventListener("serververification", (e) => {
expect(e.detail.publickey).to.eql(receiveData.slice(0, 516));
done();
});
sock._websocket._receiveData(receiveData);
});
it('should handle approveServer and fire the credentialsrequired event', function (done) {
rfb.addEventListener("credentialsrequired", (e) => {
expect(e.detail.types).to.eql(["password"]);
done();
});
rfb.approveServer();
});
it('should match sendData after sending credentials', function (done) {
rfb.addEventListener("securityresult", (event) => {
expect(sentData.slice(1)).to.eql(sendData);
done();
});
rfb.sendCredentials({ "password": "123456" });
});
});
+57 -32
View File
@@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) { function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock; let sock;
let done = false;
sock = new Websock; sock = new Websock;
sock.open("ws://example.com"); sock.open("ws://example.com");
sock.on('message', () => { sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth); done = decoder.decodeRect(x, y, width, height, sock, display, depth);
}); });
// Empty messages are filtered at multiple layers, so we need to // Empty messages are filtered at multiple layers, so we need to
// do a direct call // do a direct call
if (data.length === 0) { if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth); done = decoder.decodeRect(x, y, width, height, sock, display, depth);
} else { } else {
sock._websocket._receiveData(new Uint8Array(data)); sock._websocket._receiveData(new Uint8Array(data));
} }
display.flip(); display.flip();
return done;
} }
describe('Raw Decoder', function () { describe('Raw Decoder', function () {
@@ -42,22 +45,36 @@ describe('Raw Decoder', function () {
}); });
it('should handle the Raw encoding', function () { it('should handle the Raw encoding', function () {
testDecodeRect(decoder, 0, 0, 2, 2, let done;
[0xff, 0x00, 0x00, 0, 0x00, 0xff, 0x00, 0,
0x00, 0xff, 0x00, 0, 0xff, 0x00, 0x00, 0], done = testDecodeRect(decoder, 0, 0, 2, 2,
display, 24); [0xff, 0x00, 0x00, 0,
testDecodeRect(decoder, 2, 0, 2, 2, 0x00, 0xff, 0x00, 0,
[0x00, 0x00, 0xff, 0, 0x00, 0x00, 0xff, 0, 0x00, 0xff, 0x00, 0,
0x00, 0x00, 0xff, 0, 0x00, 0x00, 0xff, 0], 0xff, 0x00, 0x00, 0],
display, 24); display, 24);
testDecodeRect(decoder, 0, 2, 4, 1, expect(done).to.be.true;
[0xee, 0x00, 0xff, 0, 0x00, 0xee, 0xff, 0, done = testDecodeRect(decoder, 2, 0, 2, 2,
0xaa, 0xee, 0xff, 0, 0xab, 0xee, 0xff, 0], [0x00, 0x00, 0xff, 0,
display, 24); 0x00, 0x00, 0xff, 0,
testDecodeRect(decoder, 0, 3, 4, 1, 0x00, 0x00, 0xff, 0,
[0xee, 0x00, 0xff, 0, 0x00, 0xee, 0xff, 0, 0x00, 0x00, 0xff, 0],
0xaa, 0xee, 0xff, 0, 0xab, 0xee, 0xff, 0], display, 24);
display, 24); expect(done).to.be.true;
done = testDecodeRect(decoder, 0, 2, 4, 1,
[0xee, 0x00, 0xff, 0,
0x00, 0xee, 0xff, 0,
0xaa, 0xee, 0xff, 0,
0xab, 0xee, 0xff, 0],
display, 24);
expect(done).to.be.true;
done = testDecodeRect(decoder, 0, 3, 4, 1,
[0xee, 0x00, 0xff, 0,
0x00, 0xee, 0xff, 0,
0xaa, 0xee, 0xff, 0,
0xab, 0xee, 0xff, 0],
display, 24);
expect(done).to.be.true;
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
@@ -70,18 +87,24 @@ describe('Raw Decoder', function () {
}); });
it('should handle the Raw encoding in low colour mode', function () { it('should handle the Raw encoding in low colour mode', function () {
testDecodeRect(decoder, 0, 0, 2, 2, let done;
[0x30, 0x30, 0x30, 0x30],
display, 8); done = testDecodeRect(decoder, 0, 0, 2, 2,
testDecodeRect(decoder, 2, 0, 2, 2, [0x30, 0x30, 0x30, 0x30],
[0x0c, 0x0c, 0x0c, 0x0c], display, 8);
display, 8); expect(done).to.be.true;
testDecodeRect(decoder, 0, 2, 4, 1, done = testDecodeRect(decoder, 2, 0, 2, 2,
[0x0c, 0x0c, 0x30, 0x30], [0x0c, 0x0c, 0x0c, 0x0c],
display, 8); display, 8);
testDecodeRect(decoder, 0, 3, 4, 1, expect(done).to.be.true;
[0x0c, 0x0c, 0x30, 0x30], done = testDecodeRect(decoder, 0, 2, 4, 1,
display, 8); [0x0c, 0x0c, 0x30, 0x30],
display, 8);
expect(done).to.be.true;
done = testDecodeRect(decoder, 0, 3, 4, 1,
[0x0c, 0x0c, 0x30, 0x30],
display, 8);
expect(done).to.be.true;
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -98,7 +121,7 @@ describe('Raw Decoder', function () {
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]); display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]); display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, [], display, 24); let done = testDecodeRect(decoder, 1, 2, 0, 0, [], display, 24);
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -107,6 +130,7 @@ describe('Raw Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]); ]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData); expect(display).to.have.displayed(targetData);
}); });
@@ -115,7 +139,7 @@ describe('Raw Decoder', function () {
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]); display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]); display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, [], display, 8); let done = testDecodeRect(decoder, 1, 2, 0, 0, [], display, 8);
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -124,6 +148,7 @@ describe('Raw Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]); ]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData); expect(display).to.have.displayed(targetData);
}); });
}); });
+1220 -324
View File
File diff suppressed because it is too large Load Diff
+12 -4
View File
@@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) { function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock; let sock;
let done = false;
sock = new Websock; sock = new Websock;
sock.open("ws://example.com"); sock.open("ws://example.com");
sock.on('message', () => { sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth); done = decoder.decodeRect(x, y, width, height, sock, display, depth);
}); });
// Empty messages are filtered at multiple layers, so we need to // Empty messages are filtered at multiple layers, so we need to
// do a direct call // do a direct call
if (data.length === 0) { if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth); done = decoder.decodeRect(x, y, width, height, sock, display, depth);
} else { } else {
sock._websocket._receiveData(new Uint8Array(data)); sock._websocket._receiveData(new Uint8Array(data));
} }
display.flip(); display.flip();
return done;
} }
function push16(arr, num) { function push16(arr, num) {
@@ -76,7 +79,7 @@ describe('RRE Decoder', function () {
push16(data, 2); // width: 2 push16(data, 2); // width: 2
push16(data, 2); // height: 2 push16(data, 2); // height: 2
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24); let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -85,6 +88,7 @@ describe('RRE Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]); ]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData); expect(display).to.have.displayed(targetData);
}); });
@@ -93,7 +97,10 @@ describe('RRE Decoder', function () {
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]); display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]); display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, [ 0x00, 0xff, 0xff, 0xff, 0xff ], display, 24); let done = testDecodeRect(decoder, 1, 2, 0, 0,
[ 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff ],
display, 24);
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -102,6 +109,7 @@ describe('RRE Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]); ]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData); expect(display).to.have.displayed(targetData);
}); });
}); });
+55 -33
View File
@@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) { function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock; let sock;
let done = false;
sock = new Websock; sock = new Websock;
sock.open("ws://example.com"); sock.open("ws://example.com");
sock.on('message', () => { sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth); done = decoder.decodeRect(x, y, width, height, sock, display, depth);
}); });
// Empty messages are filtered at multiple layers, so we need to // Empty messages are filtered at multiple layers, so we need to
// do a direct call // do a direct call
if (data.length === 0) { if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth); done = decoder.decodeRect(x, y, width, height, sock, display, depth);
} else { } else {
sock._websocket._receiveData(new Uint8Array(data)); sock._websocket._receiveData(new Uint8Array(data));
} }
display.flip(); display.flip();
return done;
} }
describe('Tight Decoder', function () { describe('Tight Decoder', function () {
@@ -42,9 +45,9 @@ describe('Tight Decoder', function () {
}); });
it('should handle fill rects', function () { it('should handle fill rects', function () {
testDecodeRect(decoder, 0, 0, 4, 4, let done = testDecodeRect(decoder, 0, 0, 4, 4,
[0x80, 0xff, 0x88, 0x44], [0x80, 0xff, 0x88, 0x44],
display, 24); display, 24);
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255,
@@ -53,21 +56,31 @@ describe('Tight Decoder', function () {
0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255,
]); ]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData); expect(display).to.have.displayed(targetData);
}); });
it('should handle uncompressed copy rects', function () { it('should handle uncompressed copy rects', function () {
let done;
let blueData = [ 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff ]; let blueData = [ 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff ];
let greenData = [ 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00 ]; let greenData = [ 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00 ];
testDecodeRect(decoder, 0, 0, 2, 1, blueData, display, 24); done = testDecodeRect(decoder, 0, 0, 2, 1, blueData, display, 24);
testDecodeRect(decoder, 0, 1, 2, 1, blueData, display, 24); expect(done).to.be.true;
testDecodeRect(decoder, 2, 0, 2, 1, greenData, display, 24); done = testDecodeRect(decoder, 0, 1, 2, 1, blueData, display, 24);
testDecodeRect(decoder, 2, 1, 2, 1, greenData, display, 24); expect(done).to.be.true;
testDecodeRect(decoder, 0, 2, 2, 1, greenData, display, 24); done = testDecodeRect(decoder, 2, 0, 2, 1, greenData, display, 24);
testDecodeRect(decoder, 0, 3, 2, 1, greenData, display, 24); expect(done).to.be.true;
testDecodeRect(decoder, 2, 2, 2, 1, blueData, display, 24); done = testDecodeRect(decoder, 2, 1, 2, 1, greenData, display, 24);
testDecodeRect(decoder, 2, 3, 2, 1, blueData, display, 24); expect(done).to.be.true;
done = testDecodeRect(decoder, 0, 2, 2, 1, greenData, display, 24);
expect(done).to.be.true;
done = testDecodeRect(decoder, 0, 3, 2, 1, greenData, display, 24);
expect(done).to.be.true;
done = testDecodeRect(decoder, 2, 2, 2, 1, blueData, display, 24);
expect(done).to.be.true;
done = testDecodeRect(decoder, 2, 3, 2, 1, blueData, display, 24);
expect(done).to.be.true;
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -89,7 +102,7 @@ describe('Tight Decoder', function () {
0x60, 0x82, 0x01, 0x99, 0x8d, 0x29, 0x02, 0xa6, 0x60, 0x82, 0x01, 0x99, 0x8d, 0x29, 0x02, 0xa6,
0x00, 0x7e, 0xbf, 0x0f, 0xf1 ]; 0x00, 0x7e, 0xbf, 0x0f, 0xf1 ];
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24); let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -98,6 +111,7 @@ describe('Tight Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]); ]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData); expect(display).to.have.displayed(targetData);
}); });
@@ -110,7 +124,7 @@ describe('Tight Decoder', function () {
// Pixels // Pixels
0x30, 0x30, 0xc0, 0xc0 ]; 0x30, 0x30, 0xc0, 0xc0 ];
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24); let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -119,6 +133,7 @@ describe('Tight Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]); ]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData); expect(display).to.have.displayed(targetData);
}); });
@@ -135,7 +150,7 @@ describe('Tight Decoder', function () {
0x78, 0x9c, 0x33, 0x30, 0x38, 0x70, 0xc0, 0x00, 0x78, 0x9c, 0x33, 0x30, 0x38, 0x70, 0xc0, 0x00,
0x8a, 0x01, 0x21, 0x3c, 0x05, 0xa1 ]; 0x8a, 0x01, 0x21, 0x3c, 0x05, 0xa1 ];
testDecodeRect(decoder, 0, 0, 4, 12, data, display, 24); let done = testDecodeRect(decoder, 0, 0, 4, 12, data, display, 24);
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -152,10 +167,12 @@ describe('Tight Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]); ]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData); expect(display).to.have.displayed(targetData);
}); });
it('should handle uncompressed palette rects', function () { it('should handle uncompressed palette rects', function () {
let done;
let data1 = [ let data1 = [
// Control bytes // Control bytes
0x40, 0x01, 0x40, 0x01,
@@ -171,8 +188,10 @@ describe('Tight Decoder', function () {
// Pixels // Pixels
0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00 ]; 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00 ];
testDecodeRect(decoder, 0, 0, 4, 2, data1, display, 24); done = testDecodeRect(decoder, 0, 0, 4, 2, data1, display, 24);
testDecodeRect(decoder, 0, 2, 4, 2, data2, display, 24); expect(done).to.be.true;
done = testDecodeRect(decoder, 0, 2, 4, 2, data2, display, 24);
expect(done).to.be.true;
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -196,7 +215,7 @@ describe('Tight Decoder', function () {
0x62, 0x08, 0xc9, 0xc0, 0x00, 0x00, 0x00, 0x54, 0x62, 0x08, 0xc9, 0xc0, 0x00, 0x00, 0x00, 0x54,
0x00, 0x09 ]; 0x00, 0x09 ];
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24); let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -205,6 +224,7 @@ describe('Tight Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]); ]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData); expect(display).to.have.displayed(targetData);
}); });
@@ -221,7 +241,7 @@ describe('Tight Decoder', function () {
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]); display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]); display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, [ 0x00 ], display, 24); let done = testDecodeRect(decoder, 1, 2, 0, 0, [ 0x00 ], display, 24);
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -230,6 +250,7 @@ describe('Tight Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]); ]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData); expect(display).to.have.displayed(targetData);
}); });
@@ -238,10 +259,10 @@ describe('Tight Decoder', function () {
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]); display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]); display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, let done = testDecodeRect(decoder, 1, 2, 0, 0,
[ 0x40, 0x01, 0x01, [ 0x40, 0x01, 0x01,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff ], display, 24); 0xff, 0xff, 0xff ], display, 24);
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -250,6 +271,7 @@ describe('Tight Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]); ]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData); expect(display).to.have.displayed(targetData);
}); });
@@ -258,8 +280,9 @@ describe('Tight Decoder', function () {
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]); display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]); display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, let done = testDecodeRect(decoder, 1, 2, 0, 0,
[ 0x80, 0xff, 0xff, 0xff ], display, 24); [ 0x80, 0xff, 0xff, 0xff ],
display, 24);
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -268,10 +291,11 @@ describe('Tight Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]); ]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData); expect(display).to.have.displayed(targetData);
}); });
it('should handle JPEG rects', function (done) { it('should handle JPEG rects', async function () {
let data = [ let data = [
// Control bytes // Control bytes
0x90, 0xd6, 0x05, 0x90, 0xd6, 0x05,
@@ -369,7 +393,8 @@ describe('Tight Decoder', function () {
0x3f, 0xeb, 0xff, 0x00, 0xff, 0xd9, 0x3f, 0xeb, 0xff, 0x00, 0xff, 0xd9,
]; ];
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24); let decodeDone = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
expect(decodeDone).to.be.true;
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -385,10 +410,7 @@ describe('Tight Decoder', function () {
return diff < 5; return diff < 5;
} }
display.onflush = () => { await display.flush();
expect(display).to.have.displayed(targetData, almost); expect(display).to.have.displayed(targetData, almost);
done();
};
display.flush();
}); });
}); });
+10 -9
View File
@@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) { function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock; let sock;
let done = false;
sock = new Websock; sock = new Websock;
sock.open("ws://example.com"); sock.open("ws://example.com");
sock.on('message', () => { sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth); done = decoder.decodeRect(x, y, width, height, sock, display, depth);
}); });
// Empty messages are filtered at multiple layers, so we need to // Empty messages are filtered at multiple layers, so we need to
// do a direct call // do a direct call
if (data.length === 0) { if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth); done = decoder.decodeRect(x, y, width, height, sock, display, depth);
} else { } else {
sock._websocket._receiveData(new Uint8Array(data)); sock._websocket._receiveData(new Uint8Array(data));
} }
display.flip(); display.flip();
return done;
} }
describe('TightPng Decoder', function () { describe('TightPng Decoder', function () {
@@ -41,7 +44,7 @@ describe('TightPng Decoder', function () {
display.resize(4, 4); display.resize(4, 4);
}); });
it('should handle the TightPng encoding', function (done) { it('should handle the TightPng encoding', async function () {
let data = [ let data = [
// Control bytes // Control bytes
0xa0, 0xb4, 0x04, 0xa0, 0xb4, 0x04,
@@ -119,7 +122,8 @@ describe('TightPng Decoder', function () {
0xae, 0x42, 0x60, 0x82, 0xae, 0x42, 0x60, 0x82,
]; ];
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24); let decodeDone = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
expect(decodeDone).to.be.true;
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -135,10 +139,7 @@ describe('TightPng Decoder', function () {
return diff < 30; return diff < 30;
} }
display.onflush = () => { await display.flush();
expect(display).to.have.displayed(targetData, almost); expect(display).to.have.displayed(targetData, almost);
done();
};
display.flush();
}); });
}); });
+261 -185
View File
@@ -6,92 +6,64 @@ import FakeWebSocket from './fake.websocket.js';
describe('Websock', function () { describe('Websock', function () {
"use strict"; "use strict";
describe('Queue methods', function () { describe('Receive queue methods', function () {
let sock; let sock, websock;
const RQ_TEMPLATE = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);
beforeEach(function () { beforeEach(function () {
sock = new Websock(); sock = new Websock();
// skip init websock = new FakeWebSocket();
sock._allocateBuffers(); websock._open();
sock._rQ.set(RQ_TEMPLATE); sock.attach(websock);
sock._rQlen = RQ_TEMPLATE.length;
});
describe('rQlen', function () {
it('should return the length of the receive queue', function () {
sock.rQi = 0;
expect(sock.rQlen).to.equal(RQ_TEMPLATE.length);
});
it("should return the proper length if we read some from the receive queue", function () {
sock.rQi = 1;
expect(sock.rQlen).to.equal(RQ_TEMPLATE.length - 1);
});
}); });
describe('rQpeek8', function () { describe('rQpeek8', function () {
it('should peek at the next byte without poping it off the queue', function () { it('should peek at the next byte without poping it off the queue', function () {
const befLen = sock.rQlen; websock._receiveData(new Uint8Array([0xab, 0xcd]));
const peek = sock.rQpeek8(); expect(sock.rQpeek8()).to.equal(0xab);
expect(sock.rQpeek8()).to.equal(peek); expect(sock.rQpeek8()).to.equal(0xab);
expect(sock.rQlen).to.equal(befLen);
}); });
}); });
describe('rQshift8()', function () { describe('rQshift8()', function () {
it('should pop a single byte from the receive queue', function () { it('should pop a single byte from the receive queue', function () {
const peek = sock.rQpeek8(); websock._receiveData(new Uint8Array([0xab, 0xcd]));
const befLen = sock.rQlen; expect(sock.rQshift8()).to.equal(0xab);
expect(sock.rQshift8()).to.equal(peek); expect(sock.rQshift8()).to.equal(0xcd);
expect(sock.rQlen).to.equal(befLen - 1);
}); });
}); });
describe('rQshift16()', function () { describe('rQshift16()', function () {
it('should pop two bytes from the receive queue and return a single number', function () { it('should pop two bytes from the receive queue and return a single number', function () {
const befLen = sock.rQlen; websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
const expected = (RQ_TEMPLATE[0] << 8) + RQ_TEMPLATE[1]; expect(sock.rQshift16()).to.equal(0xabcd);
expect(sock.rQshift16()).to.equal(expected); expect(sock.rQshift16()).to.equal(0x1234);
expect(sock.rQlen).to.equal(befLen - 2);
}); });
}); });
describe('rQshift32()', function () { describe('rQshift32()', function () {
it('should pop four bytes from the receive queue and return a single number', function () { it('should pop four bytes from the receive queue and return a single number', function () {
const befLen = sock.rQlen; websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
const expected = (RQ_TEMPLATE[0] << 24) + 0x88, 0xee, 0x11, 0x33]));
(RQ_TEMPLATE[1] << 16) + expect(sock.rQshift32()).to.equal(0xabcd1234);
(RQ_TEMPLATE[2] << 8) + expect(sock.rQshift32()).to.equal(0x88ee1133);
RQ_TEMPLATE[3];
expect(sock.rQshift32()).to.equal(expected);
expect(sock.rQlen).to.equal(befLen - 4);
}); });
}); });
describe('rQshiftStr', function () { describe('rQshiftStr', function () {
it('should shift the given number of bytes off of the receive queue and return a string', function () { it('should shift the given number of bytes off of the receive queue and return a string', function () {
const befLen = sock.rQlen; websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
const befRQi = sock.rQi; 0x88, 0xee, 0x11, 0x33]));
const shifted = sock.rQshiftStr(3); expect(sock.rQshiftStr(4)).to.equal('\xab\xcd\x12\x34');
expect(shifted).to.be.a('string'); expect(sock.rQshiftStr(4)).to.equal('\x88\xee\x11\x33');
expect(shifted).to.equal(String.fromCharCode.apply(null, Array.prototype.slice.call(new Uint8Array(RQ_TEMPLATE.buffer, befRQi, 3))));
expect(sock.rQlen).to.equal(befLen - 3);
});
it('should shift the entire rest of the queue off if no length is given', function () {
sock.rQshiftStr();
expect(sock.rQlen).to.equal(0);
}); });
it('should be able to handle very large strings', function () { it('should be able to handle very large strings', function () {
const BIG_LEN = 500000; const BIG_LEN = 500000;
const RQ_BIG = new Uint8Array(BIG_LEN); const incoming = new Uint8Array(BIG_LEN);
let expected = ""; let expected = "";
let letterCode = 'a'.charCodeAt(0); let letterCode = 'a'.charCodeAt(0);
for (let i = 0; i < BIG_LEN; i++) { for (let i = 0; i < BIG_LEN; i++) {
RQ_BIG[i] = letterCode; incoming[i] = letterCode;
expected += String.fromCharCode(letterCode); expected += String.fromCharCode(letterCode);
if (letterCode < 'z'.charCodeAt(0)) { if (letterCode < 'z'.charCodeAt(0)) {
@@ -100,146 +72,282 @@ describe('Websock', function () {
letterCode = 'a'.charCodeAt(0); letterCode = 'a'.charCodeAt(0);
} }
} }
sock._rQ.set(RQ_BIG); websock._receiveData(incoming);
sock._rQlen = RQ_BIG.length;
const shifted = sock.rQshiftStr(); const shifted = sock.rQshiftStr(BIG_LEN);
expect(shifted).to.be.equal(expected); expect(shifted).to.be.equal(expected);
expect(sock.rQlen).to.equal(0);
}); });
}); });
describe('rQshiftBytes', function () { describe('rQshiftBytes', function () {
it('should shift the given number of bytes of the receive queue and return an array', function () { it('should shift the given number of bytes of the receive queue and return an array', function () {
const befLen = sock.rQlen; websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
const befRQi = sock.rQi; 0x88, 0xee, 0x11, 0x33]));
const shifted = sock.rQshiftBytes(3); expect(sock.rQshiftBytes(4)).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
expect(shifted).to.be.an.instanceof(Uint8Array); expect(sock.rQshiftBytes(4)).to.array.equal(new Uint8Array([0x88, 0xee, 0x11, 0x33]));
expect(shifted).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, befRQi, 3));
expect(sock.rQlen).to.equal(befLen - 3);
}); });
it('should shift the entire rest of the queue off if no length is given', function () { it('should return a shared array if requested', function () {
sock.rQshiftBytes(); websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
expect(sock.rQlen).to.equal(0); 0x88, 0xee, 0x11, 0x33]));
const bytes = sock.rQshiftBytes(4, false);
expect(bytes).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
expect(bytes.buffer.byteLength).to.not.equal(bytes.length);
}); });
}); });
describe('rQslice', function () { describe('rQpeekBytes', function () {
beforeEach(function () {
sock.rQi = 0;
});
it('should not modify the receive queue', function () { it('should not modify the receive queue', function () {
const befLen = sock.rQlen; websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
sock.rQslice(0, 2); 0x88, 0xee, 0x11, 0x33]));
expect(sock.rQlen).to.equal(befLen); expect(sock.rQpeekBytes(4)).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
expect(sock.rQpeekBytes(4)).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
}); });
it('should return an array containing the given slice of the receive queue', function () { it('should return a shared array if requested', function () {
const sl = sock.rQslice(0, 2); websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
expect(sl).to.be.an.instanceof(Uint8Array); 0x88, 0xee, 0x11, 0x33]));
expect(sl).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 0, 2)); const bytes = sock.rQpeekBytes(4, false);
}); expect(bytes).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
expect(bytes.buffer.byteLength).to.not.equal(bytes.length);
it('should use the rest of the receive queue if no end is given', function () {
const sl = sock.rQslice(1);
expect(sl).to.have.length(RQ_TEMPLATE.length - 1);
expect(sl).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 1));
});
it('should take the current rQi in to account', function () {
sock.rQi = 1;
expect(sock.rQslice(0, 2)).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 1, 2));
}); });
}); });
describe('rQwait', function () { describe('rQwait', function () {
beforeEach(function () { beforeEach(function () {
sock.rQi = 0; websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
0x88, 0xee, 0x11, 0x33]));
}); });
it('should return true if there are not enough bytes in the receive queue', function () { it('should return true if there are not enough bytes in the receive queue', function () {
expect(sock.rQwait('hi', RQ_TEMPLATE.length + 1)).to.be.true; expect(sock.rQwait('hi', 9)).to.be.true;
}); });
it('should return false if there are enough bytes in the receive queue', function () { it('should return false if there are enough bytes in the receive queue', function () {
expect(sock.rQwait('hi', RQ_TEMPLATE.length)).to.be.false; expect(sock.rQwait('hi', 8)).to.be.false;
}); });
it('should return true and reduce rQi by "goback" if there are not enough bytes', function () { it('should return true and reduce rQi by "goback" if there are not enough bytes', function () {
sock.rQi = 5; expect(sock.rQshift32()).to.equal(0xabcd1234);
expect(sock.rQwait('hi', RQ_TEMPLATE.length, 4)).to.be.true; expect(sock.rQwait('hi', 8, 2)).to.be.true;
expect(sock.rQi).to.equal(1); expect(sock.rQshift32()).to.equal(0x123488ee);
}); });
it('should raise an error if we try to go back more than possible', function () { it('should raise an error if we try to go back more than possible', function () {
sock.rQi = 5; expect(sock.rQshift32()).to.equal(0xabcd1234);
expect(() => sock.rQwait('hi', RQ_TEMPLATE.length, 6)).to.throw(Error); expect(() => sock.rQwait('hi', 8, 6)).to.throw(Error);
}); });
it('should not reduce rQi if there are enough bytes', function () { it('should not reduce rQi if there are enough bytes', function () {
sock.rQi = 5; expect(sock.rQshift32()).to.equal(0xabcd1234);
sock.rQwait('hi', 1, 6); expect(sock.rQwait('hi', 4, 2)).to.be.false;
expect(sock.rQi).to.equal(5); expect(sock.rQshift32()).to.equal(0x88ee1133);
});
});
});
describe('Send queue methods', function () {
let sock;
const bufferSize = 10 * 1024;
beforeEach(function () {
let websock = new FakeWebSocket();
websock._open();
sock = new Websock();
sock.attach(websock);
});
describe('sQpush8()', function () {
it('should send a single byte', function () {
sock.sQpush8(42);
sock.flush();
expect(sock).to.have.sent(new Uint8Array([42]));
});
it('should not send any data until flushing', function () {
sock.sQpush8(42);
expect(sock).to.have.sent(new Uint8Array([]));
});
it('should implicitly flush if the queue is full', function () {
for (let i = 0;i <= bufferSize;i++) {
sock.sQpush8(42);
}
let expected = [];
for (let i = 0;i < bufferSize;i++) {
expected.push(42);
}
expect(sock).to.have.sent(new Uint8Array(expected));
});
});
describe('sQpush16()', function () {
it('should send a number as two bytes', function () {
sock.sQpush16(420);
sock.flush();
expect(sock).to.have.sent(new Uint8Array([1, 164]));
});
it('should not send any data until flushing', function () {
sock.sQpush16(420);
expect(sock).to.have.sent(new Uint8Array([]));
});
it('should implicitly flush if the queue is full', function () {
for (let i = 0;i <= bufferSize/2;i++) {
sock.sQpush16(420);
}
let expected = [];
for (let i = 0;i < bufferSize/2;i++) {
expected.push(1);
expected.push(164);
}
expect(sock).to.have.sent(new Uint8Array(expected));
});
});
describe('sQpush32()', function () {
it('should send a number as two bytes', function () {
sock.sQpush32(420420);
sock.flush();
expect(sock).to.have.sent(new Uint8Array([0, 6, 106, 68]));
});
it('should not send any data until flushing', function () {
sock.sQpush32(420420);
expect(sock).to.have.sent(new Uint8Array([]));
});
it('should implicitly flush if the queue is full', function () {
for (let i = 0;i <= bufferSize/4;i++) {
sock.sQpush32(420420);
}
let expected = [];
for (let i = 0;i < bufferSize/4;i++) {
expected.push(0);
expected.push(6);
expected.push(106);
expected.push(68);
}
expect(sock).to.have.sent(new Uint8Array(expected));
});
});
describe('sQpushString()', function () {
it('should send a string buffer', function () {
sock.sQpushString('\x12\x34\x56\x78\x90');
sock.flush();
expect(sock).to.have.sent(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));
});
it('should not send any data until flushing', function () {
sock.sQpushString('\x12\x34\x56\x78\x90');
expect(sock).to.have.sent(new Uint8Array([]));
});
it('should implicitly flush if the queue is full', function () {
for (let i = 0;i <= bufferSize/5;i++) {
sock.sQpushString('\x12\x34\x56\x78\x90');
}
let expected = [];
for (let i = 0;i < bufferSize/5;i++) {
expected.push(0x12);
expected.push(0x34);
expected.push(0x56);
expected.push(0x78);
expected.push(0x90);
}
expect(sock).to.have.sent(new Uint8Array(expected));
});
it('should implicitly split a large buffer', function () {
let str = '';
for (let i = 0;i <= bufferSize/5;i++) {
str += '\x12\x34\x56\x78\x90';
}
sock.sQpushString(str);
let expected = [];
for (let i = 0;i < bufferSize/5;i++) {
expected.push(0x12);
expected.push(0x34);
expected.push(0x56);
expected.push(0x78);
expected.push(0x90);
}
expect(sock).to.have.sent(new Uint8Array(expected));
});
});
describe('sQpushBytes()', function () {
it('should send a byte buffer', function () {
sock.sQpushBytes(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));
sock.flush();
expect(sock).to.have.sent(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));
});
it('should not send any data until flushing', function () {
sock.sQpushBytes(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));
expect(sock).to.have.sent(new Uint8Array([]));
});
it('should implicitly flush if the queue is full', function () {
for (let i = 0;i <= bufferSize/5;i++) {
sock.sQpushBytes(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));
}
let expected = [];
for (let i = 0;i < bufferSize/5;i++) {
expected.push(0x12);
expected.push(0x34);
expected.push(0x56);
expected.push(0x78);
expected.push(0x90);
}
expect(sock).to.have.sent(new Uint8Array(expected));
});
it('should implicitly split a large buffer', function () {
let buffer = [];
for (let i = 0;i <= bufferSize/5;i++) {
buffer.push(0x12);
buffer.push(0x34);
buffer.push(0x56);
buffer.push(0x78);
buffer.push(0x90);
}
sock.sQpushBytes(new Uint8Array(buffer));
let expected = [];
for (let i = 0;i < bufferSize/5;i++) {
expected.push(0x12);
expected.push(0x34);
expected.push(0x56);
expected.push(0x78);
expected.push(0x90);
}
expect(sock).to.have.sent(new Uint8Array(expected));
}); });
}); });
describe('flush', function () { describe('flush', function () {
beforeEach(function () {
sock._websocket = {
send: sinon.spy()
};
});
it('should actually send on the websocket', function () { it('should actually send on the websocket', function () {
sock._websocket.bufferedAmount = 8;
sock._websocket.readyState = WebSocket.OPEN;
sock._sQ = new Uint8Array([1, 2, 3]); sock._sQ = new Uint8Array([1, 2, 3]);
sock._sQlen = 3; sock._sQlen = 3;
const encoded = sock._encodeMessage();
sock.flush(); sock.flush();
expect(sock._websocket.send).to.have.been.calledOnce; expect(sock).to.have.sent(new Uint8Array([1, 2, 3]));
expect(sock._websocket.send).to.have.been.calledWith(encoded);
}); });
it('should not call send if we do not have anything queued up', function () { it('should not call send if we do not have anything queued up', function () {
sock._sQlen = 0; sock._sQlen = 0;
sock._websocket.bufferedAmount = 8;
sock.flush(); sock.flush();
expect(sock._websocket.send).not.to.have.been.called; expect(sock).to.have.sent(new Uint8Array([]));
});
});
describe('send', function () {
beforeEach(function () {
sock.flush = sinon.spy();
});
it('should add to the send queue', function () {
sock.send([1, 2, 3]);
const sq = sock.sQ;
expect(new Uint8Array(sq.buffer, sock._sQlen - 3, 3)).to.array.equal(new Uint8Array([1, 2, 3]));
});
it('should call flush', function () {
sock.send([1, 2, 3]);
expect(sock.flush).to.have.been.calledOnce;
});
});
describe('sendString', function () {
beforeEach(function () {
sock.send = sinon.spy();
});
it('should call send after converting the string to an array', function () {
sock.sendString("\x01\x02\x03");
expect(sock.send).to.have.been.calledWith([1, 2, 3]);
}); });
}); });
}); });
@@ -450,9 +558,8 @@ describe('Websock', function () {
sock._allocateBuffers(); sock._allocateBuffers();
}); });
it('should support adding binary Uint8Array data to the receive queue', function () { it('should support adding data to the receive queue', function () {
const msg = { data: new Uint8Array([1, 2, 3]) }; const msg = { data: new Uint8Array([1, 2, 3]) };
sock._mode = 'binary';
sock._recvMessage(msg); sock._recvMessage(msg);
expect(sock.rQshiftStr(3)).to.equal('\x01\x02\x03'); expect(sock.rQshiftStr(3)).to.equal('\x01\x02\x03');
}); });
@@ -473,80 +580,49 @@ describe('Websock', function () {
expect(sock._eventHandlers.message).not.to.have.been.called; expect(sock._eventHandlers.message).not.to.have.been.called;
}); });
it('should compact the receive queue when a message handler empties it', function () { it('should compact the receive queue when fully read', function () {
sock._eventHandlers.message = () => { sock.rQi = sock._rQlen; };
sock._rQ = new Uint8Array([0, 1, 2, 3, 4, 5, 0, 0, 0, 0]); sock._rQ = new Uint8Array([0, 1, 2, 3, 4, 5, 0, 0, 0, 0]);
sock._rQlen = 6; sock._rQlen = 6;
sock.rQi = 6; sock._rQi = 6;
const msg = { data: new Uint8Array([1, 2, 3]).buffer }; const msg = { data: new Uint8Array([1, 2, 3]).buffer };
sock._mode = 'binary';
sock._recvMessage(msg); sock._recvMessage(msg);
expect(sock._rQlen).to.equal(0); expect(sock._rQlen).to.equal(3);
expect(sock.rQi).to.equal(0); expect(sock._rQi).to.equal(0);
}); });
it('should compact the receive queue when we reach the end of the buffer', function () { it('should compact the receive queue when we reach the end of the buffer', function () {
sock._rQ = new Uint8Array(20); sock._rQ = new Uint8Array(20);
sock._rQbufferSize = 20; sock._rQbufferSize = 20;
sock._rQlen = 20; sock._rQlen = 20;
sock.rQi = 10; sock._rQi = 10;
const msg = { data: new Uint8Array([1, 2]).buffer }; const msg = { data: new Uint8Array([1, 2]).buffer };
sock._mode = 'binary';
sock._recvMessage(msg); sock._recvMessage(msg);
expect(sock._rQlen).to.equal(12); expect(sock._rQlen).to.equal(12);
expect(sock.rQi).to.equal(0); expect(sock._rQi).to.equal(0);
}); });
it('should automatically resize the receive queue if the incoming message is larger than the buffer', function () { it('should automatically resize the receive queue if the incoming message is larger than the buffer', function () {
sock._rQ = new Uint8Array(20); sock._rQ = new Uint8Array(20);
sock._rQlen = 0; sock._rQlen = 0;
sock.rQi = 0; sock._rQi = 0;
sock._rQbufferSize = 20; sock._rQbufferSize = 20;
const msg = { data: new Uint8Array(30).buffer }; const msg = { data: new Uint8Array(30).buffer };
sock._mode = 'binary';
sock._recvMessage(msg); sock._recvMessage(msg);
expect(sock._rQlen).to.equal(30); expect(sock._rQlen).to.equal(30);
expect(sock.rQi).to.equal(0); expect(sock._rQi).to.equal(0);
expect(sock._rQ.length).to.equal(240); // keep the invariant that rQbufferSize / 8 >= rQlen expect(sock._rQ.length).to.equal(240); // keep the invariant that rQbufferSize / 8 >= rQlen
}); });
it('should automatically resize the receive queue if the incoming message is larger than 1/8th of the buffer and we reach the end of the buffer', function () { it('should automatically resize the receive queue if the incoming message is larger than 1/8th of the buffer and we reach the end of the buffer', function () {
sock._rQ = new Uint8Array(20); sock._rQ = new Uint8Array(20);
sock._rQlen = 16; sock._rQlen = 16;
sock.rQi = 16; sock._rQi = 15;
sock._rQbufferSize = 20; sock._rQbufferSize = 20;
const msg = { data: new Uint8Array(6).buffer }; const msg = { data: new Uint8Array(6).buffer };
sock._mode = 'binary';
sock._recvMessage(msg); sock._recvMessage(msg);
expect(sock._rQlen).to.equal(6); expect(sock._rQlen).to.equal(7);
expect(sock.rQi).to.equal(0); expect(sock._rQi).to.equal(0);
expect(sock._rQ.length).to.equal(48); expect(sock._rQ.length).to.equal(56);
});
});
describe('Data encoding', function () {
before(function () { FakeWebSocket.replace(); });
after(function () { FakeWebSocket.restore(); });
describe('as binary data', function () {
let sock;
beforeEach(function () {
sock = new Websock();
sock.open('ws://', 'binary');
sock._websocket._open();
});
it('should only send the send queue up to the send queue length', function () {
sock._sQ = new Uint8Array([1, 2, 3, 4, 5]);
sock._sQlen = 3;
const res = sock._encodeMessage();
expect(res).to.array.equal(new Uint8Array([1, 2, 3]));
});
it('should properly pass the encoded data off to the actual WebSocket', function () {
sock.send([1, 2, 3]);
expect(sock._websocket._getSentData()).to.array.equal(new Uint8Array([1, 2, 3]));
});
}); });
}); });
}); });
+32 -8
View File
@@ -8,39 +8,48 @@ describe('WebUtil', function () {
"use strict"; "use strict";
describe('config variables', function () { describe('config variables', function () {
let origState, origHref;
beforeEach(function () {
origState = history.state;
origHref = location.href;
});
afterEach(function () {
history.replaceState(origState, '', origHref);
});
it('should parse query string variables', function () { it('should parse query string variables', function () {
// history.pushState() will not cause the browser to attempt loading // history.pushState() will not cause the browser to attempt loading
// the URL, this is exactly what we want here for the tests. // the URL, this is exactly what we want here for the tests.
history.pushState({}, '', "test?myvar=myval"); history.replaceState({}, '', "test?myvar=myval");
expect(WebUtil.getConfigVar("myvar")).to.be.equal("myval"); expect(WebUtil.getConfigVar("myvar")).to.be.equal("myval");
}); });
it('should return default value when no query match', function () { it('should return default value when no query match', function () {
history.pushState({}, '', "test?myvar=myval"); history.replaceState({}, '', "test?myvar=myval");
expect(WebUtil.getConfigVar("other", "def")).to.be.equal("def"); expect(WebUtil.getConfigVar("other", "def")).to.be.equal("def");
}); });
it('should handle no query match and no default value', function () { it('should handle no query match and no default value', function () {
history.pushState({}, '', "test?myvar=myval"); history.replaceState({}, '', "test?myvar=myval");
expect(WebUtil.getConfigVar("other")).to.be.equal(null); expect(WebUtil.getConfigVar("other")).to.be.equal(null);
}); });
it('should parse fragment variables', function () { it('should parse fragment variables', function () {
history.pushState({}, '', "test#myvar=myval"); history.replaceState({}, '', "test#myvar=myval");
expect(WebUtil.getConfigVar("myvar")).to.be.equal("myval"); expect(WebUtil.getConfigVar("myvar")).to.be.equal("myval");
}); });
it('should return default value when no fragment match', function () { it('should return default value when no fragment match', function () {
history.pushState({}, '', "test#myvar=myval"); history.replaceState({}, '', "test#myvar=myval");
expect(WebUtil.getConfigVar("other", "def")).to.be.equal("def"); expect(WebUtil.getConfigVar("other", "def")).to.be.equal("def");
}); });
it('should handle no fragment match and no default value', function () { it('should handle no fragment match and no default value', function () {
history.pushState({}, '', "test#myvar=myval"); history.replaceState({}, '', "test#myvar=myval");
expect(WebUtil.getConfigVar("other")).to.be.equal(null); expect(WebUtil.getConfigVar("other")).to.be.equal(null);
}); });
it('should handle both query and fragment', function () { it('should handle both query and fragment', function () {
history.pushState({}, '', "test?myquery=1#myhash=2"); history.replaceState({}, '', "test?myquery=1#myhash=2");
expect(WebUtil.getConfigVar("myquery")).to.be.equal("1"); expect(WebUtil.getConfigVar("myquery")).to.be.equal("1");
expect(WebUtil.getConfigVar("myhash")).to.be.equal("2"); expect(WebUtil.getConfigVar("myhash")).to.be.equal("2");
}); });
it('should prioritize fragment if both provide same var', function () { it('should prioritize fragment if both provide same var', function () {
history.pushState({}, '', "test?myvar=1#myvar=2"); history.replaceState({}, '', "test?myvar=1#myvar=2");
expect(WebUtil.getConfigVar("myvar")).to.be.equal("2"); expect(WebUtil.getConfigVar("myvar")).to.be.equal("2");
}); });
}); });
@@ -83,6 +92,11 @@ describe('WebUtil', function () {
expect(window.localStorage.setItem).to.have.been.calledWithExactly('test', 'value'); expect(window.localStorage.setItem).to.have.been.calledWithExactly('test', 'value');
expect(WebUtil.readSetting('test')).to.equal('value'); expect(WebUtil.readSetting('test')).to.equal('value');
}); });
it('should not crash when local storage save fails', function () {
localStorage.setItem.throws(new DOMException());
expect(WebUtil.writeSetting('test', 'value')).to.not.throw;
});
}); });
describe('setSetting', function () { describe('setSetting', function () {
@@ -128,6 +142,11 @@ describe('WebUtil', function () {
WebUtil.writeSetting('test', 'something else'); WebUtil.writeSetting('test', 'something else');
expect(WebUtil.readSetting('test')).to.equal('something else'); expect(WebUtil.readSetting('test')).to.equal('something else');
}); });
it('should not crash when local storage read fails', function () {
localStorage.getItem.throws(new DOMException());
expect(WebUtil.readSetting('test')).to.not.throw;
});
}); });
// this doesn't appear to be used anywhere // this doesn't appear to be used anywhere
@@ -136,6 +155,11 @@ describe('WebUtil', function () {
WebUtil.eraseSetting('test'); WebUtil.eraseSetting('test');
expect(window.localStorage.removeItem).to.have.been.calledWithExactly('test'); expect(window.localStorage.removeItem).to.have.been.calledWithExactly('test');
}); });
it('should not crash when local storage remove fails', function () {
localStorage.removeItem.throws(new DOMException());
expect(WebUtil.eraseSetting('test')).to.not.throw;
});
}); });
}); });
+37 -17
View File
@@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) { function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock; let sock;
let done = false;
sock = new Websock; sock = new Websock;
sock.open("ws://example.com"); sock.open("ws://example.com");
sock.on('message', () => { sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth); done = decoder.decodeRect(x, y, width, height, sock, display, depth);
}); });
// Empty messages are filtered at multiple layers, so we need to // Empty messages are filtered at multiple layers, so we need to
// do a direct call // do a direct call
if (data.length === 0) { if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth); done = decoder.decodeRect(x, y, width, height, sock, display, depth);
} else { } else {
sock._websocket._receiveData(new Uint8Array(data)); sock._websocket._receiveData(new Uint8Array(data));
} }
display.flip(); display.flip();
return done;
} }
describe('ZRLE Decoder', function () { describe('ZRLE Decoder', function () {
@@ -42,9 +45,11 @@ describe('ZRLE Decoder', function () {
}); });
it('should handle the Raw subencoding', function () { it('should handle the Raw subencoding', function () {
testDecodeRect(decoder, 0, 0, 4, 4, let done = testDecodeRect(decoder, 0, 0, 4, 4,
[0x00, 0x00, 0x00, 0x0e, 0x78, 0x5e, 0x62, 0x60, 0x60, 0xf8, 0x4f, 0x12, 0x02, 0x00, 0x00, 0x00, 0xff, 0xff], [0x00, 0x00, 0x00, 0x0e, 0x78, 0x5e,
display, 24); 0x62, 0x60, 0x60, 0xf8, 0x4f, 0x12,
0x02, 0x00, 0x00, 0x00, 0xff, 0xff],
display, 24);
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
@@ -53,13 +58,16 @@ describe('ZRLE Decoder', function () {
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff
]); ]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData); expect(display).to.have.displayed(targetData);
}); });
it('should handle the Solid subencoding', function () { it('should handle the Solid subencoding', function () {
testDecodeRect(decoder, 0, 0, 4, 4, let done = testDecodeRect(decoder, 0, 0, 4, 4,
[0x00, 0x00, 0x00, 0x0c, 0x78, 0x5e, 0x62, 0x64, 0x60, 0xf8, 0x0f, 0x00, 0x00, 0x00, 0xff, 0xff], [0x00, 0x00, 0x00, 0x0c, 0x78, 0x5e,
display, 24); 0x62, 0x64, 0x60, 0xf8, 0x0f, 0x00,
0x00, 0x00, 0xff, 0xff],
display, 24);
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
@@ -68,14 +76,18 @@ describe('ZRLE Decoder', function () {
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff
]); ]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData); expect(display).to.have.displayed(targetData);
}); });
it('should handle the Palette Tile subencoding', function () { it('should handle the Palette Tile subencoding', function () {
testDecodeRect(decoder, 0, 0, 4, 4, let done = testDecodeRect(decoder, 0, 0, 4, 4,
[0x00, 0x00, 0x00, 0x12, 0x78, 0x5E, 0x62, 0x62, 0x60, 248, 0xff, 0x9F, 0x01, 0x08, 0x3E, 0x7C, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff], [0x00, 0x00, 0x00, 0x12, 0x78, 0x5E,
display, 24); 0x62, 0x62, 0x60, 248, 0xff, 0x9F,
0x01, 0x08, 0x3E, 0x7C, 0x00, 0x00,
0x00, 0x00, 0xff, 0xff],
display, 24);
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
@@ -84,13 +96,16 @@ describe('ZRLE Decoder', function () {
0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff
]); ]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData); expect(display).to.have.displayed(targetData);
}); });
it('should handle the RLE Tile subencoding', function () { it('should handle the RLE Tile subencoding', function () {
testDecodeRect(decoder, 0, 0, 4, 4, let done = testDecodeRect(decoder, 0, 0, 4, 4,
[0x00, 0x00, 0x00, 0x0d, 0x78, 0x5e, 0x6a, 0x60, 0x60, 0xf8, 0x2f, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff], [0x00, 0x00, 0x00, 0x0d, 0x78, 0x5e,
display, 24); 0x6a, 0x60, 0x60, 0xf8, 0x2f, 0x00,
0x00, 0x00, 0x00, 0xff, 0xff],
display, 24);
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
@@ -99,13 +114,17 @@ describe('ZRLE Decoder', function () {
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff
]); ]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData); expect(display).to.have.displayed(targetData);
}); });
it('should handle the RLE Palette Tile subencoding', function () { it('should handle the RLE Palette Tile subencoding', function () {
testDecodeRect(decoder, 0, 0, 4, 4, let done = testDecodeRect(decoder, 0, 0, 4, 4,
[0x00, 0x00, 0x00, 0x11, 0x78, 0x5e, 0x6a, 0x62, 0x60, 0xf8, 0xff, 0x9f, 0x81, 0xa1, 0x81, 0x1f, 0x00, 0x00, 0x00, 0xff, 0xff], [0x00, 0x00, 0x00, 0x11, 0x78, 0x5e,
display, 24); 0x6a, 0x62, 0x60, 0xf8, 0xff, 0x9f,
0x81, 0xa1, 0x81, 0x1f, 0x00, 0x00,
0x00, 0xff, 0xff],
display, 24);
let targetData = new Uint8Array([ let targetData = new Uint8Array([
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
@@ -114,6 +133,7 @@ describe('ZRLE Decoder', function () {
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff
]); ]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData); expect(display).to.have.displayed(targetData);
}); });
+38 -18
View File
@@ -8,13 +8,13 @@ usage() {
echo "$*" echo "$*"
echo echo
fi fi
echo "Usage: ${NAME} [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only]" echo "Usage: ${NAME} [--listen [HOST:]PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only]"
echo echo
echo "Starts the WebSockets proxy and a mini-webserver and " echo "Starts the WebSockets proxy and a mini-webserver and "
echo "provides a cut-and-paste URL to go to." echo "provides a cut-and-paste URL to go to."
echo echo
echo " --listen PORT Port for proxy/webserver to listen on" echo " --listen [HOST:]PORT Port for proxy/webserver to listen on"
echo " Default: 6080" echo " Default: 6080 (on all interfaces)"
echo " --vnc VNC_HOST:PORT VNC server host:port proxy target" echo " --vnc VNC_HOST:PORT VNC server host:port proxy target"
echo " Default: localhost:5900" echo " Default: localhost:5900"
echo " --cert CERT Path to combined cert/key file, or just" echo " --cert CERT Path to combined cert/key file, or just"
@@ -47,14 +47,16 @@ usage() {
NAME="$(basename $0)" NAME="$(basename $0)"
REAL_NAME="$(readlink -f $0)" REAL_NAME="$(readlink -f $0)"
HERE="$(cd "$(dirname "$REAL_NAME")" && pwd)" HERE="$(cd "$(dirname "$REAL_NAME")" && pwd)"
HOST=""
PORT="6080" PORT="6080"
LISTEN="$PORT"
VNC_DEST="localhost:5900" VNC_DEST="localhost:5900"
CERT="" CERT=""
KEY="" KEY=""
WEB="" WEB=""
proxy_pid="" proxy_pid=""
SSLONLY="" SSLONLY=""
RECORD_ARG="" RECORD=""
SYSLOG_ARG="" SYSLOG_ARG=""
HEARTBEAT_ARG="" HEARTBEAT_ARG=""
IDLETIMEOUT_ARG="" IDLETIMEOUT_ARG=""
@@ -86,14 +88,14 @@ cleanup() {
while [ "$*" ]; do while [ "$*" ]; do
param=$1; shift; OPTARG=$1 param=$1; shift; OPTARG=$1
case $param in case $param in
--listen) PORT="${OPTARG}"; shift ;; --listen) LISTEN="${OPTARG}"; shift ;;
--vnc) VNC_DEST="${OPTARG}"; shift ;; --vnc) VNC_DEST="${OPTARG}"; shift ;;
--cert) CERT="${OPTARG}"; shift ;; --cert) CERT="${OPTARG}"; shift ;;
--key) KEY="${OPTARG}"; shift ;; --key) KEY="${OPTARG}"; shift ;;
--web) WEB="${OPTARG}"; shift ;; --web) WEB="${OPTARG}"; shift ;;
--ssl-only) SSLONLY="--ssl-only" ;; --ssl-only) SSLONLY="--ssl-only" ;;
--file-only) FILEONLY_ARG="--file-only" ;; --file-only) FILEONLY_ARG="--file-only" ;;
--record) RECORD_ARG="--record ${OPTARG}"; shift ;; --record) RECORD="${OPTARG}"; shift ;;
--syslog) SYSLOG_ARG="--syslog ${OPTARG}"; shift ;; --syslog) SYSLOG_ARG="--syslog ${OPTARG}"; shift ;;
--heartbeat) HEARTBEAT_ARG="--heartbeat ${OPTARG}"; shift ;; --heartbeat) HEARTBEAT_ARG="--heartbeat ${OPTARG}"; shift ;;
--idle-timeout) IDLETIMEOUT_ARG="--idle-timeout ${OPTARG}"; shift ;; --idle-timeout) IDLETIMEOUT_ARG="--idle-timeout ${OPTARG}"; shift ;;
@@ -107,14 +109,23 @@ while [ "$*" ]; do
esac esac
done done
if [ "$LISTEN" != "$PORT" ]; then
HOST=${LISTEN%:*}
PORT=${LISTEN##*:}
# if no host was given, restore
[ "$HOST" = "$PORT" ] && HOST=""
fi
# Sanity checks # Sanity checks
if bash -c "exec 7<>/dev/tcp/localhost/${PORT}" &> /dev/null; then if [ -z "${HOST}" ]; then
exec 7<&- if bash -c "exec 7<>/dev/tcp/localhost/${PORT}" &> /dev/null; then
exec 7>&- exec 7<&-
die "Port ${PORT} in use. Try --listen PORT" exec 7>&-
else die "Port ${PORT} in use. Try --listen PORT"
exec 7<&- else
exec 7>&- exec 7<&-
exec 7>&-
fi
fi fi
trap "cleanup" TERM QUIT INT EXIT trap "cleanup" TERM QUIT INT EXIT
@@ -191,9 +202,14 @@ else
fi fi
fi fi
echo "Starting webserver and WebSockets proxy on port ${PORT}" # Make all file paths absolute as websockify changes working directory
#${HERE}/websockify --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} & WEB=`realpath "${WEB}"`
${WEBSOCKIFY} ${SYSLOG_ARG} ${SSLONLY} ${FILEONLY_ARG} --web ${WEB} ${CERT:+--cert ${CERT}} ${KEY:+--key ${KEY}} ${PORT} ${VNC_DEST} ${HEARTBEAT_ARG} ${IDLETIMEOUT_ARG} ${RECORD_ARG} ${TIMEOUT_ARG} ${WEBAUTH_ARG} ${AUTHPLUGIN_ARG} ${AUTHSOURCE_ARG} & [ -n "${CERT}" ] && CERT=`realpath "${CERT}"`
[ -n "${KEY}" ] && KEY=`realpath "${KEY}"`
[ -n "${RECORD}" ] && RECORD=`realpath "${RECORD}"`
echo "Starting webserver and WebSockets proxy on${HOST:+ host ${HOST}} port ${PORT}"
${WEBSOCKIFY} ${SYSLOG_ARG} ${SSLONLY} ${FILEONLY_ARG} --web ${WEB} ${CERT:+--cert ${CERT}} ${KEY:+--key ${KEY}} ${LISTEN} ${VNC_DEST} ${HEARTBEAT_ARG} ${IDLETIMEOUT_ARG} ${RECORD:+--record ${RECORD}} ${TIMEOUT_ARG} ${WEBAUTH_ARG} ${AUTHPLUGIN_ARG} ${AUTHSOURCE_ARG} &
proxy_pid="$!" proxy_pid="$!"
sleep 1 sleep 1
if [ -z "$proxy_pid" ] || ! ps -eo pid= | grep -w "$proxy_pid" > /dev/null; then if [ -z "$proxy_pid" ] || ! ps -eo pid= | grep -w "$proxy_pid" > /dev/null; then
@@ -202,11 +218,15 @@ if [ -z "$proxy_pid" ] || ! ps -eo pid= | grep -w "$proxy_pid" > /dev/null; then
exit 1 exit 1
fi fi
if [ -z "$HOST" ]; then
HOST=$(hostname)
fi
echo -e "\n\nNavigate to this URL:\n" echo -e "\n\nNavigate to this URL:\n"
if [ "x$SSLONLY" == "x" ]; then if [ "x$SSLONLY" == "x" ]; then
echo -e " http://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n" echo -e " http://${HOST}:${PORT}/vnc.html?host=${HOST}&port=${PORT}\n"
else else
echo -e " https://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n" echo -e " https://${HOST}:${PORT}/vnc.html?host=${HOST}&port=${PORT}\n"
fi fi
echo -e "Press Ctrl-C to exit\n\n" echo -e "Press Ctrl-C to exit\n\n"
+2 -9
View File
@@ -107,20 +107,13 @@
// query string. If the variable isn't defined in the URL // query string. If the variable isn't defined in the URL
// it returns the default value instead. // it returns the default value instead.
function readQueryVariable(name, defaultValue) { function readQueryVariable(name, defaultValue) {
// A URL with a query parameter can look like this (But will most probably get logged on the http server): // A URL with a query parameter can look like this:
// https://www.example.com?myqueryparam=myvalue // https://www.example.com?myqueryparam=myvalue
// //
// For privacy (Using a hastag #, the parameters will not be sent to the server)
// the url can be requested in the following way:
// https://www.example.com#myqueryparam=myvalue&password=secreatvalue
//
// Even Mixing public and non public parameters will work:
// https://www.example.com?nonsecretparam=example.com#password=secreatvalue
//
// Note that we use location.href instead of location.search // Note that we use location.href instead of location.search
// because Firefox < 53 has a bug w.r.t location.search // because Firefox < 53 has a bug w.r.t location.search
const re = new RegExp('.*[?&]' + name + '=([^&#]*)'), const re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
match = ''.concat(document.location.href, window.location.hash).match(re); match = document.location.href.match(re);
if (match) { if (match) {
// We have to decode the URL since want the cleartext value // We have to decode the URL since want the cleartext value