update noVNC to 1.5.0
Signed-off-by: si458 <simonsmith5521@gmail.com>
si458 committed
Sep 29, 2024 at 15:26 UTC
1139a373387d55f5e5783296bb8ffedf9a81fe87
34 files changed
+1723
-1029
public/novnc/app/locale/el.json
+21
-11
@@ -1,4 +1,5 @@
1
{
2
+ "HTTPS is required for full functionality": "Το HTTPS είναι απαιτούμενο για πλήρη λειτουργικότητα",
3
"Connecting...": "Συνδέεται...",
4
"Disconnecting...": "Aποσυνδέεται...",
5
"Reconnecting...": "Επανασυνδέεται...",
@@ -7,19 +8,15 @@
8
"Connected (encrypted) to ": "Συνδέθηκε (κρυπτογραφημένα) με το ",
9
"Connected (unencrypted) to ": "Συνδέθηκε (μη κρυπτογραφημένα) με το ",
10
"Something went wrong, connection is closed": "Κάτι πήγε στραβά, η σύνδεση διακόπηκε",
11
+ "Failed to connect to server": "Αποτυχία στη σύνδεση με το διακομιστή",
12
"Disconnected": "Αποσυνδέθηκε",
13
"New connection has been rejected with reason: ": "Η νέα σύνδεση απορρίφθηκε διότι: ",
14
"New connection has been rejected": "Η νέα σύνδεση απορρίφθηκε ",
13
- "Password is required": "Απαιτείται ο κωδικός πρόσβασης",
15
+ "Credentials are required": "Απαιτούνται διαπιστευτήρια",
16
"noVNC encountered an error:": "το noVNC αντιμετώπισε ένα σφάλμα:",
17
"Hide/Show the control bar": "Απόκρυψη/Εμφάνιση γραμμής ελέγχου",
18
+ "Drag": "Σύρσιμο",
19
"Move/Drag Viewport": "Μετακίνηση/Σύρσιμο Θεατού πεδίου",
17
- "viewport drag": "σύρσιμο θεατού πεδίου",
18
- "Active Mouse Button": "Ενεργό Πλήκτρο Ποντικιού",
19
- "No mousebutton": "Χωρίς Πλήκτρο Ποντικιού",
20
- "Left mousebutton": "Αριστερό Πλήκτρο Ποντικιού",
21
- "Middle mousebutton": "Μεσαίο Πλήκτρο Ποντικιού",
22
- "Right mousebutton": "Δεξί Πλήκτρο Ποντικιού",
20
"Keyboard": "Πληκτρολόγιο",
21
"Show Keyboard": "Εμφάνιση Πληκτρολογίου",
22
"Extra keys": "Επιπλέον πλήκτρα",
@@ -28,6 +25,8 @@
25
"Toggle Ctrl": "Εναλλαγή Ctrl",
26
"Alt": "Alt",
27
"Toggle Alt": "Εναλλαγή Alt",
28
+ "Toggle Windows": "Εναλλαγή Παράθυρων",
29
+ "Windows": "Παράθυρα",
30
"Send Tab": "Αποστολή Tab",
31
"Tab": "Tab",
32
"Esc": "Esc",
@@ -41,8 +40,7 @@
40
"Reboot": "Επανεκκίνηση",
41
"Reset": "Επαναφορά",
42
"Clipboard": "Πρόχειρο",
44
- "Clear": "Καθάρισμα",
45
- "Fullscreen": "Πλήρης Οθόνη",
43
+ "Edit clipboard content in the textarea below.": "Επεξεργαστείτε το περιεχόμενο του πρόχειρου στην περιοχή κειμένου παρακάτω.",
44
"Settings": "Ρυθμίσεις",
45
"Shared Mode": "Κοινόχρηστη Λειτουργία",
46
"View Only": "Μόνο Θέαση",
@@ -52,6 +50,8 @@
50
"Local Scaling": "Τοπική Κλιμάκωση",
51
"Remote Resizing": "Απομακρυσμένη Αλλαγή μεγέθους",
52
"Advanced": "Για προχωρημένους",
53
+ "Quality:": "Ποιότητα:",
54
+ "Compression level:": "Επίπεδο συμπίεσης:",
55
"Repeater ID:": "Repeater ID:",
56
"WebSocket": "WebSocket",
57
"Encrypt": "Κρυπτογράφηση",
@@ -60,10 +60,20 @@
60
"Path:": "Διαδρομή:",
61
"Automatic Reconnect": "Αυτόματη επανασύνδεση",
62
"Reconnect Delay (ms):": "Καθυστέρηση επανασύνδεσης (ms):",
63
+ "Show Dot when No Cursor": "Εμφάνιση Τελείας όταν δεν υπάρχει Δρομέας",
64
"Logging:": "Καταγραφή:",
65
+ "Version:": "Έκδοση:",
66
"Disconnect": "Αποσύνδεση",
67
"Connect": "Σύνδεση",
68
+ "Server identity": "Ταυτότητα Διακομιστή",
69
+ "The server has provided the following identifying information:": "Ο διακομιστής παρείχε την ακόλουθη πληροφορία ταυτοποίησης:",
70
+ "Fingerprint:": "Δακτυλικό αποτύπωμα:",
71
+ "Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "Παρακαλώ επαληθεύσετε ότι η πληροφορία είναι σωστή και πιέστε \"Αποδοχή\". Αλλιώς πιέστε \"Απόρριψη\".",
72
+ "Approve": "Αποδοχή",
73
+ "Reject": "Απόρριψη",
74
+ "Credentials": "Διαπιστευτήρια",
75
+ "Username:": "Κωδικός Χρήστη:",
76
"Password:": "Κωδικός Πρόσβασης:",
67
- "Cancel": "Ακύρωση",
68
- "Canvas not supported.": "Δεν υποστηρίζεται το στοιχείο Canvas"
77
+ "Send Credentials": "Αποστολή Διαπιστευτηρίων",
78
+ "Cancel": "Ακύρωση"
79
}
\ No newline at end of file
public/novnc/app/locale/fr.json
+2
-8
@@ -1,5 +1,4 @@
1
{
2
- "HTTPS is required for full functionality": "",
2
"Connecting...": "En cours de connexion...",
3
"Disconnecting...": "Déconnexion en cours...",
4
"Reconnecting...": "Reconnexion en cours...",
@@ -40,7 +39,8 @@
39
"Reboot": "Redémarrer",
40
"Reset": "Réinitialiser",
41
"Clipboard": "Presse-papiers",
43
- "Edit clipboard content in the textarea below.": "",
42
+ "Clear": "Effacer",
43
+ "Fullscreen": "Plein écran",
44
"Settings": "Paramètres",
45
"Shared Mode": "Mode partagé",
46
"View Only": "Afficher uniquement",
@@ -65,12 +65,6 @@
65
"Version:": "Version :",
66
"Disconnect": "Déconnecter",
67
"Connect": "Connecter",
68
- "Server identity": "",
69
- "The server has provided the following identifying information:": "",
70
- "Fingerprint:": "",
71
- "Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "",
72
- "Approve": "",
73
- "Reject": "",
68
"Username:": "Nom d'utilisateur :",
69
"Password:": "Mot de passe :",
70
"Send Credentials": "Envoyer les identifiants",
public/novnc/app/locale/it.json
-4
@@ -14,8 +14,6 @@
14
"Credentials are required": "Le credenziali sono obbligatorie",
15
"noVNC encountered an error:": "noVNC ha riscontrato un errore:",
16
"Hide/Show the control bar": "Nascondi/Mostra la barra di controllo",
17
- "Drag": "",
18
- "Move/Drag Viewport": "",
17
"Keyboard": "Tastiera",
18
"Show Keyboard": "Mostra tastiera",
19
"Extra keys": "Tasti Aggiuntivi",
@@ -44,7 +42,6 @@
42
"Settings": "Impostazioni",
43
"Shared Mode": "Modalità condivisa",
44
"View Only": "Sola Visualizzazione",
47
- "Clip to Window": "",
45
"Scaling Mode:": "Modalità di ridimensionamento:",
46
"None": "Nessuna",
47
"Local Scaling": "Ridimensionamento Locale",
@@ -61,7 +58,6 @@
58
"Automatic Reconnect": "Riconnessione Automatica",
59
"Reconnect Delay (ms):": "Ritardo Riconnessione (ms):",
60
"Show Dot when No Cursor": "Mostra Punto quando Nessun Cursore",
64
- "Logging:": "",
61
"Version:": "Versione:",
62
"Disconnect": "Disconnetti",
63
"Connect": "Connetti",
public/novnc/app/locale/ja.json
+15
-7
@@ -1,4 +1,5 @@
1
{
2
+ "HTTPS is required for full functionality": "すべての機能を使用するにはHTTPS接続が必要です",
3
"Connecting...": "接続しています...",
4
"Disconnecting...": "切断しています...",
5
"Reconnecting...": "再接続しています...",
@@ -21,10 +22,10 @@
22
"Extra keys": "追加キー",
23
"Show Extra Keys": "追加キーを表示",
24
"Ctrl": "Ctrl",
24
- "Toggle Ctrl": "Ctrl キーを切り替え",
25
+ "Toggle Ctrl": "Ctrl キーをトグル",
26
"Alt": "Alt",
26
- "Toggle Alt": "Alt キーを切り替え",
27
- "Toggle Windows": "Windows キーを切り替え",
27
+ "Toggle Alt": "Alt キーをトグル",
28
+ "Toggle Windows": "Windows キーをトグル",
29
"Windows": "Windows",
30
"Send Tab": "Tab キーを送信",
31
"Tab": "Tab",
@@ -39,11 +40,11 @@
40
"Reboot": "再起動",
41
"Reset": "リセット",
42
"Clipboard": "クリップボード",
42
- "Clear": "クリア",
43
- "Fullscreen": "全画面表示",
43
+ "Edit clipboard content in the textarea below.": "以下の入力欄からクリップボードの内容を編集できます。",
44
+ "Full Screen": "全画面表示",
45
"Settings": "設定",
46
"Shared Mode": "共有モード",
46
- "View Only": "表示のみ",
47
+ "View Only": "表示専用",
48
"Clip to Window": "ウィンドウにクリップ",
49
"Scaling Mode:": "スケーリングモード:",
50
"None": "なし",
@@ -60,11 +61,18 @@
61
"Path:": "パス:",
62
"Automatic Reconnect": "自動再接続",
63
"Reconnect Delay (ms):": "再接続する遅延 (ミリ秒):",
63
- "Show Dot when No Cursor": "カーソルがないときにドットを表示",
64
+ "Show Dot when No Cursor": "カーソルがないときにドットを表示する",
65
"Logging:": "ロギング:",
66
"Version:": "バージョン:",
67
"Disconnect": "切断",
68
"Connect": "接続",
69
+ "Server identity": "サーバーの識別情報",
70
+ "The server has provided the following identifying information:": "サーバーは以下の識別情報を提供しています:",
71
+ "Fingerprint:": "フィンガープリント:",
72
+ "Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "この情報が正しい場合は「承認」を、そうでない場合は「拒否」を押してください。",
73
+ "Approve": "承認",
74
+ "Reject": "拒否",
75
+ "Credentials": "資格情報",
76
"Username:": "ユーザー名:",
77
"Password:": "パスワード:",
78
"Send Credentials": "資格情報を送信",
public/novnc/app/locale/sv.json
+2
-1
@@ -1,10 +1,11 @@
1
{
2
- "HTTPS is required for full functionality": "HTTPS krävs för full funktionalitet",
2
+ "Running without HTTPS is not recommended, crashes or other issues are likely.": "Det är ej rekommenderat att köra utan HTTPS, krascher och andra problem är troliga.",
3
"Connecting...": "Ansluter...",
4
"Disconnecting...": "Kopplar ner...",
5
"Reconnecting...": "Återansluter...",
6
"Internal error": "Internt fel",
7
"Must set host": "Du måste specifiera en värd",
8
+ "Failed to connect to server: ": "Misslyckades att ansluta till servern: ",
9
"Connected (encrypted) to ": "Ansluten (krypterat) till ",
10
"Connected (unencrypted) to ": "Ansluten (okrypterat) till ",
11
"Something went wrong, connection is closed": "Något gick fel, anslutningen avslutades",
public/novnc/app/locale/zh_CN.json
+24
-24
@@ -1,69 +1,69 @@
1
{
2
"Connecting...": "连接中...",
3
+ "Connected (encrypted) to ": "已连接(已加密)到",
4
+ "Connected (unencrypted) to ": "已连接(未加密)到",
5
"Disconnecting...": "正在断开连接...",
4
- "Reconnecting...": "重新连接中...",
5
- "Internal error": "内部错误",
6
- "Must set host": "请提供主机名",
7
- "Connected (encrypted) to ": "已连接到(加密)",
8
- "Connected (unencrypted) to ": "已连接到(未加密)",
9
- "Something went wrong, connection is closed": "发生错误,连接已关闭",
10
- "Failed to connect to server": "无法连接到服务器",
6
"Disconnected": "已断开连接",
12
- "New connection has been rejected with reason: ": "连接被拒绝,原因:",
13
- "New connection has been rejected": "连接被拒绝",
7
+ "Must set host": "必须设置主机",
8
+ "Reconnecting...": "重新连接中...",
9
"Password is required": "请提供密码",
10
+ "Disconnect timeout": "超时断开",
11
"noVNC encountered an error:": "noVNC 遇到一个错误:",
12
"Hide/Show the control bar": "显示/隐藏控制栏",
17
- "Move/Drag Viewport": "拖放显示范围",
18
- "viewport drag": "显示范围拖放",
19
- "Active Mouse Button": "启动鼠标按鍵",
20
- "No mousebutton": "禁用鼠标按鍵",
21
- "Left mousebutton": "鼠标左鍵",
22
- "Middle mousebutton": "鼠标中鍵",
23
- "Right mousebutton": "鼠标右鍵",
13
+ "Move/Drag Viewport": "移动/拖动窗口",
14
+ "viewport drag": "窗口拖动",
15
+ "Active Mouse Button": "启动鼠标按键",
16
+ "No mousebutton": "禁用鼠标按键",
17
+ "Left mousebutton": "鼠标左键",
18
+ "Middle mousebutton": "鼠标中键",
19
+ "Right mousebutton": "鼠标右键",
20
"Keyboard": "键盘",
21
"Show Keyboard": "显示键盘",
22
"Extra keys": "额外按键",
23
"Show Extra Keys": "显示额外按键",
24
"Ctrl": "Ctrl",
25
"Toggle Ctrl": "切换 Ctrl",
26
+ "Edit clipboard content in the textarea below.": "在下面的文本区域中编辑剪贴板内容。",
27
"Alt": "Alt",
28
"Toggle Alt": "切换 Alt",
29
"Send Tab": "发送 Tab 键",
30
"Tab": "Tab",
31
"Esc": "Esc",
32
"Send Escape": "发送 Escape 键",
36
- "Ctrl+Alt+Del": "Ctrl-Alt-Del",
37
- "Send Ctrl-Alt-Del": "发送 Ctrl-Alt-Del 键",
38
- "Shutdown/Reboot": "关机/重新启动",
39
- "Shutdown/Reboot...": "关机/重新启动...",
33
+ "Ctrl+Alt+Del": "Ctrl+Alt+Del",
34
+ "Send Ctrl-Alt-Del": "发送 Ctrl+Alt+Del 键",
35
+ "Shutdown/Reboot": "关机/重启",
36
+ "Shutdown/Reboot...": "关机/重启...",
37
"Power": "电源",
38
"Shutdown": "关机",
42
- "Reboot": "重新启动",
39
+ "Reboot": "重启",
40
"Reset": "重置",
41
"Clipboard": "剪贴板",
42
"Clear": "清除",
43
"Fullscreen": "全屏",
44
"Settings": "设置",
45
+ "Encrypt": "加密",
46
"Shared Mode": "分享模式",
47
"View Only": "仅查看",
48
"Clip to Window": "限制/裁切窗口大小",
49
"Scaling Mode:": "缩放模式:",
50
"None": "无",
51
"Local Scaling": "本地缩放",
52
+ "Local Downscaling": "降低本地尺寸",
53
"Remote Resizing": "远程调整大小",
54
"Advanced": "高级",
55
+ "Local Cursor": "本地光标",
56
"Repeater ID:": "中继站 ID",
57
"WebSocket": "WebSocket",
58
- "Encrypt": "加密",
58
"Host:": "主机:",
59
"Port:": "端口:",
60
"Path:": "路径:",
61
"Automatic Reconnect": "自动重新连接",
62
"Reconnect Delay (ms):": "重新连接间隔 (ms):",
63
"Logging:": "日志级别:",
65
- "Disconnect": "中断连接",
64
+ "Disconnect": "断开连接",
65
"Connect": "连接",
66
"Password:": "密码:",
68
- "Cancel": "取消"
67
+ "Cancel": "取消",
68
+ "Canvas not supported.": "不支持 Canvas。"
69
}
\ No newline at end of file
public/novnc/app/localization.js
+38
-11
@@ -16,13 +16,19 @@ export class Localizer {
16
this.language = 'en';
17
18
// Current dictionary of translations
19
- this.dictionary = undefined;
19
+ this._dictionary = undefined;
20
}
21
22
// Configure suitable language based on user preferences
23
- setup(supportedLanguages) {
23
+ async setup(supportedLanguages, baseURL) {
24
this.language = 'en'; // Default: US English
25
+ this._dictionary = undefined;
26
27
+ this._setupLanguage(supportedLanguages);
28
+ await this._setupDictionary(baseURL);
29
+ }
30
+
31
+ _setupLanguage(supportedLanguages) {
32
/*
33
* Navigator.languages only available in Chrome (32+) and FireFox (32+)
34
* Fall back to navigator.language for other browsers
@@ -40,12 +46,6 @@ export class Localizer {
46
.replace("_", "-")
47
.split("-");
48
43
- // Built-in default?
44
- if ((userLang[0] === 'en') &&
45
- ((userLang[1] === undefined) || (userLang[1] === 'us'))) {
46
- return;
47
- }
48
-
49
// First pass: perfect match
50
for (let j = 0; j < supportedLanguages.length; j++) {
51
const supLang = supportedLanguages[j]
@@ -64,7 +64,12 @@ export class Localizer {
64
return;
65
}
66
67
- // Second pass: fallback
67
+ // Second pass: English fallback
68
+ if (userLang[0] === 'en') {
69
+ return;
70
+ }
71
+
72
+ // Third pass pass: other fallback
73
for (let j = 0;j < supportedLanguages.length;j++) {
74
const supLang = supportedLanguages[j]
75
.toLowerCase()
@@ -84,10 +89,32 @@ export class Localizer {
89
}
90
}
91
92
+ async _setupDictionary(baseURL) {
93
+ if (baseURL) {
94
+ if (!baseURL.endsWith("/")) {
95
+ baseURL = baseURL + "/";
96
+ }
97
+ } else {
98
+ baseURL = "";
99
+ }
100
+
101
+ if (this.language === "en") {
102
+ return;
103
+ }
104
+
105
+ let response = await fetch(baseURL + this.language + ".json");
106
+ if (!response.ok) {
107
+ throw Error("" + response.status + " " + response.statusText);
108
+ }
109
+
110
+ this._dictionary = await response.json();
111
+ }
112
+
113
// Retrieve localised text
114
get(id) {
89
- if (typeof this.dictionary !== 'undefined' && this.dictionary[id]) {
90
- return this.dictionary[id];
115
+ if (typeof this._dictionary !== 'undefined' &&
116
+ this._dictionary[id]) {
117
+ return this._dictionary[id];
118
} else {
119
return id;
120
}
public/novnc/app/styles/base.css
+2
-2
@@ -661,7 +661,7 @@ html {
661
justify-content: center;
662
align-content: center;
663
664
- line-height: 25px;
664
+ line-height: 1.6;
665
word-wrap: break-word;
666
color: #fff;
667
@@ -887,7 +887,7 @@ html {
887
.noVNC_logo {
888
color:yellow;
889
font-family: 'Orbitron', 'OrbitronTTF', sans-serif;
890
- line-height:90%;
890
+ line-height: 0.9;
891
text-shadow: 0.1em 0.1em 0 black;
892
}
893
.noVNC_logo span{
public/novnc/app/styles/input.css
+4
-4
@@ -86,6 +86,9 @@ option {
86
* Checkboxes
87
*/
88
input[type=checkbox] {
89
+ display: inline-flex;
90
+ justify-content: center;
91
+ align-items: center;
92
background-color: white;
93
background-image: unset;
94
border: 1px solid dimgrey;
@@ -104,14 +107,11 @@ input[type=checkbox]:checked {
107
input[type=checkbox]:checked::after {
108
content: "";
109
display: block; /* width & height doesn't work on inline elements */
107
- position: relative;
108
- top: 0;
109
- left: 3px;
110
width: 3px;
111
height: 7px;
112
border: 1px solid white;
113
border-width: 0 2px 2px 0;
114
- transform: rotate(40deg);
114
+ transform: rotate(40deg) translateY(-1px);
115
}
116
117
/*
public/novnc/app/ui.js
+16
-20
@@ -91,7 +91,7 @@ const UI = {
91
// insecure context
92
if (!window.isSecureContext) {
93
// FIXME: This gets hidden when connecting
94
- UI.showStatus(_("HTTPS is required for full functionality"), 'error');
94
+ UI.showStatus(_("Running without HTTPS is not recommended, crashes or other issues are likely."), 'error');
95
}
96
97
// Try to fetch version number
@@ -1058,11 +1058,18 @@ const UI = {
1058
1059
1060
1061
+ try {
1062
+ UI.rfb = new RFB(document.getElementById('noVNC_container'), urlargs.ws,
1063
+ { shared: UI.getSetting('shared'),
1064
+ repeaterID: UI.getSetting('repeaterID'),
1065
+ credentials: { password: password } });
1066
+ } catch (exc) {
1067
+ Log.Error("Failed to connect to server: " + exc);
1068
+ UI.updateVisualState('disconnected');
1069
+ UI.showStatus(_("Failed to connect to server: ") + exc, 'error');
1070
+ return;
1071
+ }
1072
1062
- UI.rfb = new RFB(document.getElementById('noVNC_container'), urlargs.ws,
1063
- { shared: UI.getSetting('shared'),
1064
- repeaterID: UI.getSetting('repeaterID'),
1065
- credentials: { password: password } });
1073
UI.rfb.addEventListener("connect", UI.connectFinished);
1074
UI.rfb.addEventListener("disconnect", UI.disconnectFinished);
1075
UI.rfb.addEventListener("serververification", UI.serverVerify);
@@ -1168,6 +1175,7 @@ const UI = {
1175
UI.showStatus(_("Disconnected"), 'normal');
1176
}
1177
1178
+ document.title = PAGE_TITLE;
1179
1180
UI.openControlbar();
1181
UI.openConnectPanel();
@@ -1780,20 +1788,8 @@ const UI = {
1788
1789
// Set up translations
1790
const LINGUAS = ["cs", "de", "el", "es", "fr", "it", "ja", "ko", "nl", "pl", "pt_BR", "ru", "sv", "tr", "zh_CN", "zh_TW"];
1783
-l10n.setup(LINGUAS);
1784
-if (l10n.language === "en" || l10n.dictionary !== undefined) {
1785
- UI.prime();
1786
-} else {
1787
- fetch('app/locale/' + l10n.language + '.json')
1788
- .then((response) => {
1789
- if (!response.ok) {
1790
- throw Error("" + response.status + " " + response.statusText);
1791
- }
1792
- return response.json();
1793
- })
1794
- .then((translations) => { l10n.dictionary = translations; })
1795
- .catch(err => Log.Error("Failed to load translations: " + err))
1796
- .then(UI.prime);
1797
-}
1791
+l10n.setup(LINGUAS, "app/locale/")
1792
+ .catch(err => Log.Error("Failed to load translations: " + err))
1793
+ .then(UI.prime);
1794
1795
export default UI;
public/novnc/app/webutil.js
+72
-8
@@ -6,16 +6,16 @@
6
* See README.md for usage and integration instructions.
7
*/
8
9
-import { initLogging as mainInitLogging } from '../core/util/logging.js';
9
+import * as Log from '../core/util/logging.js';
10
11
// init log level reading the logging HTTP param
12
export function initLogging(level) {
13
"use strict";
14
if (typeof level !== "undefined") {
15
- mainInitLogging(level);
15
+ Log.initLogging(level);
16
} else {
17
const param = document.location.href.match(/logging=([A-Za-z0-9._-]*)/);
18
- mainInitLogging(param || undefined);
18
+ Log.initLogging(param || undefined);
19
}
20
}
21
@@ -25,14 +25,14 @@ export function initLogging(level) {
25
//
26
// For privacy (Using a hastag #, the parameters will not be sent to the server)
27
// the url can be requested in the following way:
28
-// https://www.example.com#myqueryparam=myvalue&password=secreatvalue
28
+// https://www.example.com#myqueryparam=myvalue&password=secretvalue
29
//
30
// Even Mixing public and non public parameters will work:
31
-// https://www.example.com?nonsecretparam=example.com#password=secreatvalue
31
+// https://www.example.com?nonsecretparam=example.com#password=secretvalue
32
export function getQueryVar(name, defVal) {
33
"use strict";
34
const re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
35
- match = ''.concat(document.location.href, window.location.hash).match(re);
35
+ match = document.location.href.match(re);
36
if (typeof defVal === 'undefined') { defVal = null; }
37
38
if (match) {
@@ -146,7 +146,7 @@ export function writeSetting(name, value) {
146
if (window.chrome && window.chrome.storage) {
147
window.chrome.storage.sync.set(settings);
148
} else {
149
- localStorage.setItem(name, value);
149
+ localStorageSet(name, value);
150
}
151
}
152
@@ -156,7 +156,7 @@ export function readSetting(name, defaultValue) {
156
if ((name in settings) || (window.chrome && window.chrome.storage)) {
157
value = settings[name];
158
} else {
159
- value = localStorage.getItem(name);
159
+ value = localStorageGet(name);
160
settings[name] = value;
161
}
162
if (typeof value === "undefined") {
@@ -181,6 +181,70 @@ export function eraseSetting(name) {
181
if (window.chrome && window.chrome.storage) {
182
window.chrome.storage.sync.remove(name);
183
} else {
184
+ localStorageRemove(name);
185
+ }
186
+}
187
+
188
+let loggedMsgs = [];
189
+function logOnce(msg, level = "warn") {
190
+ if (!loggedMsgs.includes(msg)) {
191
+ switch (level) {
192
+ case "error":
193
+ Log.Error(msg);
194
+ break;
195
+ case "warn":
196
+ Log.Warn(msg);
197
+ break;
198
+ case "debug":
199
+ Log.Debug(msg);
200
+ break;
201
+ default:
202
+ Log.Info(msg);
203
+ }
204
+ loggedMsgs.push(msg);
205
+ }
206
+}
207
+
208
+let cookiesMsg = "Couldn't access noVNC settings, are cookies disabled?";
209
+
210
+function localStorageGet(name) {
211
+ let r;
212
+ try {
213
+ r = localStorage.getItem(name);
214
+ } catch (e) {
215
+ if (e instanceof DOMException) {
216
+ logOnce(cookiesMsg);
217
+ logOnce("'localStorage.getItem(" + name + ")' failed: " + e,
218
+ "debug");
219
+ } else {
220
+ throw e;
221
+ }
222
+ }
223
+ return r;
224
+}
225
+function localStorageSet(name, value) {
226
+ try {
227
+ localStorage.setItem(name, value);
228
+ } catch (e) {
229
+ if (e instanceof DOMException) {
230
+ logOnce(cookiesMsg);
231
+ logOnce("'localStorage.setItem(" + name + "," + value +
232
+ ")' failed: " + e, "debug");
233
+ } else {
234
+ throw e;
235
+ }
236
+ }
237
+}
238
+function localStorageRemove(name) {
239
+ try {
240
localStorage.removeItem(name);
241
+ } catch (e) {
242
+ if (e instanceof DOMException) {
243
+ logOnce(cookiesMsg);
244
+ logOnce("'localStorage.removeItem(" + name + ")' failed: " + e,
245
+ "debug");
246
+ } else {
247
+ throw e;
248
+ }
249
}
250
}
public/novnc/core/crypto/aes.js
new
+178
@@ -0,0 +1,178 @@
1
+export class AESECBCipher {
2
+ constructor() {
3
+ this._key = null;
4
+ }
5
+
6
+ get algorithm() {
7
+ return { name: "AES-ECB" };
8
+ }
9
+
10
+ static async importKey(key, _algorithm, extractable, keyUsages) {
11
+ const cipher = new AESECBCipher;
12
+ await cipher._importKey(key, extractable, keyUsages);
13
+ return cipher;
14
+ }
15
+
16
+ async _importKey(key, extractable, keyUsages) {
17
+ this._key = await window.crypto.subtle.importKey(
18
+ "raw", key, {name: "AES-CBC"}, extractable, keyUsages);
19
+ }
20
+
21
+ async encrypt(_algorithm, plaintext) {
22
+ const x = new Uint8Array(plaintext);
23
+ if (x.length % 16 !== 0 || this._key === null) {
24
+ return null;
25
+ }
26
+ const n = x.length / 16;
27
+ for (let i = 0; i < n; i++) {
28
+ const y = new Uint8Array(await window.crypto.subtle.encrypt({
29
+ name: "AES-CBC",
30
+ iv: new Uint8Array(16),
31
+ }, this._key, x.slice(i * 16, i * 16 + 16))).slice(0, 16);
32
+ x.set(y, i * 16);
33
+ }
34
+ return x;
35
+ }
36
+}
37
+
38
+export class AESEAXCipher {
39
+ constructor() {
40
+ this._rawKey = null;
41
+ this._ctrKey = null;
42
+ this._cbcKey = null;
43
+ this._zeroBlock = new Uint8Array(16);
44
+ this._prefixBlock0 = this._zeroBlock;
45
+ this._prefixBlock1 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
46
+ this._prefixBlock2 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
47
+ }
48
+
49
+ get algorithm() {
50
+ return { name: "AES-EAX" };
51
+ }
52
+
53
+ async _encryptBlock(block) {
54
+ const encrypted = await window.crypto.subtle.encrypt({
55
+ name: "AES-CBC",
56
+ iv: this._zeroBlock,
57
+ }, this._cbcKey, block);
58
+ return new Uint8Array(encrypted).slice(0, 16);
59
+ }
60
+
61
+ async _initCMAC() {
62
+ const k1 = await this._encryptBlock(this._zeroBlock);
63
+ const k2 = new Uint8Array(16);
64
+ const v = k1[0] >>> 6;
65
+ for (let i = 0; i < 15; i++) {
66
+ k2[i] = (k1[i + 1] >> 6) | (k1[i] << 2);
67
+ k1[i] = (k1[i + 1] >> 7) | (k1[i] << 1);
68
+ }
69
+ const lut = [0x0, 0x87, 0x0e, 0x89];
70
+ k2[14] ^= v >>> 1;
71
+ k2[15] = (k1[15] << 2) ^ lut[v];
72
+ k1[15] = (k1[15] << 1) ^ lut[v >> 1];
73
+ this._k1 = k1;
74
+ this._k2 = k2;
75
+ }
76
+
77
+ async _encryptCTR(data, counter) {
78
+ const encrypted = await window.crypto.subtle.encrypt({
79
+ name: "AES-CTR",
80
+ counter: counter,
81
+ length: 128
82
+ }, this._ctrKey, data);
83
+ return new Uint8Array(encrypted);
84
+ }
85
+
86
+ async _decryptCTR(data, counter) {
87
+ const decrypted = await window.crypto.subtle.decrypt({
88
+ name: "AES-CTR",
89
+ counter: counter,
90
+ length: 128
91
+ }, this._ctrKey, data);
92
+ return new Uint8Array(decrypted);
93
+ }
94
+
95
+ async _computeCMAC(data, prefixBlock) {
96
+ if (prefixBlock.length !== 16) {
97
+ return null;
98
+ }
99
+ const n = Math.floor(data.length / 16);
100
+ const m = Math.ceil(data.length / 16);
101
+ const r = data.length - n * 16;
102
+ const cbcData = new Uint8Array((m + 1) * 16);
103
+ cbcData.set(prefixBlock);
104
+ cbcData.set(data, 16);
105
+ if (r === 0) {
106
+ for (let i = 0; i < 16; i++) {
107
+ cbcData[n * 16 + i] ^= this._k1[i];
108
+ }
109
+ } else {
110
+ cbcData[(n + 1) * 16 + r] = 0x80;
111
+ for (let i = 0; i < 16; i++) {
112
+ cbcData[(n + 1) * 16 + i] ^= this._k2[i];
113
+ }
114
+ }
115
+ let cbcEncrypted = await window.crypto.subtle.encrypt({
116
+ name: "AES-CBC",
117
+ iv: this._zeroBlock,
118
+ }, this._cbcKey, cbcData);
119
+
120
+ cbcEncrypted = new Uint8Array(cbcEncrypted);
121
+ const mac = cbcEncrypted.slice(cbcEncrypted.length - 32, cbcEncrypted.length - 16);
122
+ return mac;
123
+ }
124
+
125
+ static async importKey(key, _algorithm, _extractable, _keyUsages) {
126
+ const cipher = new AESEAXCipher;
127
+ await cipher._importKey(key);
128
+ return cipher;
129
+ }
130
+
131
+ async _importKey(key) {
132
+ this._rawKey = key;
133
+ this._ctrKey = await window.crypto.subtle.importKey(
134
+ "raw", key, {name: "AES-CTR"}, false, ["encrypt", "decrypt"]);
135
+ this._cbcKey = await window.crypto.subtle.importKey(
136
+ "raw", key, {name: "AES-CBC"}, false, ["encrypt"]);
137
+ await this._initCMAC();
138
+ }
139
+
140
+ async encrypt(algorithm, message) {
141
+ const ad = algorithm.additionalData;
142
+ const nonce = algorithm.iv;
143
+ const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
144
+ const encrypted = await this._encryptCTR(message, nCMAC);
145
+ const adCMAC = await this._computeCMAC(ad, this._prefixBlock1);
146
+ const mac = await this._computeCMAC(encrypted, this._prefixBlock2);
147
+ for (let i = 0; i < 16; i++) {
148
+ mac[i] ^= nCMAC[i] ^ adCMAC[i];
149
+ }
150
+ const res = new Uint8Array(16 + encrypted.length);
151
+ res.set(encrypted);
152
+ res.set(mac, encrypted.length);
153
+ return res;
154
+ }
155
+
156
+ async decrypt(algorithm, data) {
157
+ const encrypted = data.slice(0, data.length - 16);
158
+ const ad = algorithm.additionalData;
159
+ const nonce = algorithm.iv;
160
+ const mac = data.slice(data.length - 16);
161
+ const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
162
+ const adCMAC = await this._computeCMAC(ad, this._prefixBlock1);
163
+ const computedMac = await this._computeCMAC(encrypted, this._prefixBlock2);
164
+ for (let i = 0; i < 16; i++) {
165
+ computedMac[i] ^= nCMAC[i] ^ adCMAC[i];
166
+ }
167
+ if (computedMac.length !== mac.length) {
168
+ return null;
169
+ }
170
+ for (let i = 0; i < mac.length; i++) {
171
+ if (computedMac[i] !== mac[i]) {
172
+ return null;
173
+ }
174
+ }
175
+ const res = await this._decryptCTR(encrypted, nCMAC);
176
+ return res;
177
+ }
178
+}
public/novnc/core/crypto/bigint.js
new
+34
@@ -0,0 +1,34 @@
1
+export function modPow(b, e, m) {
2
+ let r = 1n;
3
+ b = b % m;
4
+ while (e > 0n) {
5
+ if ((e & 1n) === 1n) {
6
+ r = (r * b) % m;
7
+ }
8
+ e = e >> 1n;
9
+ b = (b * b) % m;
10
+ }
11
+ return r;
12
+}
13
+
14
+export function bigIntToU8Array(bigint, padLength=0) {
15
+ let hex = bigint.toString(16);
16
+ if (padLength === 0) {
17
+ padLength = Math.ceil(hex.length / 2);
18
+ }
19
+ hex = hex.padStart(padLength * 2, '0');
20
+ const length = hex.length / 2;
21
+ const arr = new Uint8Array(length);
22
+ for (let i = 0; i < length; i++) {
23
+ arr[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
24
+ }
25
+ return arr;
26
+}
27
+
28
+export function u8ArrayToBigInt(arr) {
29
+ let hex = '0x';
30
+ for (let i = 0; i < arr.length; i++) {
31
+ hex += arr[i].toString(16).padStart(2, '0');
32
+ }
33
+ return BigInt(hex);
34
+}
public/novnc/core/crypto/crypto.js
new
+90
@@ -0,0 +1,90 @@
1
+import { AESECBCipher, AESEAXCipher } from "./aes.js";
2
+import { DESCBCCipher, DESECBCipher } from "./des.js";
3
+import { RSACipher } from "./rsa.js";
4
+import { DHCipher } from "./dh.js";
5
+import { MD5 } from "./md5.js";
6
+
7
+// A single interface for the cryptographic algorithms not supported by SubtleCrypto.
8
+// Both synchronous and asynchronous implmentations are allowed.
9
+class LegacyCrypto {
10
+ constructor() {
11
+ this._algorithms = {
12
+ "AES-ECB": AESECBCipher,
13
+ "AES-EAX": AESEAXCipher,
14
+ "DES-ECB": DESECBCipher,
15
+ "DES-CBC": DESCBCCipher,
16
+ "RSA-PKCS1-v1_5": RSACipher,
17
+ "DH": DHCipher,
18
+ "MD5": MD5,
19
+ };
20
+ }
21
+
22
+ encrypt(algorithm, key, data) {
23
+ if (key.algorithm.name !== algorithm.name) {
24
+ throw new Error("algorithm does not match");
25
+ }
26
+ if (typeof key.encrypt !== "function") {
27
+ throw new Error("key does not support encryption");
28
+ }
29
+ return key.encrypt(algorithm, data);
30
+ }
31
+
32
+ decrypt(algorithm, key, data) {
33
+ if (key.algorithm.name !== algorithm.name) {
34
+ throw new Error("algorithm does not match");
35
+ }
36
+ if (typeof key.decrypt !== "function") {
37
+ throw new Error("key does not support encryption");
38
+ }
39
+ return key.decrypt(algorithm, data);
40
+ }
41
+
42
+ importKey(format, keyData, algorithm, extractable, keyUsages) {
43
+ if (format !== "raw") {
44
+ throw new Error("key format is not supported");
45
+ }
46
+ const alg = this._algorithms[algorithm.name];
47
+ if (typeof alg === "undefined" || typeof alg.importKey !== "function") {
48
+ throw new Error("algorithm is not supported");
49
+ }
50
+ return alg.importKey(keyData, algorithm, extractable, keyUsages);
51
+ }
52
+
53
+ generateKey(algorithm, extractable, keyUsages) {
54
+ const alg = this._algorithms[algorithm.name];
55
+ if (typeof alg === "undefined" || typeof alg.generateKey !== "function") {
56
+ throw new Error("algorithm is not supported");
57
+ }
58
+ return alg.generateKey(algorithm, extractable, keyUsages);
59
+ }
60
+
61
+ exportKey(format, key) {
62
+ if (format !== "raw") {
63
+ throw new Error("key format is not supported");
64
+ }
65
+ if (typeof key.exportKey !== "function") {
66
+ throw new Error("key does not support exportKey");
67
+ }
68
+ return key.exportKey();
69
+ }
70
+
71
+ digest(algorithm, data) {
72
+ const alg = this._algorithms[algorithm];
73
+ if (typeof alg !== "function") {
74
+ throw new Error("algorithm is not supported");
75
+ }
76
+ return alg(data);
77
+ }
78
+
79
+ deriveBits(algorithm, key, length) {
80
+ if (key.algorithm.name !== algorithm.name) {
81
+ throw new Error("algorithm does not match");
82
+ }
83
+ if (typeof key.deriveBits !== "function") {
84
+ throw new Error("key does not support deriveBits");
85
+ }
86
+ return key.deriveBits(algorithm, length);
87
+ }
88
+}
89
+
90
+export default new LegacyCrypto;
public/novnc/core/crypto/des.js
new
+330
@@ -0,0 +1,330 @@
1
+/*
2
+ * Ported from Flashlight VNC ActionScript implementation:
3
+ * http://www.wizhelp.com/flashlight-vnc/
4
+ *
5
+ * Full attribution follows:
6
+ *
7
+ * -------------------------------------------------------------------------
8
+ *
9
+ * This DES class has been extracted from package Acme.Crypto for use in VNC.
10
+ * The unnecessary odd parity code has been removed.
11
+ *
12
+ * These changes are:
13
+ * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
14
+ *
15
+ * This software is distributed in the hope that it will be useful,
16
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18
+ *
19
+
20
+ * DesCipher - the DES encryption method
21
+ *
22
+ * The meat of this code is by Dave Zimmerman <dzimm@widget.com>, and is:
23
+ *
24
+ * Copyright (c) 1996 Widget Workshop, Inc. All Rights Reserved.
25
+ *
26
+ * Permission to use, copy, modify, and distribute this software
27
+ * and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and
28
+ * without fee is hereby granted, provided that this copyright notice is kept
29
+ * intact.
30
+ *
31
+ * WIDGET WORKSHOP MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY
32
+ * OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
33
+ * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
34
+ * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. WIDGET WORKSHOP SHALL NOT BE LIABLE
35
+ * FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
36
+ * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
37
+ *
38
+ * THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE
39
+ * CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE
40
+ * PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT
41
+ * NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE
42
+ * SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
43
+ * SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE
44
+ * PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES"). WIDGET WORKSHOP
45
+ * SPECIFICALLY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR
46
+ * HIGH RISK ACTIVITIES.
47
+ *
48
+ *
49
+ * The rest is:
50
+ *
51
+ * Copyright (C) 1996 by Jef Poskanzer <jef@acme.com>. All rights reserved.
52
+ *
53
+ * Redistribution and use in source and binary forms, with or without
54
+ * modification, are permitted provided that the following conditions
55
+ * are met:
56
+ * 1. Redistributions of source code must retain the above copyright
57
+ * notice, this list of conditions and the following disclaimer.
58
+ * 2. Redistributions in binary form must reproduce the above copyright
59
+ * notice, this list of conditions and the following disclaimer in the
60
+ * documentation and/or other materials provided with the distribution.
61
+ *
62
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
63
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
64
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
65
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
66
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
67
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
68
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
69
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
70
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
71
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
72
+ * SUCH DAMAGE.
73
+ *
74
+ * Visit the ACME Labs Java page for up-to-date versions of this and other
75
+ * fine Java utilities: http://www.acme.com/java/
76
+ */
77
+
78
+/* eslint-disable comma-spacing */
79
+
80
+// Tables, permutations, S-boxes, etc.
81
+const PC2 = [13,16,10,23, 0, 4, 2,27,14, 5,20, 9,22,18,11, 3,
82
+ 25, 7,15, 6,26,19,12, 1,40,51,30,36,46,54,29,39,
83
+ 50,44,32,47,43,48,38,55,33,52,45,41,49,35,28,31 ],
84
+ totrot = [ 1, 2, 4, 6, 8,10,12,14,15,17,19,21,23,25,27,28];
85
+
86
+const z = 0x0;
87
+let a,b,c,d,e,f;
88
+a=1<<16; b=1<<24; c=a|b; d=1<<2; e=1<<10; f=d|e;
89
+const SP1 = [c|e,z|z,a|z,c|f,c|d,a|f,z|d,a|z,z|e,c|e,c|f,z|e,b|f,c|d,b|z,z|d,
90
+ z|f,b|e,b|e,a|e,a|e,c|z,c|z,b|f,a|d,b|d,b|d,a|d,z|z,z|f,a|f,b|z,
91
+ a|z,c|f,z|d,c|z,c|e,b|z,b|z,z|e,c|d,a|z,a|e,b|d,z|e,z|d,b|f,a|f,
92
+ c|f,a|d,c|z,b|f,b|d,z|f,a|f,c|e,z|f,b|e,b|e,z|z,a|d,a|e,z|z,c|d];
93
+a=1<<20; b=1<<31; c=a|b; d=1<<5; e=1<<15; f=d|e;
94
+const SP2 = [c|f,b|e,z|e,a|f,a|z,z|d,c|d,b|f,b|d,c|f,c|e,b|z,b|e,a|z,z|d,c|d,
95
+ a|e,a|d,b|f,z|z,b|z,z|e,a|f,c|z,a|d,b|d,z|z,a|e,z|f,c|e,c|z,z|f,
96
+ z|z,a|f,c|d,a|z,b|f,c|z,c|e,z|e,c|z,b|e,z|d,c|f,a|f,z|d,z|e,b|z,
97
+ z|f,c|e,a|z,b|d,a|d,b|f,b|d,a|d,a|e,z|z,b|e,z|f,b|z,c|d,c|f,a|e];
98
+a=1<<17; b=1<<27; c=a|b; d=1<<3; e=1<<9; f=d|e;
99
+const SP3 = [z|f,c|e,z|z,c|d,b|e,z|z,a|f,b|e,a|d,b|d,b|d,a|z,c|f,a|d,c|z,z|f,
100
+ b|z,z|d,c|e,z|e,a|e,c|z,c|d,a|f,b|f,a|e,a|z,b|f,z|d,c|f,z|e,b|z,
101
+ c|e,b|z,a|d,z|f,a|z,c|e,b|e,z|z,z|e,a|d,c|f,b|e,b|d,z|e,z|z,c|d,
102
+ b|f,a|z,b|z,c|f,z|d,a|f,a|e,b|d,c|z,b|f,z|f,c|z,a|f,z|d,c|d,a|e];
103
+a=1<<13; b=1<<23; c=a|b; d=1<<0; e=1<<7; f=d|e;
104
+const SP4 = [c|d,a|f,a|f,z|e,c|e,b|f,b|d,a|d,z|z,c|z,c|z,c|f,z|f,z|z,b|e,b|d,
105
+ z|d,a|z,b|z,c|d,z|e,b|z,a|d,a|e,b|f,z|d,a|e,b|e,a|z,c|e,c|f,z|f,
106
+ b|e,b|d,c|z,c|f,z|f,z|z,z|z,c|z,a|e,b|e,b|f,z|d,c|d,a|f,a|f,z|e,
107
+ c|f,z|f,z|d,a|z,b|d,a|d,c|e,b|f,a|d,a|e,b|z,c|d,z|e,b|z,a|z,c|e];
108
+a=1<<25; b=1<<30; c=a|b; d=1<<8; e=1<<19; f=d|e;
109
+const SP5 = [z|d,a|f,a|e,c|d,z|e,z|d,b|z,a|e,b|f,z|e,a|d,b|f,c|d,c|e,z|f,b|z,
110
+ a|z,b|e,b|e,z|z,b|d,c|f,c|f,a|d,c|e,b|d,z|z,c|z,a|f,a|z,c|z,z|f,
111
+ z|e,c|d,z|d,a|z,b|z,a|e,c|d,b|f,a|d,b|z,c|e,a|f,b|f,z|d,a|z,c|e,
112
+ c|f,z|f,c|z,c|f,a|e,z|z,b|e,c|z,z|f,a|d,b|d,z|e,z|z,b|e,a|f,b|d];
113
+a=1<<22; b=1<<29; c=a|b; d=1<<4; e=1<<14; f=d|e;
114
+const SP6 = [b|d,c|z,z|e,c|f,c|z,z|d,c|f,a|z,b|e,a|f,a|z,b|d,a|d,b|e,b|z,z|f,
115
+ z|z,a|d,b|f,z|e,a|e,b|f,z|d,c|d,c|d,z|z,a|f,c|e,z|f,a|e,c|e,b|z,
116
+ b|e,z|d,c|d,a|e,c|f,a|z,z|f,b|d,a|z,b|e,b|z,z|f,b|d,c|f,a|e,c|z,
117
+ a|f,c|e,z|z,c|d,z|d,z|e,c|z,a|f,z|e,a|d,b|f,z|z,c|e,b|z,a|d,b|f];
118
+a=1<<21; b=1<<26; c=a|b; d=1<<1; e=1<<11; f=d|e;
119
+const SP7 = [a|z,c|d,b|f,z|z,z|e,b|f,a|f,c|e,c|f,a|z,z|z,b|d,z|d,b|z,c|d,z|f,
120
+ b|e,a|f,a|d,b|e,b|d,c|z,c|e,a|d,c|z,z|e,z|f,c|f,a|e,z|d,b|z,a|e,
121
+ b|z,a|e,a|z,b|f,b|f,c|d,c|d,z|d,a|d,b|z,b|e,a|z,c|e,z|f,a|f,c|e,
122
+ z|f,b|d,c|f,c|z,a|e,z|z,z|d,c|f,z|z,a|f,c|z,z|e,b|d,b|e,z|e,a|d];
123
+a=1<<18; b=1<<28; c=a|b; d=1<<6; e=1<<12; f=d|e;
124
+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,
125
+ c|z,b|d,b|e,z|f,a|e,a|d,c|d,c|e,z|f,z|z,z|z,c|d,b|d,b|e,a|f,a|z,
126
+ a|f,a|z,c|e,z|e,z|d,c|d,z|e,a|f,b|e,z|d,b|d,c|z,c|d,b|z,a|z,b|f,
127
+ z|z,c|f,a|d,b|d,c|z,b|e,b|f,z|z,c|f,a|e,a|e,z|f,z|f,a|d,b|z,c|e];
128
+
129
+/* eslint-enable comma-spacing */
130
+
131
+class DES {
132
+ constructor(password) {
133
+ this.keys = [];
134
+
135
+ // Set the key.
136
+ const pc1m = [], pcr = [], kn = [];
137
+
138
+ for (let j = 0, l = 56; j < 56; ++j, l -= 8) {
139
+ l += l < -5 ? 65 : l < -3 ? 31 : l < -1 ? 63 : l === 27 ? 35 : 0; // PC1
140
+ const m = l & 0x7;
141
+ pc1m[j] = ((password[l >>> 3] & (1<<m)) !== 0) ? 1: 0;
142
+ }
143
+
144
+ for (let i = 0; i < 16; ++i) {
145
+ const m = i << 1;
146
+ const n = m + 1;
147
+ kn[m] = kn[n] = 0;
148
+ for (let o = 28; o < 59; o += 28) {
149
+ for (let j = o - 28; j < o; ++j) {
150
+ const l = j + totrot[i];
151
+ pcr[j] = l < o ? pc1m[l] : pc1m[l - 28];
152
+ }
153
+ }
154
+ for (let j = 0; j < 24; ++j) {
155
+ if (pcr[PC2[j]] !== 0) {
156
+ kn[m] |= 1 << (23 - j);
157
+ }
158
+ if (pcr[PC2[j + 24]] !== 0) {
159
+ kn[n] |= 1 << (23 - j);
160
+ }
161
+ }
162
+ }
163
+
164
+ // cookey
165
+ for (let i = 0, rawi = 0, KnLi = 0; i < 16; ++i) {
166
+ const raw0 = kn[rawi++];
167
+ const raw1 = kn[rawi++];
168
+ this.keys[KnLi] = (raw0 & 0x00fc0000) << 6;
169
+ this.keys[KnLi] |= (raw0 & 0x00000fc0) << 10;
170
+ this.keys[KnLi] |= (raw1 & 0x00fc0000) >>> 10;
171
+ this.keys[KnLi] |= (raw1 & 0x00000fc0) >>> 6;
172
+ ++KnLi;
173
+ this.keys[KnLi] = (raw0 & 0x0003f000) << 12;
174
+ this.keys[KnLi] |= (raw0 & 0x0000003f) << 16;
175
+ this.keys[KnLi] |= (raw1 & 0x0003f000) >>> 4;
176
+ this.keys[KnLi] |= (raw1 & 0x0000003f);
177
+ ++KnLi;
178
+ }
179
+ }
180
+
181
+ // Encrypt 8 bytes of text
182
+ enc8(text) {
183
+ const b = text.slice();
184
+ let i = 0, l, r, x; // left, right, accumulator
185
+
186
+ // Squash 8 bytes to 2 ints
187
+ l = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++];
188
+ r = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++];
189
+
190
+ x = ((l >>> 4) ^ r) & 0x0f0f0f0f;
191
+ r ^= x;
192
+ l ^= (x << 4);
193
+ x = ((l >>> 16) ^ r) & 0x0000ffff;
194
+ r ^= x;
195
+ l ^= (x << 16);
196
+ x = ((r >>> 2) ^ l) & 0x33333333;
197
+ l ^= x;
198
+ r ^= (x << 2);
199
+ x = ((r >>> 8) ^ l) & 0x00ff00ff;
200
+ l ^= x;
201
+ r ^= (x << 8);
202
+ r = (r << 1) | ((r >>> 31) & 1);
203
+ x = (l ^ r) & 0xaaaaaaaa;
204
+ l ^= x;
205
+ r ^= x;
206
+ l = (l << 1) | ((l >>> 31) & 1);
207
+
208
+ for (let i = 0, keysi = 0; i < 8; ++i) {
209
+ x = (r << 28) | (r >>> 4);
210
+ x ^= this.keys[keysi++];
211
+ let fval = SP7[x & 0x3f];
212
+ fval |= SP5[(x >>> 8) & 0x3f];
213
+ fval |= SP3[(x >>> 16) & 0x3f];
214
+ fval |= SP1[(x >>> 24) & 0x3f];
215
+ x = r ^ this.keys[keysi++];
216
+ fval |= SP8[x & 0x3f];
217
+ fval |= SP6[(x >>> 8) & 0x3f];
218
+ fval |= SP4[(x >>> 16) & 0x3f];
219
+ fval |= SP2[(x >>> 24) & 0x3f];
220
+ l ^= fval;
221
+ x = (l << 28) | (l >>> 4);
222
+ x ^= this.keys[keysi++];
223
+ fval = SP7[x & 0x3f];
224
+ fval |= SP5[(x >>> 8) & 0x3f];
225
+ fval |= SP3[(x >>> 16) & 0x3f];
226
+ fval |= SP1[(x >>> 24) & 0x3f];
227
+ x = l ^ this.keys[keysi++];
228
+ fval |= SP8[x & 0x0000003f];
229
+ fval |= SP6[(x >>> 8) & 0x3f];
230
+ fval |= SP4[(x >>> 16) & 0x3f];
231
+ fval |= SP2[(x >>> 24) & 0x3f];
232
+ r ^= fval;
233
+ }
234
+
235
+ r = (r << 31) | (r >>> 1);
236
+ x = (l ^ r) & 0xaaaaaaaa;
237
+ l ^= x;
238
+ r ^= x;
239
+ l = (l << 31) | (l >>> 1);
240
+ x = ((l >>> 8) ^ r) & 0x00ff00ff;
241
+ r ^= x;
242
+ l ^= (x << 8);
243
+ x = ((l >>> 2) ^ r) & 0x33333333;
244
+ r ^= x;
245
+ l ^= (x << 2);
246
+ x = ((r >>> 16) ^ l) & 0x0000ffff;
247
+ l ^= x;
248
+ r ^= (x << 16);
249
+ x = ((r >>> 4) ^ l) & 0x0f0f0f0f;
250
+ l ^= x;
251
+ r ^= (x << 4);
252
+
253
+ // Spread ints to bytes
254
+ x = [r, l];
255
+ for (i = 0; i < 8; i++) {
256
+ b[i] = (x[i>>>2] >>> (8 * (3 - (i % 4)))) % 256;
257
+ if (b[i] < 0) { b[i] += 256; } // unsigned
258
+ }
259
+ return b;
260
+ }
261
+}
262
+
263
+export class DESECBCipher {
264
+ constructor() {
265
+ this._cipher = null;
266
+ }
267
+
268
+ get algorithm() {
269
+ return { name: "DES-ECB" };
270
+ }
271
+
272
+ static importKey(key, _algorithm, _extractable, _keyUsages) {
273
+ const cipher = new DESECBCipher;
274
+ cipher._importKey(key);
275
+ return cipher;
276
+ }
277
+
278
+ _importKey(key, _extractable, _keyUsages) {
279
+ this._cipher = new DES(key);
280
+ }
281
+
282
+ encrypt(_algorithm, plaintext) {
283
+ const x = new Uint8Array(plaintext);
284
+ if (x.length % 8 !== 0 || this._cipher === null) {
285
+ return null;
286
+ }
287
+ const n = x.length / 8;
288
+ for (let i = 0; i < n; i++) {
289
+ x.set(this._cipher.enc8(x.slice(i * 8, i * 8 + 8)), i * 8);
290
+ }
291
+ return x;
292
+ }
293
+}
294
+
295
+export class DESCBCCipher {
296
+ constructor() {
297
+ this._cipher = null;
298
+ }
299
+
300
+ get algorithm() {
301
+ return { name: "DES-CBC" };
302
+ }
303
+
304
+ static importKey(key, _algorithm, _extractable, _keyUsages) {
305
+ const cipher = new DESCBCCipher;
306
+ cipher._importKey(key);
307
+ return cipher;
308
+ }
309
+
310
+ _importKey(key) {
311
+ this._cipher = new DES(key);
312
+ }
313
+
314
+ encrypt(algorithm, plaintext) {
315
+ const x = new Uint8Array(plaintext);
316
+ let y = new Uint8Array(algorithm.iv);
317
+ if (x.length % 8 !== 0 || this._cipher === null) {
318
+ return null;
319
+ }
320
+ const n = x.length / 8;
321
+ for (let i = 0; i < n; i++) {
322
+ for (let j = 0; j < 8; j++) {
323
+ y[j] ^= plaintext[i * 8 + j];
324
+ }
325
+ y = this._cipher.enc8(y);
326
+ x.set(y, i * 8);
327
+ }
328
+ return x;
329
+ }
330
+}
public/novnc/core/crypto/dh.js
new
+55
@@ -0,0 +1,55 @@
1
+import { modPow, bigIntToU8Array, u8ArrayToBigInt } from "./bigint.js";
2
+
3
+class DHPublicKey {
4
+ constructor(key) {
5
+ this._key = key;
6
+ }
7
+
8
+ get algorithm() {
9
+ return { name: "DH" };
10
+ }
11
+
12
+ exportKey() {
13
+ return this._key;
14
+ }
15
+}
16
+
17
+export class DHCipher {
18
+ constructor() {
19
+ this._g = null;
20
+ this._p = null;
21
+ this._gBigInt = null;
22
+ this._pBigInt = null;
23
+ this._privateKey = null;
24
+ }
25
+
26
+ get algorithm() {
27
+ return { name: "DH" };
28
+ }
29
+
30
+ static generateKey(algorithm, _extractable) {
31
+ const cipher = new DHCipher;
32
+ cipher._generateKey(algorithm);
33
+ return { privateKey: cipher, publicKey: new DHPublicKey(cipher._publicKey) };
34
+ }
35
+
36
+ _generateKey(algorithm) {
37
+ const g = algorithm.g;
38
+ const p = algorithm.p;
39
+ this._keyBytes = p.length;
40
+ this._gBigInt = u8ArrayToBigInt(g);
41
+ this._pBigInt = u8ArrayToBigInt(p);
42
+ this._privateKey = window.crypto.getRandomValues(new Uint8Array(this._keyBytes));
43
+ this._privateKeyBigInt = u8ArrayToBigInt(this._privateKey);
44
+ this._publicKey = bigIntToU8Array(modPow(
45
+ this._gBigInt, this._privateKeyBigInt, this._pBigInt), this._keyBytes);
46
+ }
47
+
48
+ deriveBits(algorithm, length) {
49
+ const bytes = Math.ceil(length / 8);
50
+ const pkey = new Uint8Array(algorithm.public);
51
+ const len = bytes > this._keyBytes ? bytes : this._keyBytes;
52
+ const secret = modPow(u8ArrayToBigInt(pkey), this._privateKeyBigInt, this._pBigInt);
53
+ return bigIntToU8Array(secret, len).slice(0, len);
54
+ }
55
+}
public/novnc/core/crypto/md5.js
new
+82
@@ -0,0 +1,82 @@
1
+/*
2
+ * noVNC: HTML5 VNC client
3
+ * Copyright (C) 2021 The noVNC Authors
4
+ * Licensed under MPL 2.0 (see LICENSE.txt)
5
+ *
6
+ * See README.md for usage and integration instructions.
7
+ */
8
+
9
+/*
10
+ * Performs MD5 hashing on an array of bytes, returns an array of bytes
11
+ */
12
+
13
+export async function MD5(d) {
14
+ let s = "";
15
+ for (let i = 0; i < d.length; i++) {
16
+ s += String.fromCharCode(d[i]);
17
+ }
18
+ return M(V(Y(X(s), 8 * s.length)));
19
+}
20
+
21
+function M(d) {
22
+ let f = new Uint8Array(d.length);
23
+ for (let i=0;i<d.length;i++) {
24
+ f[i] = d.charCodeAt(i);
25
+ }
26
+ return f;
27
+}
28
+
29
+function X(d) {
30
+ let r = Array(d.length >> 2);
31
+ for (let m = 0; m < r.length; m++) r[m] = 0;
32
+ for (let m = 0; m < 8 * d.length; m += 8) r[m >> 5] |= (255 & d.charCodeAt(m / 8)) << m % 32;
33
+ return r;
34
+}
35
+
36
+function V(d) {
37
+ let r = "";
38
+ for (let m = 0; m < 32 * d.length; m += 8) r += String.fromCharCode(d[m >> 5] >>> m % 32 & 255);
39
+ return r;
40
+}
41
+
42
+function Y(d, g) {
43
+ d[g >> 5] |= 128 << g % 32, d[14 + (g + 64 >>> 9 << 4)] = g;
44
+ let m = 1732584193, f = -271733879, r = -1732584194, i = 271733878;
45
+ for (let n = 0; n < d.length; n += 16) {
46
+ let h = m,
47
+ t = f,
48
+ g = r,
49
+ e = i;
50
+ f = ii(f = ii(f = ii(f = ii(f = hh(f = hh(f = hh(f = hh(f = gg(f = gg(f = gg(f = gg(f = ff(f = ff(f = ff(f = ff(f, r = ff(r, i = ff(i, m = ff(m, f, r, i, d[n + 0], 7, -680876936), f, r, d[n + 1], 12, -389564586), m, f, d[n + 2], 17, 606105819), i, m, d[n + 3], 22, -1044525330), r = ff(r, i = ff(i, m = ff(m, f, r, i, d[n + 4], 7, -176418897), f, r, d[n + 5], 12, 1200080426), m, f, d[n + 6], 17, -1473231341), i, m, d[n + 7], 22, -45705983), r = ff(r, i = ff(i, m = ff(m, f, r, i, d[n + 8], 7, 1770035416), f, r, d[n + 9], 12, -1958414417), m, f, d[n + 10], 17, -42063), i, m, d[n + 11], 22, -1990404162), r = ff(r, i = ff(i, m = ff(m, f, r, i, d[n + 12], 7, 1804603682), f, r, d[n + 13], 12, -40341101), m, f, d[n + 14], 17, -1502002290), i, m, d[n + 15], 22, 1236535329), r = gg(r, i = gg(i, m = gg(m, f, r, i, d[n + 1], 5, -165796510), f, r, d[n + 6], 9, -1069501632), m, f, d[n + 11], 14, 643717713), i, m, d[n + 0], 20, -373897302), r = gg(r, i = gg(i, m = gg(m, f, r, i, d[n + 5], 5, -701558691), f, r, d[n + 10], 9, 38016083), m, f, d[n + 15], 14, -660478335), i, m, d[n + 4], 20, -405537848), r = gg(r, i = gg(i, m = gg(m, f, r, i, d[n + 9], 5, 568446438), f, r, d[n + 14], 9, -1019803690), m, f, d[n + 3], 14, -187363961), i, m, d[n + 8], 20, 1163531501), r = gg(r, i = gg(i, m = gg(m, f, r, i, d[n + 13], 5, -1444681467), f, r, d[n + 2], 9, -51403784), m, f, d[n + 7], 14, 1735328473), i, m, d[n + 12], 20, -1926607734), r = hh(r, i = hh(i, m = hh(m, f, r, i, d[n + 5], 4, -378558), f, r, d[n + 8], 11, -2022574463), m, f, d[n + 11], 16, 1839030562), i, m, d[n + 14], 23, -35309556), r = hh(r, i = hh(i, m = hh(m, f, r, i, d[n + 1], 4, -1530992060), f, r, d[n + 4], 11, 1272893353), m, f, d[n + 7], 16, -155497632), i, m, d[n + 10], 23, -1094730640), r = hh(r, i = hh(i, m = hh(m, f, r, i, d[n + 13], 4, 681279174), f, r, d[n + 0], 11, -358537222), m, f, d[n + 3], 16, -722521979), i, m, d[n + 6], 23, 76029189), r = hh(r, i = hh(i, m = hh(m, f, r, i, d[n + 9], 4, -640364487), f, r, d[n + 12], 11, -421815835), m, f, d[n + 15], 16, 530742520), i, m, d[n + 2], 23, -995338651), r = ii(r, i = ii(i, m = ii(m, f, r, i, d[n + 0], 6, -198630844), f, r, d[n + 7], 10, 1126891415), m, f, d[n + 14], 15, -1416354905), i, m, d[n + 5], 21, -57434055), r = ii(r, i = ii(i, m = ii(m, f, r, i, d[n + 12], 6, 1700485571), f, r, d[n + 3], 10, -1894986606), m, f, d[n + 10], 15, -1051523), i, m, d[n + 1], 21, -2054922799), r = ii(r, i = ii(i, m = ii(m, f, r, i, d[n + 8], 6, 1873313359), f, r, d[n + 15], 10, -30611744), m, f, d[n + 6], 15, -1560198380), i, m, d[n + 13], 21, 1309151649), r = ii(r, i = ii(i, m = ii(m, f, r, i, d[n + 4], 6, -145523070), f, r, d[n + 11], 10, -1120210379), m, f, d[n + 2], 15, 718787259), i, m, d[n + 9], 21, -343485551), m = add(m, h), f = add(f, t), r = add(r, g), i = add(i, e);
51
+ }
52
+ return Array(m, f, r, i);
53
+}
54
+
55
+function cmn(d, g, m, f, r, i) {
56
+ return add(rol(add(add(g, d), add(f, i)), r), m);
57
+}
58
+
59
+function ff(d, g, m, f, r, i, n) {
60
+ return cmn(g & m | ~g & f, d, g, r, i, n);
61
+}
62
+
63
+function gg(d, g, m, f, r, i, n) {
64
+ return cmn(g & f | m & ~f, d, g, r, i, n);
65
+}
66
+
67
+function hh(d, g, m, f, r, i, n) {
68
+ return cmn(g ^ m ^ f, d, g, r, i, n);
69
+}
70
+
71
+function ii(d, g, m, f, r, i, n) {
72
+ return cmn(m ^ (g | ~f), d, g, r, i, n);
73
+}
74
+
75
+function add(d, g) {
76
+ let m = (65535 & d) + (65535 & g);
77
+ return (d >> 16) + (g >> 16) + (m >> 16) << 16 | 65535 & m;
78
+}
79
+
80
+function rol(d, g) {
81
+ return d << g | d >>> 32 - g;
82
+}
public/novnc/core/crypto/rsa.js
new
+132
@@ -0,0 +1,132 @@
1
+import Base64 from "../base64.js";
2
+import { modPow, bigIntToU8Array, u8ArrayToBigInt } from "./bigint.js";
3
+
4
+export class RSACipher {
5
+ constructor() {
6
+ this._keyLength = 0;
7
+ this._keyBytes = 0;
8
+ this._n = null;
9
+ this._e = null;
10
+ this._d = null;
11
+ this._nBigInt = null;
12
+ this._eBigInt = null;
13
+ this._dBigInt = null;
14
+ this._extractable = false;
15
+ }
16
+
17
+ get algorithm() {
18
+ return { name: "RSA-PKCS1-v1_5" };
19
+ }
20
+
21
+ _base64urlDecode(data) {
22
+ data = data.replace(/-/g, "+").replace(/_/g, "/");
23
+ data = data.padEnd(Math.ceil(data.length / 4) * 4, "=");
24
+ return Base64.decode(data);
25
+ }
26
+
27
+ _padArray(arr, length) {
28
+ const res = new Uint8Array(length);
29
+ res.set(arr, length - arr.length);
30
+ return res;
31
+ }
32
+
33
+ static async generateKey(algorithm, extractable, _keyUsages) {
34
+ const cipher = new RSACipher;
35
+ await cipher._generateKey(algorithm, extractable);
36
+ return { privateKey: cipher };
37
+ }
38
+
39
+ async _generateKey(algorithm, extractable) {
40
+ this._keyLength = algorithm.modulusLength;
41
+ this._keyBytes = Math.ceil(this._keyLength / 8);
42
+ const key = await window.crypto.subtle.generateKey(
43
+ {
44
+ name: "RSA-OAEP",
45
+ modulusLength: algorithm.modulusLength,
46
+ publicExponent: algorithm.publicExponent,
47
+ hash: {name: "SHA-256"},
48
+ },
49
+ true, ["encrypt", "decrypt"]);
50
+ const privateKey = await window.crypto.subtle.exportKey("jwk", key.privateKey);
51
+ this._n = this._padArray(this._base64urlDecode(privateKey.n), this._keyBytes);
52
+ this._nBigInt = u8ArrayToBigInt(this._n);
53
+ this._e = this._padArray(this._base64urlDecode(privateKey.e), this._keyBytes);
54
+ this._eBigInt = u8ArrayToBigInt(this._e);
55
+ this._d = this._padArray(this._base64urlDecode(privateKey.d), this._keyBytes);
56
+ this._dBigInt = u8ArrayToBigInt(this._d);
57
+ this._extractable = extractable;
58
+ }
59
+
60
+ static async importKey(key, _algorithm, extractable, keyUsages) {
61
+ if (keyUsages.length !== 1 || keyUsages[0] !== "encrypt") {
62
+ throw new Error("only support importing RSA public key");
63
+ }
64
+ const cipher = new RSACipher;
65
+ await cipher._importKey(key, extractable);
66
+ return cipher;
67
+ }
68
+
69
+ async _importKey(key, extractable) {
70
+ const n = key.n;
71
+ const e = key.e;
72
+ if (n.length !== e.length) {
73
+ throw new Error("the sizes of modulus and public exponent do not match");
74
+ }
75
+ this._keyBytes = n.length;
76
+ this._keyLength = this._keyBytes * 8;
77
+ this._n = new Uint8Array(this._keyBytes);
78
+ this._e = new Uint8Array(this._keyBytes);
79
+ this._n.set(n);
80
+ this._e.set(e);
81
+ this._nBigInt = u8ArrayToBigInt(this._n);
82
+ this._eBigInt = u8ArrayToBigInt(this._e);
83
+ this._extractable = extractable;
84
+ }
85
+
86
+ async encrypt(_algorithm, message) {
87
+ if (message.length > this._keyBytes - 11) {
88
+ return null;
89
+ }
90
+ const ps = new Uint8Array(this._keyBytes - message.length - 3);
91
+ window.crypto.getRandomValues(ps);
92
+ for (let i = 0; i < ps.length; i++) {
93
+ ps[i] = Math.floor(ps[i] * 254 / 255 + 1);
94
+ }
95
+ const em = new Uint8Array(this._keyBytes);
96
+ em[1] = 0x02;
97
+ em.set(ps, 2);
98
+ em.set(message, ps.length + 3);
99
+ const emBigInt = u8ArrayToBigInt(em);
100
+ const c = modPow(emBigInt, this._eBigInt, this._nBigInt);
101
+ return bigIntToU8Array(c, this._keyBytes);
102
+ }
103
+
104
+ async decrypt(_algorithm, message) {
105
+ if (message.length !== this._keyBytes) {
106
+ return null;
107
+ }
108
+ const msgBigInt = u8ArrayToBigInt(message);
109
+ const emBigInt = modPow(msgBigInt, this._dBigInt, this._nBigInt);
110
+ const em = bigIntToU8Array(emBigInt, this._keyBytes);
111
+ if (em[0] !== 0x00 || em[1] !== 0x02) {
112
+ return null;
113
+ }
114
+ let i = 2;
115
+ for (; i < em.length; i++) {
116
+ if (em[i] === 0x00) {
117
+ break;
118
+ }
119
+ }
120
+ if (i === em.length) {
121
+ return null;
122
+ }
123
+ return em.slice(i + 1, em.length);
124
+ }
125
+
126
+ async exportKey() {
127
+ if (!this._extractable) {
128
+ throw new Error("key is not extractable");
129
+ }
130
+ return { n: this._n, e: this._e, d: this._d };
131
+ }
132
+}
public/novnc/core/decoders/hextile.js
+12
-22
@@ -31,10 +31,7 @@ export default class HextileDecoder {
31
return false;
32
}
33
34
- let rQ = sock.rQ;
35
- let rQi = sock.rQi;
36
-
37
- let subencoding = rQ[rQi]; // Peek
34
+ let subencoding = sock.rQpeek8();
35
if (subencoding > 30) { // Raw
36
throw new Error("Illegal hextile subencoding (subencoding: " +
37
subencoding + ")");
@@ -65,7 +62,7 @@ export default class HextileDecoder {
62
return false;
63
}
64
68
- let subrects = rQ[rQi + bytes - 1]; // Peek
65
+ let subrects = sock.rQpeekBytes(bytes).at(-1);
66
if (subencoding & 0x10) { // SubrectsColoured
67
bytes += subrects * (4 + 2);
68
} else {
@@ -79,7 +76,7 @@ export default class HextileDecoder {
76
}
77
78
// We know the encoding and have a whole tile
82
- rQi++;
79
+ sock.rQshift8();
80
if (subencoding === 0) {
81
if (this._lastsubencoding & 0x01) {
82
// Weird: ignore blanks are RAW
@@ -89,42 +86,36 @@ export default class HextileDecoder {
86
}
87
} else if (subencoding & 0x01) { // Raw
88
let pixels = tw * th;
89
+ let data = sock.rQshiftBytes(pixels * 4, false);
90
// Max sure the image is fully opaque
91
for (let i = 0;i < pixels;i++) {
94
- rQ[rQi + i * 4 + 3] = 255;
92
+ data[i * 4 + 3] = 255;
93
}
96
- display.blitImage(tx, ty, tw, th, rQ, rQi);
97
- rQi += bytes - 1;
94
+ display.blitImage(tx, ty, tw, th, data, 0);
95
} else {
96
if (subencoding & 0x02) { // Background
100
- this._background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
101
- rQi += 4;
97
+ this._background = new Uint8Array(sock.rQshiftBytes(4));
98
}
99
if (subencoding & 0x04) { // Foreground
104
- this._foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
105
- rQi += 4;
100
+ this._foreground = new Uint8Array(sock.rQshiftBytes(4));
101
}
102
103
this._startTile(tx, ty, tw, th, this._background);
104
if (subencoding & 0x08) { // AnySubrects
110
- let subrects = rQ[rQi];
111
- rQi++;
105
+ let subrects = sock.rQshift8();
106
107
for (let s = 0; s < subrects; s++) {
108
let color;
109
if (subencoding & 0x10) { // SubrectsColoured
116
- color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
117
- rQi += 4;
110
+ color = sock.rQshiftBytes(4);
111
} else {
112
color = this._foreground;
113
}
121
- const xy = rQ[rQi];
122
- rQi++;
114
+ const xy = sock.rQshift8();
115
const sx = (xy >> 4);
116
const sy = (xy & 0x0f);
117
126
- const wh = rQ[rQi];
127
- rQi++;
118
+ const wh = sock.rQshift8();
119
const sw = (wh >> 4) + 1;
120
const sh = (wh & 0x0f) + 1;
121
@@ -133,7 +124,6 @@ export default class HextileDecoder {
124
}
125
this._finishTile(display);
126
}
136
- sock.rQi = rQi;
127
this._lastsubencoding = subencoding;
128
this._tiles--;
129
}
public/novnc/core/decoders/jpeg.js
+111
-106
@@ -11,131 +11,136 @@ export default class JPEGDecoder {
11
constructor() {
12
// RealVNC will reuse the quantization tables
13
// and Huffman tables, so we need to cache them.
14
- this._quantTables = [];
15
- this._huffmanTables = [];
14
this._cachedQuantTables = [];
15
this._cachedHuffmanTables = [];
16
19
- this._jpegLength = 0;
17
this._segments = [];
18
}
19
20
decodeRect(x, y, width, height, sock, display, depth) {
21
// A rect of JPEG encodings is simply a JPEG file
25
- if (!this._parseJPEG(sock.rQslice(0))) {
26
- return false;
27
- }
28
- const data = sock.rQshiftBytes(this._jpegLength);
29
- if (this._quantTables.length != 0 && this._huffmanTables.length != 0) {
30
- // If there are quantization tables and Huffman tables in the JPEG
31
- // image, we can directly render it.
32
- display.imageRect(x, y, width, height, "image/jpeg", data);
33
- return true;
34
- } else {
35
- // Otherwise we need to insert cached tables.
36
- const sofIndex = this._segments.findIndex(
37
- x => x[1] == 0xC0 || x[1] == 0xC2
38
- );
39
- if (sofIndex == -1) {
40
- throw new Error("Illegal JPEG image without SOF");
22
+ while (true) {
23
+ let segment = this._readSegment(sock);
24
+ if (segment === null) {
25
+ return false;
26
}
42
- let segments = this._segments.slice(0, sofIndex);
43
- segments = segments.concat(this._quantTables.length ?
44
- this._quantTables :
45
- this._cachedQuantTables);
46
- segments.push(this._segments[sofIndex]);
47
- segments = segments.concat(this._huffmanTables.length ?
48
- this._huffmanTables :
49
- this._cachedHuffmanTables,
50
- this._segments.slice(sofIndex + 1));
51
- let length = 0;
52
- for (let i = 0; i < segments.length; i++) {
53
- length += segments[i].length;
27
+ this._segments.push(segment);
28
+ // End of image?
29
+ if (segment[1] === 0xD9) {
30
+ break;
31
}
55
- const data = new Uint8Array(length);
56
- length = 0;
57
- for (let i = 0; i < segments.length; i++) {
58
- data.set(segments[i], length);
59
- length += segments[i].length;
32
+ }
33
+
34
+ let huffmanTables = [];
35
+ let quantTables = [];
36
+ for (let segment of this._segments) {
37
+ let type = segment[1];
38
+ if (type === 0xC4) {
39
+ // Huffman tables
40
+ huffmanTables.push(segment);
41
+ } else if (type === 0xDB) {
42
+ // Quantization tables
43
+ quantTables.push(segment);
44
}
61
- display.imageRect(x, y, width, height, "image/jpeg", data);
62
- return true;
45
}
64
- }
46
66
- _parseJPEG(buffer) {
67
- if (this._quantTables.length != 0) {
68
- this._cachedQuantTables = this._quantTables;
47
+ const sofIndex = this._segments.findIndex(
48
+ x => x[1] == 0xC0 || x[1] == 0xC2
49
+ );
50
+ if (sofIndex == -1) {
51
+ throw new Error("Illegal JPEG image without SOF");
52
+ }
53
+
54
+ if (quantTables.length === 0) {
55
+ this._segments.splice(sofIndex+1, 0,
56
+ ...this._cachedQuantTables);
57
+ }
58
+ if (huffmanTables.length === 0) {
59
+ this._segments.splice(sofIndex+1, 0,
60
+ ...this._cachedHuffmanTables);
61
+ }
62
+
63
+ let length = 0;
64
+ for (let segment of this._segments) {
65
+ length += segment.length;
66
+ }
67
+
68
+ let data = new Uint8Array(length);
69
+ length = 0;
70
+ for (let segment of this._segments) {
71
+ data.set(segment, length);
72
+ length += segment.length;
73
+ }
74
+
75
+ display.imageRect(x, y, width, height, "image/jpeg", data);
76
+
77
+ if (huffmanTables.length !== 0) {
78
+ this._cachedHuffmanTables = huffmanTables;
79
}
70
- if (this._huffmanTables.length != 0) {
71
- this._cachedHuffmanTables = this._huffmanTables;
80
+ if (quantTables.length !== 0) {
81
+ this._cachedQuantTables = quantTables;
82
}
73
- this._quantTables = [];
74
- this._huffmanTables = [];
83
+
84
this._segments = [];
76
- let i = 0;
77
- let bufferLength = buffer.length;
78
- while (true) {
79
- let j = i;
80
- if (j + 2 > bufferLength) {
81
- return false;
82
- }
83
- if (buffer[j] != 0xFF) {
84
- throw new Error("Illegal JPEG marker received (byte: " +
85
- buffer[j] + ")");
86
- }
87
- const type = buffer[j+1];
88
- j += 2;
89
- if (type == 0xD9) {
90
- this._jpegLength = j;
91
- this._segments.push(buffer.slice(i, j));
92
- return true;
93
- } else if (type == 0xDA) {
94
- // start of scan
95
- let hasFoundEndOfScan = false;
96
- for (let k = j + 3; k + 1 < bufferLength; k++) {
97
- if (buffer[k] == 0xFF && buffer[k+1] != 0x00 &&
98
- !(buffer[k+1] >= 0xD0 && buffer[k+1] <= 0xD7)) {
99
- j = k;
100
- hasFoundEndOfScan = true;
101
- break;
102
- }
85
+
86
+ return true;
87
+ }
88
+
89
+ _readSegment(sock) {
90
+ if (sock.rQwait("JPEG", 2)) {
91
+ return null;
92
+ }
93
+
94
+ let marker = sock.rQshift8();
95
+ if (marker != 0xFF) {
96
+ throw new Error("Illegal JPEG marker received (byte: " +
97
+ marker + ")");
98
+ }
99
+ let type = sock.rQshift8();
100
+ if (type >= 0xD0 && type <= 0xD9 || type == 0x01) {
101
+ // No length after marker
102
+ return new Uint8Array([marker, type]);
103
+ }
104
+
105
+ if (sock.rQwait("JPEG", 2, 2)) {
106
+ return null;
107
+ }
108
+
109
+ let length = sock.rQshift16();
110
+ if (length < 2) {
111
+ throw new Error("Illegal JPEG length received (length: " +
112
+ length + ")");
113
+ }
114
+
115
+ if (sock.rQwait("JPEG", length-2, 4)) {
116
+ return null;
117
+ }
118
+
119
+ let extra = 0;
120
+ if (type === 0xDA) {
121
+ // start of scan
122
+ extra += 2;
123
+ while (true) {
124
+ if (sock.rQwait("JPEG", length-2+extra, 4)) {
125
+ return null;
126
}
104
- if (!hasFoundEndOfScan) {
105
- return false;
127
+ let data = sock.rQpeekBytes(length-2+extra, false);
128
+ if (data.at(-2) === 0xFF && data.at(-1) !== 0x00 &&
129
+ !(data.at(-1) >= 0xD0 && data.at(-1) <= 0xD7)) {
130
+ extra -= 2;
131
+ break;
132
}
107
- this._segments.push(buffer.slice(i, j));
108
- i = j;
109
- continue;
110
- } else if (type >= 0xD0 && type < 0xD9 || type == 0x01) {
111
- // No length after marker
112
- this._segments.push(buffer.slice(i, j));
113
- i = j;
114
- continue;
115
- }
116
- if (j + 2 > bufferLength) {
117
- return false;
118
- }
119
- const length = (buffer[j] << 8) + buffer[j+1] - 2;
120
- if (length < 0) {
121
- throw new Error("Illegal JPEG length received (length: " +
122
- length + ")");
123
- }
124
- j += 2;
125
- if (j + length > bufferLength) {
126
- return false;
133
+ extra++;
134
}
128
- j += length;
129
- const segment = buffer.slice(i, j);
130
- if (type == 0xC4) {
131
- // Huffman tables
132
- this._huffmanTables.push(segment);
133
- } else if (type == 0xDB) {
134
- // Quantization tables
135
- this._quantTables.push(segment);
136
- }
137
- this._segments.push(segment);
138
- i = j;
135
}
136
+
137
+ let segment = new Uint8Array(2 + length + extra);
138
+ segment[0] = marker;
139
+ segment[1] = type;
140
+ segment[2] = length >> 8;
141
+ segment[3] = length;
142
+ segment.set(sock.rQshiftBytes(length-2+extra, false), 4);
143
+
144
+ return segment;
145
}
146
}
public/novnc/core/decoders/raw.js
+22
-29
@@ -24,41 +24,34 @@ export default class RawDecoder {
24
const pixelSize = depth == 8 ? 1 : 4;
25
const bytesPerLine = width * pixelSize;
26
27
- if (sock.rQwait("RAW", bytesPerLine)) {
28
- return false;
29
- }
27
+ while (this._lines > 0) {
28
+ if (sock.rQwait("RAW", bytesPerLine)) {
29
+ return false;
30
+ }
31
31
- const curY = y + (height - this._lines);
32
- const currHeight = Math.min(this._lines,
33
- Math.floor(sock.rQlen / bytesPerLine));
34
- const pixels = width * currHeight;
32
+ const curY = y + (height - this._lines);
33
36
- let data = sock.rQ;
37
- let index = sock.rQi;
34
+ let data = sock.rQshiftBytes(bytesPerLine, false);
35
39
- // Convert data if needed
40
- if (depth == 8) {
41
- const newdata = new Uint8Array(pixels * 4);
42
- for (let i = 0; i < pixels; i++) {
43
- newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3;
44
- newdata[i * 4 + 1] = ((data[index + i] >> 2) & 0x3) * 255 / 3;
45
- newdata[i * 4 + 2] = ((data[index + i] >> 4) & 0x3) * 255 / 3;
46
- newdata[i * 4 + 3] = 255;
36
+ // Convert data if needed
37
+ if (depth == 8) {
38
+ const newdata = new Uint8Array(width * 4);
39
+ for (let i = 0; i < width; i++) {
40
+ newdata[i * 4 + 0] = ((data[i] >> 0) & 0x3) * 255 / 3;
41
+ newdata[i * 4 + 1] = ((data[i] >> 2) & 0x3) * 255 / 3;
42
+ newdata[i * 4 + 2] = ((data[i] >> 4) & 0x3) * 255 / 3;
43
+ newdata[i * 4 + 3] = 255;
44
+ }
45
+ data = newdata;
46
}
48
- data = newdata;
49
- index = 0;
50
- }
47
52
- // Max sure the image is fully opaque
53
- for (let i = 0; i < pixels; i++) {
54
- data[index + i * 4 + 3] = 255;
55
- }
48
+ // Max sure the image is fully opaque
49
+ for (let i = 0; i < width; i++) {
50
+ data[i * 4 + 3] = 255;
51
+ }
52
57
- display.blitImage(x, curY, width, currHeight, data, index);
58
- sock.rQskipBytes(currHeight * bytesPerLine);
59
- this._lines -= currHeight;
60
- if (this._lines > 0) {
61
- return false;
53
+ display.blitImage(x, curY, width, 1, data, 0);
54
+ this._lines--;
55
}
56
57
return true;
public/novnc/core/decoders/tight.js
+70
-8
@@ -76,12 +76,8 @@ export default class TightDecoder {
76
return false;
77
}
78
79
- const rQi = sock.rQi;
80
- const rQ = sock.rQ;
81
-
82
- display.fillRect(x, y, width, height,
83
- [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2]], false);
84
- sock.rQskipBytes(3);
79
+ let pixel = sock.rQshiftBytes(3);
80
+ display.fillRect(x, y, width, height, pixel, false);
81
82
return true;
83
}
@@ -289,7 +285,73 @@ export default class TightDecoder {
285
}
286
287
_gradientFilter(streamId, x, y, width, height, sock, display, depth) {
292
- throw new Error("Gradient filter not implemented");
288
+ // assume the TPIXEL is 3 bytes long
289
+ const uncompressedSize = width * height * 3;
290
+ let data;
291
+
292
+ if (uncompressedSize === 0) {
293
+ return true;
294
+ }
295
+
296
+ if (uncompressedSize < 12) {
297
+ if (sock.rQwait("TIGHT", uncompressedSize)) {
298
+ return false;
299
+ }
300
+
301
+ data = sock.rQshiftBytes(uncompressedSize);
302
+ } else {
303
+ data = this._readData(sock);
304
+ if (data === null) {
305
+ return false;
306
+ }
307
+
308
+ this._zlibs[streamId].setInput(data);
309
+ data = this._zlibs[streamId].inflate(uncompressedSize);
310
+ this._zlibs[streamId].setInput(null);
311
+ }
312
+
313
+ let rgbx = new Uint8Array(4 * width * height);
314
+
315
+ let rgbxIndex = 0, dataIndex = 0;
316
+ let left = new Uint8Array(3);
317
+ for (let x = 0; x < width; x++) {
318
+ for (let c = 0; c < 3; c++) {
319
+ const prediction = left[c];
320
+ const value = data[dataIndex++] + prediction;
321
+ rgbx[rgbxIndex++] = value;
322
+ left[c] = value;
323
+ }
324
+ rgbx[rgbxIndex++] = 255;
325
+ }
326
+
327
+ let upperIndex = 0;
328
+ let upper = new Uint8Array(3),
329
+ upperleft = new Uint8Array(3);
330
+ for (let y = 1; y < height; y++) {
331
+ left.fill(0);
332
+ upperleft.fill(0);
333
+ for (let x = 0; x < width; x++) {
334
+ for (let c = 0; c < 3; c++) {
335
+ upper[c] = rgbx[upperIndex++];
336
+ let prediction = left[c] + upper[c] - upperleft[c];
337
+ if (prediction < 0) {
338
+ prediction = 0;
339
+ } else if (prediction > 255) {
340
+ prediction = 255;
341
+ }
342
+ const value = data[dataIndex++] + prediction;
343
+ rgbx[rgbxIndex++] = value;
344
+ upperleft[c] = upper[c];
345
+ left[c] = value;
346
+ }
347
+ rgbx[rgbxIndex++] = 255;
348
+ upperIndex++;
349
+ }
350
+ }
351
+
352
+ display.blitImage(x, y, width, height, rgbx, 0, false);
353
+
354
+ return true;
355
}
356
357
_readData(sock) {
@@ -316,7 +378,7 @@ export default class TightDecoder {
378
return null;
379
}
380
319
- let data = sock.rQshiftBytes(this._len);
381
+ let data = sock.rQshiftBytes(this._len, false);
382
this._len = 0;
383
384
return data;
public/novnc/core/decoders/zrle.js
+1
-1
@@ -32,7 +32,7 @@ export default class ZRLEDecoder {
32
return false;
33
}
34
35
- const data = sock.rQshiftBytes(this._length);
35
+ const data = sock.rQshiftBytes(this._length, false);
36
37
this._inflator.setInput(data);
38
public/novnc/core/deflator.js
+2
-3
@@ -7,7 +7,7 @@
7
*/
8
9
import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js";
10
-import { Z_FULL_FLUSH } from "../vendor/pako/lib/zlib/deflate.js";
10
+import { Z_FULL_FLUSH, Z_DEFAULT_COMPRESSION } from "../vendor/pako/lib/zlib/deflate.js";
11
import ZStream from "../vendor/pako/lib/zlib/zstream.js";
12
13
export default class Deflator {
@@ -15,9 +15,8 @@ export default class Deflator {
15
this.strm = new ZStream();
16
this.chunkSize = 1024 * 10 * 10;
17
this.outputBuffer = new Uint8Array(this.chunkSize);
18
- this.windowBits = 5;
18
20
- deflateInit(this.strm, this.windowBits);
19
+ deflateInit(this.strm, Z_DEFAULT_COMPRESSION);
20
}
21
22
deflate(inData) {
public/novnc/core/display.js
+13
-10
@@ -15,7 +15,7 @@ export default class Display {
15
this._drawCtx = null;
16
17
this._renderQ = []; // queue drawing actions for in-oder rendering
18
- this._flushing = false;
18
+ this._flushPromise = null;
19
20
// the full frame buffer (logical canvas) size
21
this._fbWidth = 0;
@@ -61,10 +61,6 @@ export default class Display {
61
62
this._scale = 1.0;
63
this._clipViewport = false;
64
-
65
- // ===== EVENT HANDLERS =====
66
-
67
- this.onflush = () => {}; // A flush request has finished
64
}
65
66
// ===== PROPERTIES =====
@@ -306,9 +302,14 @@ export default class Display {
302
303
flush() {
304
if (this._renderQ.length === 0) {
309
- this.onflush();
305
+ return Promise.resolve();
306
} else {
311
- this._flushing = true;
307
+ if (this._flushPromise === null) {
308
+ this._flushPromise = new Promise((resolve) => {
309
+ this._flushResolve = resolve;
310
+ });
311
+ }
312
+ return this._flushPromise;
313
}
314
}
315
@@ -517,9 +518,11 @@ export default class Display {
518
}
519
}
520
520
- if (this._renderQ.length === 0 && this._flushing) {
521
- this._flushing = false;
522
- this.onflush();
521
+ if (this._renderQ.length === 0 &&
522
+ this._flushPromise !== null) {
523
+ this._flushResolve();
524
+ this._flushPromise = null;
525
+ this._flushResolve = null;
526
}
527
}
528
}
public/novnc/core/encodings.js
+1
@@ -22,6 +22,7 @@ export const encodings = {
22
pseudoEncodingLastRect: -224,
23
pseudoEncodingCursor: -239,
24
pseudoEncodingQEMUExtendedKeyEvent: -258,
25
+ pseudoEncodingQEMULedEvent: -261,
26
pseudoEncodingDesktopName: -307,
27
pseudoEncodingExtendedDesktopSize: -308,
28
pseudoEncodingXvp: -309,
public/novnc/core/inflator.js
+1
-2
@@ -14,9 +14,8 @@ export default class Inflate {
14
this.strm = new ZStream();
15
this.chunkSize = 1024 * 10 * 10;
16
this.strm.output = new Uint8Array(this.chunkSize);
17
- this.windowBits = 5;
17
19
- inflateInit(this.strm, this.windowBits);
18
+ inflateInit(this.strm);
19
}
20
21
setInput(data) {
public/novnc/core/input/keyboard.js
+22
-13
@@ -36,7 +36,7 @@ export default class Keyboard {
36
37
// ===== PRIVATE METHODS =====
38
39
- _sendKeyEvent(keysym, code, down) {
39
+ _sendKeyEvent(keysym, code, down, numlock = null, capslock = null) {
40
if (down) {
41
this._keyDownList[code] = keysym;
42
} else {
@@ -48,8 +48,9 @@ export default class Keyboard {
48
}
49
50
Log.Debug("onkeyevent " + (down ? "down" : "up") +
51
- ", keysym: " + keysym, ", code: " + code);
52
- this.onkeyevent(keysym, code, down);
51
+ ", keysym: " + keysym, ", code: " + code +
52
+ ", numlock: " + numlock + ", capslock: " + capslock);
53
+ this.onkeyevent(keysym, code, down, numlock, capslock);
54
}
55
56
_getKeyCode(e) {
@@ -86,6 +87,14 @@ export default class Keyboard {
87
_handleKeyDown(e) {
88
const code = this._getKeyCode(e);
89
let keysym = KeyboardUtil.getKeysym(e);
90
+ let numlock = e.getModifierState('NumLock');
91
+ let capslock = e.getModifierState('CapsLock');
92
+
93
+ // getModifierState for NumLock is not supported on mac and ios and always returns false.
94
+ // Set to null to indicate unknown/unsupported instead.
95
+ if (browser.isMac() || browser.isIOS()) {
96
+ numlock = null;
97
+ }
98
99
// Windows doesn't have a proper AltGr, but handles it using
100
// fake Ctrl+Alt. However the remote end might not be Windows,
@@ -107,7 +116,7 @@ export default class Keyboard {
116
// key to "AltGraph".
117
keysym = KeyTable.XK_ISO_Level3_Shift;
118
} else {
110
- this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
119
+ this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true, numlock, capslock);
120
}
121
}
122
@@ -118,8 +127,8 @@ export default class Keyboard {
127
// If it's a virtual keyboard then it should be
128
// sufficient to just send press and release right
129
// after each other
121
- this._sendKeyEvent(keysym, code, true);
122
- this._sendKeyEvent(keysym, code, false);
130
+ this._sendKeyEvent(keysym, code, true, numlock, capslock);
131
+ this._sendKeyEvent(keysym, code, false, numlock, capslock);
132
}
133
134
stopEvent(e);
@@ -157,8 +166,8 @@ export default class Keyboard {
166
// while meta is held down
167
if ((browser.isMac() || browser.isIOS()) &&
168
(e.metaKey && code !== 'MetaLeft' && code !== 'MetaRight')) {
160
- this._sendKeyEvent(keysym, code, true);
161
- this._sendKeyEvent(keysym, code, false);
169
+ this._sendKeyEvent(keysym, code, true, numlock, capslock);
170
+ this._sendKeyEvent(keysym, code, false, numlock, capslock);
171
stopEvent(e);
172
return;
173
}
@@ -168,8 +177,8 @@ export default class Keyboard {
177
// which toggles on each press, but not on release. So pretend
178
// it was a quick press and release of the button.
179
if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) {
171
- this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
172
- this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
180
+ this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true, numlock, capslock);
181
+ this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false, numlock, capslock);
182
stopEvent(e);
183
return;
184
}
@@ -182,8 +191,8 @@ export default class Keyboard {
191
KeyTable.XK_Hiragana,
192
KeyTable.XK_Romaji ];
193
if (browser.isWindows() && jpBadKeys.includes(keysym)) {
185
- this._sendKeyEvent(keysym, code, true);
186
- this._sendKeyEvent(keysym, code, false);
194
+ this._sendKeyEvent(keysym, code, true, numlock, capslock);
195
+ this._sendKeyEvent(keysym, code, false, numlock, capslock);
196
stopEvent(e);
197
return;
198
}
@@ -199,7 +208,7 @@ export default class Keyboard {
208
return;
209
}
210
202
- this._sendKeyEvent(keysym, code, true);
211
+ this._sendKeyEvent(keysym, code, true, numlock, capslock);
212
}
213
214
_handleKeyUp(e) {
public/novnc/core/input/util.js
+1
-1
@@ -67,7 +67,7 @@ export function getKeycode(evt) {
67
// Get 'KeyboardEvent.key', handling legacy browsers
68
export function getKey(evt) {
69
// Are we getting a proper key value?
70
- if (evt.key !== undefined) {
70
+ if ((evt.key !== undefined) && (evt.key !== 'Unidentified')) {
71
// Mozilla isn't fully in sync with the spec yet
72
switch (evt.key) {
73
case 'OS': return 'Meta';
public/novnc/core/ra2.js
+46
-301
@@ -1,146 +1,25 @@
1
-import Base64 from './base64.js';
1
import { encodeUTF8 } from './util/strings.js';
2
import EventTargetMixin from './util/eventtarget.js';
3
+import legacyCrypto from './crypto/crypto.js';
4
5
-export class AESEAXCipher {
5
+class RA2Cipher {
6
constructor() {
7
- this._rawKey = null;
8
- this._ctrKey = null;
9
- this._cbcKey = null;
10
- this._zeroBlock = new Uint8Array(16);
11
- this._prefixBlock0 = this._zeroBlock;
12
- this._prefixBlock1 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
13
- this._prefixBlock2 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
14
- }
15
-
16
- async _encryptBlock(block) {
17
- const encrypted = await window.crypto.subtle.encrypt({
18
- name: "AES-CBC",
19
- iv: this._zeroBlock,
20
- }, this._cbcKey, block);
21
- return new Uint8Array(encrypted).slice(0, 16);
22
- }
23
-
24
- async _initCMAC() {
25
- const k1 = await this._encryptBlock(this._zeroBlock);
26
- const k2 = new Uint8Array(16);
27
- const v = k1[0] >>> 6;
28
- for (let i = 0; i < 15; i++) {
29
- k2[i] = (k1[i + 1] >> 6) | (k1[i] << 2);
30
- k1[i] = (k1[i + 1] >> 7) | (k1[i] << 1);
31
- }
32
- const lut = [0x0, 0x87, 0x0e, 0x89];
33
- k2[14] ^= v >>> 1;
34
- k2[15] = (k1[15] << 2) ^ lut[v];
35
- k1[15] = (k1[15] << 1) ^ lut[v >> 1];
36
- this._k1 = k1;
37
- this._k2 = k2;
38
- }
39
-
40
- async _encryptCTR(data, counter) {
41
- const encrypted = await window.crypto.subtle.encrypt({
42
- "name": "AES-CTR",
43
- counter: counter,
44
- length: 128
45
- }, this._ctrKey, data);
46
- return new Uint8Array(encrypted);
47
- }
48
-
49
- async _decryptCTR(data, counter) {
50
- const decrypted = await window.crypto.subtle.decrypt({
51
- "name": "AES-CTR",
52
- counter: counter,
53
- length: 128
54
- }, this._ctrKey, data);
55
- return new Uint8Array(decrypted);
56
- }
57
-
58
- async _computeCMAC(data, prefixBlock) {
59
- if (prefixBlock.length !== 16) {
60
- return null;
61
- }
62
- const n = Math.floor(data.length / 16);
63
- const m = Math.ceil(data.length / 16);
64
- const r = data.length - n * 16;
65
- const cbcData = new Uint8Array((m + 1) * 16);
66
- cbcData.set(prefixBlock);
67
- cbcData.set(data, 16);
68
- if (r === 0) {
69
- for (let i = 0; i < 16; i++) {
70
- cbcData[n * 16 + i] ^= this._k1[i];
71
- }
72
- } else {
73
- cbcData[(n + 1) * 16 + r] = 0x80;
74
- for (let i = 0; i < 16; i++) {
75
- cbcData[(n + 1) * 16 + i] ^= this._k2[i];
76
- }
77
- }
78
- let cbcEncrypted = await window.crypto.subtle.encrypt({
79
- name: "AES-CBC",
80
- iv: this._zeroBlock,
81
- }, this._cbcKey, cbcData);
82
-
83
- cbcEncrypted = new Uint8Array(cbcEncrypted);
84
- const mac = cbcEncrypted.slice(cbcEncrypted.length - 32, cbcEncrypted.length - 16);
85
- return mac;
86
- }
87
-
88
- async setKey(key) {
89
- this._rawKey = key;
90
- this._ctrKey = await window.crypto.subtle.importKey(
91
- "raw", key, {"name": "AES-CTR"}, false, ["encrypt", "decrypt"]);
92
- this._cbcKey = await window.crypto.subtle.importKey(
93
- "raw", key, {"name": "AES-CBC"}, false, ["encrypt", "decrypt"]);
94
- await this._initCMAC();
95
- }
96
-
97
- async encrypt(message, associatedData, nonce) {
98
- const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
99
- const encrypted = await this._encryptCTR(message, nCMAC);
100
- const adCMAC = await this._computeCMAC(associatedData, this._prefixBlock1);
101
- const mac = await this._computeCMAC(encrypted, this._prefixBlock2);
102
- for (let i = 0; i < 16; i++) {
103
- mac[i] ^= nCMAC[i] ^ adCMAC[i];
104
- }
105
- const res = new Uint8Array(16 + encrypted.length);
106
- res.set(encrypted);
107
- res.set(mac, encrypted.length);
108
- return res;
109
- }
110
-
111
- async decrypt(encrypted, associatedData, nonce, mac) {
112
- const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
113
- const adCMAC = await this._computeCMAC(associatedData, this._prefixBlock1);
114
- const computedMac = await this._computeCMAC(encrypted, this._prefixBlock2);
115
- for (let i = 0; i < 16; i++) {
116
- computedMac[i] ^= nCMAC[i] ^ adCMAC[i];
117
- }
118
- if (computedMac.length !== mac.length) {
119
- return null;
120
- }
121
- for (let i = 0; i < mac.length; i++) {
122
- if (computedMac[i] !== mac[i]) {
123
- return null;
124
- }
125
- }
126
- const res = await this._decryptCTR(encrypted, nCMAC);
127
- return res;
128
- }
129
-}
130
-
131
-export class RA2Cipher {
132
- constructor() {
133
- this._cipher = new AESEAXCipher();
7
+ this._cipher = null;
8
this._counter = new Uint8Array(16);
9
}
10
11
async setKey(key) {
138
- await this._cipher.setKey(key);
12
+ this._cipher = await legacyCrypto.importKey(
13
+ "raw", key, { name: "AES-EAX" }, false, ["encrypt, decrypt"]);
14
}
15
16
async makeMessage(message) {
17
const ad = new Uint8Array([(message.length & 0xff00) >>> 8, message.length & 0xff]);
143
- const encrypted = await this._cipher.encrypt(message, ad, this._counter);
18
+ const encrypted = await legacyCrypto.encrypt({
19
+ name: "AES-EAX",
20
+ iv: this._counter,
21
+ additionalData: ad,
22
+ }, this._cipher, message);
23
for (let i = 0; i < 16 && this._counter[i]++ === 255; i++);
24
const res = new Uint8Array(message.length + 2 + 16);
25
res.set(ad);
@@ -148,164 +27,18 @@ export class RA2Cipher {
27
return res;
28
}
29
151
- async receiveMessage(length, encrypted, mac) {
30
+ async receiveMessage(length, encrypted) {
31
const ad = new Uint8Array([(length & 0xff00) >>> 8, length & 0xff]);
153
- const res = await this._cipher.decrypt(encrypted, ad, this._counter, mac);
32
+ const res = await legacyCrypto.decrypt({
33
+ name: "AES-EAX",
34
+ iv: this._counter,
35
+ additionalData: ad,
36
+ }, this._cipher, encrypted);
37
for (let i = 0; i < 16 && this._counter[i]++ === 255; i++);
38
return res;
39
}
40
}
41
159
-export class RSACipher {
160
- constructor(keyLength) {
161
- this._key = null;
162
- this._keyLength = keyLength;
163
- this._keyBytes = Math.ceil(keyLength / 8);
164
- this._n = null;
165
- this._e = null;
166
- this._d = null;
167
- this._nBigInt = null;
168
- this._eBigInt = null;
169
- this._dBigInt = null;
170
- }
171
-
172
- _base64urlDecode(data) {
173
- data = data.replace(/-/g, "+").replace(/_/g, "/");
174
- data = data.padEnd(Math.ceil(data.length / 4) * 4, "=");
175
- return Base64.decode(data);
176
- }
177
-
178
- _u8ArrayToBigInt(arr) {
179
- let hex = '0x';
180
- for (let i = 0; i < arr.length; i++) {
181
- hex += arr[i].toString(16).padStart(2, '0');
182
- }
183
- return BigInt(hex);
184
- }
185
-
186
- _padArray(arr, length) {
187
- const res = new Uint8Array(length);
188
- res.set(arr, length - arr.length);
189
- return res;
190
- }
191
-
192
- _bigIntToU8Array(bigint, padLength=0) {
193
- let hex = bigint.toString(16);
194
- if (padLength === 0) {
195
- padLength = Math.ceil(hex.length / 2) * 2;
196
- }
197
- hex = hex.padStart(padLength * 2, '0');
198
- const length = hex.length / 2;
199
- const arr = new Uint8Array(length);
200
- for (let i = 0; i < length; i++) {
201
- arr[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
202
- }
203
- return arr;
204
- }
205
-
206
- _modPow(b, e, m) {
207
- if (m === 1n) {
208
- return 0;
209
- }
210
- let r = 1n;
211
- b = b % m;
212
- while (e > 0) {
213
- if (e % 2n === 1n) {
214
- r = (r * b) % m;
215
- }
216
- e = e / 2n;
217
- b = (b * b) % m;
218
- }
219
- return r;
220
- }
221
-
222
- async generateKey() {
223
- this._key = await window.crypto.subtle.generateKey(
224
- {
225
- name: "RSA-OAEP",
226
- modulusLength: this._keyLength,
227
- publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
228
- hash: {name: "SHA-256"},
229
- },
230
- true, ["encrypt", "decrypt"]);
231
- const privateKey = await window.crypto.subtle.exportKey("jwk", this._key.privateKey);
232
- this._n = this._padArray(this._base64urlDecode(privateKey.n), this._keyBytes);
233
- this._nBigInt = this._u8ArrayToBigInt(this._n);
234
- this._e = this._padArray(this._base64urlDecode(privateKey.e), this._keyBytes);
235
- this._eBigInt = this._u8ArrayToBigInt(this._e);
236
- this._d = this._padArray(this._base64urlDecode(privateKey.d), this._keyBytes);
237
- this._dBigInt = this._u8ArrayToBigInt(this._d);
238
- }
239
-
240
- setPublicKey(n, e) {
241
- if (n.length !== this._keyBytes || e.length !== this._keyBytes) {
242
- return;
243
- }
244
- this._n = new Uint8Array(this._keyBytes);
245
- this._e = new Uint8Array(this._keyBytes);
246
- this._n.set(n);
247
- this._e.set(e);
248
- this._nBigInt = this._u8ArrayToBigInt(this._n);
249
- this._eBigInt = this._u8ArrayToBigInt(this._e);
250
- }
251
-
252
- encrypt(message) {
253
- if (message.length > this._keyBytes - 11) {
254
- return null;
255
- }
256
- const ps = new Uint8Array(this._keyBytes - message.length - 3);
257
- window.crypto.getRandomValues(ps);
258
- for (let i = 0; i < ps.length; i++) {
259
- ps[i] = Math.floor(ps[i] * 254 / 255 + 1);
260
- }
261
- const em = new Uint8Array(this._keyBytes);
262
- em[1] = 0x02;
263
- em.set(ps, 2);
264
- em.set(message, ps.length + 3);
265
- const emBigInt = this._u8ArrayToBigInt(em);
266
- const c = this._modPow(emBigInt, this._eBigInt, this._nBigInt);
267
- return this._bigIntToU8Array(c, this._keyBytes);
268
- }
269
-
270
- decrypt(message) {
271
- if (message.length !== this._keyBytes) {
272
- return null;
273
- }
274
- const msgBigInt = this._u8ArrayToBigInt(message);
275
- const emBigInt = this._modPow(msgBigInt, this._dBigInt, this._nBigInt);
276
- const em = this._bigIntToU8Array(emBigInt, this._keyBytes);
277
- if (em[0] !== 0x00 || em[1] !== 0x02) {
278
- return null;
279
- }
280
- let i = 2;
281
- for (; i < em.length; i++) {
282
- if (em[i] === 0x00) {
283
- break;
284
- }
285
- }
286
- if (i === em.length) {
287
- return null;
288
- }
289
- return em.slice(i + 1, em.length);
290
- }
291
-
292
- get keyLength() {
293
- return this._keyLength;
294
- }
295
-
296
- get n() {
297
- return this._n;
298
- }
299
-
300
- get e() {
301
- return this._e;
302
- }
303
-
304
- get d() {
305
- return this._d;
306
- }
307
-}
308
-
42
export default class RSAAESAuthenticationState extends EventTargetMixin {
43
constructor(sock, getCredentials) {
44
super();
@@ -406,7 +139,7 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
139
this._hasStarted = true;
140
// 1: Receive server public key
141
await this._waitSockAsync(4);
409
- const serverKeyLengthBuffer = this._sock.rQslice(0, 4);
142
+ const serverKeyLengthBuffer = this._sock.rQpeekBytes(4);
143
const serverKeyLength = this._sock.rQshift32();
144
if (serverKeyLength < 1024) {
145
throw new Error("RA2: server public key is too short: " + serverKeyLength);
@@ -417,26 +150,31 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
150
await this._waitSockAsync(serverKeyBytes * 2);
151
const serverN = this._sock.rQshiftBytes(serverKeyBytes);
152
const serverE = this._sock.rQshiftBytes(serverKeyBytes);
420
- const serverRSACipher = new RSACipher(serverKeyLength);
421
- serverRSACipher.setPublicKey(serverN, serverE);
153
+ const serverRSACipher = await legacyCrypto.importKey(
154
+ "raw", { n: serverN, e: serverE }, { name: "RSA-PKCS1-v1_5" }, false, ["encrypt"]);
155
const serverPublickey = new Uint8Array(4 + serverKeyBytes * 2);
156
serverPublickey.set(serverKeyLengthBuffer);
157
serverPublickey.set(serverN, 4);
158
serverPublickey.set(serverE, 4 + serverKeyBytes);
159
160
// verify server public key
161
+ let approveKey = this._waitApproveKeyAsync();
162
this.dispatchEvent(new CustomEvent("serververification", {
163
detail: { type: "RSA", publickey: serverPublickey }
164
}));
431
- await this._waitApproveKeyAsync();
165
+ await approveKey;
166
167
// 2: Send client public key
168
const clientKeyLength = 2048;
169
const clientKeyBytes = Math.ceil(clientKeyLength / 8);
436
- const clientRSACipher = new RSACipher(clientKeyLength);
437
- await clientRSACipher.generateKey();
438
- const clientN = clientRSACipher.n;
439
- const clientE = clientRSACipher.e;
170
+ const clientRSACipher = (await legacyCrypto.generateKey({
171
+ name: "RSA-PKCS1-v1_5",
172
+ modulusLength: clientKeyLength,
173
+ publicExponent: new Uint8Array([1, 0, 1]),
174
+ }, true, ["encrypt"])).privateKey;
175
+ const clientExportedRSAKey = await legacyCrypto.exportKey("raw", clientRSACipher);
176
+ const clientN = clientExportedRSAKey.n;
177
+ const clientE = clientExportedRSAKey.e;
178
const clientPublicKey = new Uint8Array(4 + clientKeyBytes * 2);
179
clientPublicKey[0] = (clientKeyLength & 0xff000000) >>> 24;
180
clientPublicKey[1] = (clientKeyLength & 0xff0000) >>> 16;
@@ -444,17 +182,20 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
182
clientPublicKey[3] = clientKeyLength & 0xff;
183
clientPublicKey.set(clientN, 4);
184
clientPublicKey.set(clientE, 4 + clientKeyBytes);
447
- this._sock.send(clientPublicKey);
185
+ this._sock.sQpushBytes(clientPublicKey);
186
+ this._sock.flush();
187
188
// 3: Send client random
189
const clientRandom = new Uint8Array(16);
190
window.crypto.getRandomValues(clientRandom);
452
- const clientEncryptedRandom = serverRSACipher.encrypt(clientRandom);
191
+ const clientEncryptedRandom = await legacyCrypto.encrypt(
192
+ { name: "RSA-PKCS1-v1_5" }, serverRSACipher, clientRandom);
193
const clientRandomMessage = new Uint8Array(2 + serverKeyBytes);
194
clientRandomMessage[0] = (serverKeyBytes & 0xff00) >>> 8;
195
clientRandomMessage[1] = serverKeyBytes & 0xff;
196
clientRandomMessage.set(clientEncryptedRandom, 2);
457
- this._sock.send(clientRandomMessage);
197
+ this._sock.sQpushBytes(clientRandomMessage);
198
+ this._sock.flush();
199
200
// 4: Receive server random
201
await this._waitSockAsync(2);
@@ -462,7 +203,8 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
203
throw new Error("RA2: wrong encrypted message length");
204
}
205
const serverEncryptedRandom = this._sock.rQshiftBytes(clientKeyBytes);
465
- const serverRandom = clientRSACipher.decrypt(serverEncryptedRandom);
206
+ const serverRandom = await legacyCrypto.decrypt(
207
+ { name: "RSA-PKCS1-v1_5" }, clientRSACipher, serverEncryptedRandom);
208
if (serverRandom === null || serverRandom.length !== 16) {
209
throw new Error("RA2: corrupted server encrypted random");
210
}
@@ -494,13 +236,14 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
236
clientHash = await window.crypto.subtle.digest("SHA-1", clientHash);
237
serverHash = new Uint8Array(serverHash);
238
clientHash = new Uint8Array(clientHash);
497
- this._sock.send(await clientCipher.makeMessage(clientHash));
239
+ this._sock.sQpushBytes(await clientCipher.makeMessage(clientHash));
240
+ this._sock.flush();
241
await this._waitSockAsync(2 + 20 + 16);
242
if (this._sock.rQshift16() !== 20) {
243
throw new Error("RA2: wrong server hash");
244
}
245
const serverHashReceived = await serverCipher.receiveMessage(
503
- 20, this._sock.rQshiftBytes(20), this._sock.rQshiftBytes(16));
246
+ 20, this._sock.rQshiftBytes(20 + 16));
247
if (serverHashReceived === null) {
248
throw new Error("RA2: failed to authenticate the message");
249
}
@@ -516,11 +259,12 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
259
throw new Error("RA2: wrong subtype");
260
}
261
let subtype = (await serverCipher.receiveMessage(
519
- 1, this._sock.rQshiftBytes(1), this._sock.rQshiftBytes(16)));
262
+ 1, this._sock.rQshiftBytes(1 + 16)));
263
if (subtype === null) {
264
throw new Error("RA2: failed to authenticate the message");
265
}
266
subtype = subtype[0];
267
+ let waitCredentials = this._waitCredentialsAsync(subtype);
268
if (subtype === 1) {
269
if (this._getCredentials().username === undefined ||
270
this._getCredentials().password === undefined) {
@@ -537,7 +281,7 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
281
} else {
282
throw new Error("RA2: wrong subtype");
283
}
540
- await this._waitCredentialsAsync(subtype);
284
+ await waitCredentials;
285
let username;
286
if (subtype === 1) {
287
username = encodeUTF8(this._getCredentials().username).slice(0, 255);
@@ -554,7 +298,8 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
298
for (let i = 0; i < password.length; i++) {
299
credentials[username.length + 2 + i] = password.charCodeAt(i);
300
}
557
- this._sock.send(await clientCipher.makeMessage(credentials));
301
+ this._sock.sQpushBytes(await clientCipher.makeMessage(credentials));
302
+ this._sock.flush();
303
}
304
305
get hasStarted() {
@@ -564,4 +309,4 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
309
set hasStarted(s) {
310
this._hasStarted = s;
311
}
567
-}
\ No newline at end of file
312
+}
public/novnc/core/rfb.js
+250
-374
@@ -21,12 +21,11 @@ import Keyboard from "./input/keyboard.js";
21
import GestureHandler from "./input/gesturehandler.js";
22
import Cursor from "./util/cursor.js";
23
import Websock from "./websock.js";
24
-import DES from "./des.js";
24
import KeyTable from "./input/keysym.js";
25
import XtScancode from "./input/xtscancodes.js";
26
import { encodings } from "./encodings.js";
27
import RSAAESAuthenticationState from "./ra2.js";
29
-import { MD5 } from "./util/md5.js";
28
+import legacyCrypto from "./crypto/crypto.js";
29
30
import RawDecoder from "./decoders/raw.js";
31
import CopyRectDecoder from "./decoders/copyrect.js";
@@ -258,10 +257,11 @@ export default class RFB extends EventTargetMixin {
257
Log.Error("Display exception: " + exc);
258
throw exc;
259
}
261
- this._display.onflush = this._onFlush.bind(this);
260
261
this._keyboard = new Keyboard(this._canvas);
262
this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
263
+ this._remoteCapsLock = null; // Null indicates unknown or irrelevant
264
+ this._remoteNumLock = null;
265
266
this._gestures = new GestureHandler();
267
@@ -960,7 +960,7 @@ export default class RFB extends EventTargetMixin {
960
}
961
962
_handleMessage() {
963
- if (this._sock.rQlen === 0) {
963
+ if (this._sock.rQwait("message", 1)) {
964
Log.Warn("handleMessage called on an empty receive queue");
965
return;
966
}
@@ -977,7 +977,7 @@ export default class RFB extends EventTargetMixin {
977
if (!this._normalMsg()) {
978
break;
979
}
980
- if (this._sock.rQlen === 0) {
980
+ if (this._sock.rQwait("message", 1)) {
981
break;
982
}
983
}
@@ -995,7 +995,35 @@ export default class RFB extends EventTargetMixin {
995
}
996
}
997
998
- _handleKeyEvent(keysym, code, down) {
998
+ _handleKeyEvent(keysym, code, down, numlock, capslock) {
999
+ // If remote state of capslock is known, and it doesn't match the local led state of
1000
+ // the keyboard, we send a capslock keypress first to bring it into sync.
1001
+ // If we just pressed CapsLock, or we toggled it remotely due to it being out of sync
1002
+ // we clear the remote state so that we don't send duplicate or spurious fixes,
1003
+ // since it may take some time to receive the new remote CapsLock state.
1004
+ if (code == 'CapsLock' && down) {
1005
+ this._remoteCapsLock = null;
1006
+ }
1007
+ if (this._remoteCapsLock !== null && capslock !== null && this._remoteCapsLock !== capslock && down) {
1008
+ Log.Debug("Fixing remote caps lock");
1009
+
1010
+ this.sendKey(KeyTable.XK_Caps_Lock, 'CapsLock', true);
1011
+ this.sendKey(KeyTable.XK_Caps_Lock, 'CapsLock', false);
1012
+ // We clear the remote capsLock state when we do this to prevent issues with doing this twice
1013
+ // before we receive an update of the the remote state.
1014
+ this._remoteCapsLock = null;
1015
+ }
1016
+
1017
+ // Logic for numlock is exactly the same.
1018
+ if (code == 'NumLock' && down) {
1019
+ this._remoteNumLock = null;
1020
+ }
1021
+ if (this._remoteNumLock !== null && numlock !== null && this._remoteNumLock !== numlock && down) {
1022
+ Log.Debug("Fixing remote num lock");
1023
+ this.sendKey(KeyTable.XK_Num_Lock, 'NumLock', true);
1024
+ this.sendKey(KeyTable.XK_Num_Lock, 'NumLock', false);
1025
+ this._remoteNumLock = null;
1026
+ }
1027
this.sendKey(keysym, code, down);
1028
}
1029
@@ -1383,7 +1411,8 @@ export default class RFB extends EventTargetMixin {
1411
while (repeaterID.length < 250) {
1412
repeaterID += "\0";
1413
}
1386
- this._sock.sendString(repeaterID);
1414
+ this._sock.sQpushString(repeaterID);
1415
+ this._sock.flush();
1416
return true;
1417
}
1418
@@ -1393,7 +1422,8 @@ export default class RFB extends EventTargetMixin {
1422
1423
const cversion = "00" + parseInt(this._rfbVersion, 10) +
1424
".00" + ((this._rfbVersion * 10) % 10);
1396
- this._sock.sendString("RFB " + cversion + "\n");
1425
+ this._sock.sQpushString("RFB " + cversion + "\n");
1426
+ this._sock.flush();
1427
Log.Debug('Sent ProtocolVersion: ' + cversion);
1428
1429
this._rfbInitState = 'Security';
@@ -1445,7 +1475,8 @@ export default class RFB extends EventTargetMixin {
1475
return this._fail("Unsupported security types (types: " + types + ")");
1476
}
1477
1448
- this._sock.send([this._rfbAuthScheme]);
1478
+ this._sock.sQpush8(this._rfbAuthScheme);
1479
+ this._sock.flush();
1480
} else {
1481
// Server decides
1482
if (this._sock.rQwait("security scheme", 4)) { return false; }
@@ -1507,12 +1538,15 @@ export default class RFB extends EventTargetMixin {
1538
return false;
1539
}
1540
1510
- const xvpAuthStr = String.fromCharCode(this._rfbCredentials.username.length) +
1511
- String.fromCharCode(this._rfbCredentials.target.length) +
1512
- this._rfbCredentials.username +
1513
- this._rfbCredentials.target;
1514
- this._sock.sendString(xvpAuthStr);
1541
+ this._sock.sQpush8(this._rfbCredentials.username.length);
1542
+ this._sock.sQpush8(this._rfbCredentials.target.length);
1543
+ this._sock.sQpushString(this._rfbCredentials.username);
1544
+ this._sock.sQpushString(this._rfbCredentials.target);
1545
+
1546
+ this._sock.flush();
1547
+
1548
this._rfbAuthScheme = securityTypeVNCAuth;
1549
+
1550
return this._negotiateAuthentication();
1551
}
1552
@@ -1530,7 +1564,9 @@ export default class RFB extends EventTargetMixin {
1564
return this._fail("Unsupported VeNCrypt version " + major + "." + minor);
1565
}
1566
1533
- this._sock.send([0, 2]);
1567
+ this._sock.sQpush8(0);
1568
+ this._sock.sQpush8(2);
1569
+ this._sock.flush();
1570
this._rfbVeNCryptState = 1;
1571
}
1572
@@ -1589,12 +1625,10 @@ export default class RFB extends EventTargetMixin {
1625
return this._fail("Unsupported security types (types: " + subtypes + ")");
1626
}
1627
1592
- this._sock.send([this._rfbAuthScheme >> 24,
1593
- this._rfbAuthScheme >> 16,
1594
- this._rfbAuthScheme >> 8,
1595
- this._rfbAuthScheme]);
1628
+ this._sock.sQpush32(this._rfbAuthScheme);
1629
+ this._sock.flush();
1630
1597
- this._rfbVeNCryptState == 4;
1631
+ this._rfbVeNCryptState = 4;
1632
return true;
1633
}
1634
}
@@ -1611,20 +1645,11 @@ export default class RFB extends EventTargetMixin {
1645
const user = encodeUTF8(this._rfbCredentials.username);
1646
const pass = encodeUTF8(this._rfbCredentials.password);
1647
1614
- this._sock.send([
1615
- (user.length >> 24) & 0xFF,
1616
- (user.length >> 16) & 0xFF,
1617
- (user.length >> 8) & 0xFF,
1618
- user.length & 0xFF
1619
- ]);
1620
- this._sock.send([
1621
- (pass.length >> 24) & 0xFF,
1622
- (pass.length >> 16) & 0xFF,
1623
- (pass.length >> 8) & 0xFF,
1624
- pass.length & 0xFF
1625
- ]);
1626
- this._sock.sendString(user);
1627
- this._sock.sendString(pass);
1648
+ this._sock.sQpush32(user.length);
1649
+ this._sock.sQpush32(pass.length);
1650
+ this._sock.sQpushString(user);
1651
+ this._sock.sQpushString(pass);
1652
+ this._sock.flush();
1653
1654
this._rfbInitState = "SecurityResult";
1655
return true;
@@ -1643,7 +1668,8 @@ export default class RFB extends EventTargetMixin {
1668
// TODO(directxman12): make genDES not require an Array
1669
const challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));
1670
const response = RFB.genDES(this._rfbCredentials.password, challenge);
1646
- this._sock.send(response);
1671
+ this._sock.sQpushBytes(response);
1672
+ this._sock.flush();
1673
this._rfbInitState = "SecurityResult";
1674
return true;
1675
}
@@ -1661,8 +1687,9 @@ export default class RFB extends EventTargetMixin {
1687
if (this._rfbCredentials.ardPublicKey != undefined &&
1688
this._rfbCredentials.ardCredentials != undefined) {
1689
// if the async web crypto is done return the results
1664
- this._sock.send(this._rfbCredentials.ardCredentials);
1665
- this._sock.send(this._rfbCredentials.ardPublicKey);
1690
+ this._sock.sQpushBytes(this._rfbCredentials.ardCredentials);
1691
+ this._sock.sQpushBytes(this._rfbCredentials.ardPublicKey);
1692
+ this._sock.flush();
1693
this._rfbCredentials.ardCredentials = null;
1694
this._rfbCredentials.ardPublicKey = null;
1695
this._rfbInitState = "SecurityResult";
@@ -1681,77 +1708,35 @@ export default class RFB extends EventTargetMixin {
1708
let prime = this._sock.rQshiftBytes(keyLength); // predetermined prime modulus
1709
let serverPublicKey = this._sock.rQshiftBytes(keyLength); // other party's public key
1710
1684
- let clientPrivateKey = window.crypto.getRandomValues(new Uint8Array(keyLength));
1685
- let padding = Array.from(window.crypto.getRandomValues(new Uint8Array(64)), byte => String.fromCharCode(65+byte%26)).join('');
1686
-
1687
- this._negotiateARDAuthAsync(generator, keyLength, prime, serverPublicKey, clientPrivateKey, padding);
1711
+ let clientKey = legacyCrypto.generateKey(
1712
+ { name: "DH", g: generator, p: prime }, false, ["deriveBits"]);
1713
+ this._negotiateARDAuthAsync(keyLength, serverPublicKey, clientKey);
1714
1715
return false;
1716
}
1717
1692
- _modPow(base, exponent, modulus) {
1718
+ async _negotiateARDAuthAsync(keyLength, serverPublicKey, clientKey) {
1719
+ const clientPublicKey = legacyCrypto.exportKey("raw", clientKey.publicKey);
1720
+ const sharedKey = legacyCrypto.deriveBits(
1721
+ { name: "DH", public: serverPublicKey }, clientKey.privateKey, keyLength * 8);
1722
1694
- let baseHex = "0x"+Array.from(base, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
1695
- let exponentHex = "0x"+Array.from(exponent, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
1696
- let modulusHex = "0x"+Array.from(modulus, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
1697
-
1698
- let b = BigInt(baseHex);
1699
- let e = BigInt(exponentHex);
1700
- let m = BigInt(modulusHex);
1701
- let r = 1n;
1702
- b = b % m;
1703
- while (e > 0) {
1704
- if (e % 2n === 1n) {
1705
- r = (r * b) % m;
1706
- }
1707
- e = e / 2n;
1708
- b = (b * b) % m;
1709
- }
1710
- let hexResult = r.toString(16);
1711
-
1712
- while (hexResult.length/2<exponent.length || (hexResult.length%2 != 0)) {
1713
- hexResult = "0"+hexResult;
1714
- }
1715
-
1716
- let bytesResult = [];
1717
- for (let c = 0; c < hexResult.length; c += 2) {
1718
- bytesResult.push(parseInt(hexResult.substr(c, 2), 16));
1719
- }
1720
- return bytesResult;
1721
- }
1723
+ const username = encodeUTF8(this._rfbCredentials.username).substring(0, 63);
1724
+ const password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);
1725
1723
- async _aesEcbEncrypt(string, key) {
1724
- // perform AES-ECB blocks
1725
- let keyString = Array.from(key, byte => String.fromCharCode(byte)).join('');
1726
- let aesKey = await window.crypto.subtle.importKey("raw", MD5(keyString), {name: "AES-CBC"}, false, ["encrypt"]);
1727
- let data = new Uint8Array(string.length);
1728
- for (let i = 0; i < string.length; ++i) {
1729
- data[i] = string.charCodeAt(i);
1726
+ const credentials = window.crypto.getRandomValues(new Uint8Array(128));
1727
+ for (let i = 0; i < username.length; i++) {
1728
+ credentials[i] = username.charCodeAt(i);
1729
}
1731
- let encrypted = new Uint8Array(data.length);
1732
- for (let i=0;i<data.length;i+=16) {
1733
- let block = data.slice(i, i+16);
1734
- let encryptedBlock = await window.crypto.subtle.encrypt({name: "AES-CBC", iv: block},
1735
- aesKey, new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
1736
- );
1737
- encrypted.set((new Uint8Array(encryptedBlock)).slice(0, 16), i);
1730
+ credentials[username.length] = 0;
1731
+ for (let i = 0; i < password.length; i++) {
1732
+ credentials[64 + i] = password.charCodeAt(i);
1733
}
1739
- return encrypted;
1740
- }
1741
-
1742
- async _negotiateARDAuthAsync(generator, keyLength, prime, serverPublicKey, clientPrivateKey, padding) {
1743
- // calculate the DH keys
1744
- let clientPublicKey = this._modPow(generator, clientPrivateKey, prime);
1745
- let sharedKey = this._modPow(serverPublicKey, clientPrivateKey, prime);
1746
-
1747
- let username = encodeUTF8(this._rfbCredentials.username).substring(0, 63);
1748
- let password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);
1749
-
1750
- let paddedUsername = username + '\0' + padding.substring(0, 63);
1751
- let paddedPassword = password + '\0' + padding.substring(0, 63);
1752
- let credentials = paddedUsername.substring(0, 64) + paddedPassword.substring(0, 64);
1734
+ credentials[64 + password.length] = 0;
1735
1754
- let encrypted = await this._aesEcbEncrypt(credentials, sharedKey);
1736
+ const key = await legacyCrypto.digest("MD5", sharedKey);
1737
+ const cipher = await legacyCrypto.importKey(
1738
+ "raw", key, { name: "AES-ECB" }, false, ["encrypt"]);
1739
+ const encrypted = await legacyCrypto.encrypt({ name: "AES-ECB" }, cipher, credentials);
1740
1741
this._rfbCredentials.ardCredentials = encrypted;
1742
this._rfbCredentials.ardPublicKey = clientPublicKey;
@@ -1768,10 +1753,12 @@ export default class RFB extends EventTargetMixin {
1753
return false;
1754
}
1755
1771
- this._sock.send([0, 0, 0, this._rfbCredentials.username.length]);
1772
- this._sock.send([0, 0, 0, this._rfbCredentials.password.length]);
1773
- this._sock.sendString(this._rfbCredentials.username);
1774
- this._sock.sendString(this._rfbCredentials.password);
1756
+ this._sock.sQpush32(this._rfbCredentials.username.length);
1757
+ this._sock.sQpush32(this._rfbCredentials.password.length);
1758
+ this._sock.sQpushString(this._rfbCredentials.username);
1759
+ this._sock.sQpushString(this._rfbCredentials.password);
1760
+ this._sock.flush();
1761
+
1762
this._rfbInitState = "SecurityResult";
1763
return true;
1764
}
@@ -1809,7 +1796,8 @@ export default class RFB extends EventTargetMixin {
1796
"vendor or signature");
1797
}
1798
Log.Debug("Selected tunnel type: " + clientSupportedTunnelTypes[0]);
1812
- this._sock.send([0, 0, 0, 0]); // use NOTUNNEL
1799
+ this._sock.sQpush32(0); // use NOTUNNEL
1800
+ this._sock.flush();
1801
return false; // wait until we receive the sub auth count to continue
1802
} else {
1803
return this._fail("Server wanted tunnels, but doesn't support " +
@@ -1859,7 +1847,8 @@ export default class RFB extends EventTargetMixin {
1847
1848
for (let authType in clientSupportedTypes) {
1849
if (serverSupportedTypes.indexOf(authType) != -1) {
1862
- this._sock.send([0, 0, 0, clientSupportedTypes[authType]]);
1850
+ this._sock.sQpush32(clientSupportedTypes[authType]);
1851
+ this._sock.flush();
1852
Log.Debug("Selected authentication type: " + authType);
1853
1854
switch (authType) {
@@ -1905,8 +1894,8 @@ export default class RFB extends EventTargetMixin {
1894
if (e.message !== "disconnect normally") {
1895
this._fail(e.message);
1896
}
1908
- }).then(() => {
1909
- this.dispatchEvent(new CustomEvent('securityresult'));
1897
+ })
1898
+ .then(() => {
1899
this._rfbInitState = "SecurityResult";
1900
return true;
1901
}).finally(() => {
@@ -1934,15 +1923,15 @@ export default class RFB extends EventTargetMixin {
1923
const g = this._sock.rQshiftBytes(8);
1924
const p = this._sock.rQshiftBytes(8);
1925
const A = this._sock.rQshiftBytes(8);
1937
- const b = window.crypto.getRandomValues(new Uint8Array(8));
1938
- const B = new Uint8Array(this._modPow(g, b, p));
1939
- const secret = new Uint8Array(this._modPow(A, b, p));
1926
+ const dhKey = legacyCrypto.generateKey({ name: "DH", g: g, p: p }, true, ["deriveBits"]);
1927
+ const B = legacyCrypto.exportKey("raw", dhKey.publicKey);
1928
+ const secret = legacyCrypto.deriveBits({ name: "DH", public: A }, dhKey.privateKey, 64);
1929
1941
- const des = new DES(secret);
1930
+ const key = legacyCrypto.importKey("raw", secret, { name: "DES-CBC" }, false, ["encrypt"]);
1931
const username = encodeUTF8(this._rfbCredentials.username).substring(0, 255);
1932
const password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);
1944
- const usernameBytes = new Uint8Array(256);
1945
- const passwordBytes = new Uint8Array(64);
1933
+ let usernameBytes = new Uint8Array(256);
1934
+ let passwordBytes = new Uint8Array(64);
1935
window.crypto.getRandomValues(usernameBytes);
1936
window.crypto.getRandomValues(passwordBytes);
1937
for (let i = 0; i < username.length; i++) {
@@ -1953,25 +1942,12 @@ export default class RFB extends EventTargetMixin {
1942
passwordBytes[i] = password.charCodeAt(i);
1943
}
1944
passwordBytes[password.length] = 0;
1956
- let x = new Uint8Array(secret);
1957
- for (let i = 0; i < 32; i++) {
1958
- for (let j = 0; j < 8; j++) {
1959
- x[j] ^= usernameBytes[i * 8 + j];
1960
- }
1961
- x = des.enc8(x);
1962
- usernameBytes.set(x, i * 8);
1963
- }
1964
- x = new Uint8Array(secret);
1965
- for (let i = 0; i < 8; i++) {
1966
- for (let j = 0; j < 8; j++) {
1967
- x[j] ^= passwordBytes[i * 8 + j];
1968
- }
1969
- x = des.enc8(x);
1970
- passwordBytes.set(x, i * 8);
1971
- }
1972
- this._sock.send(B);
1973
- this._sock.send(usernameBytes);
1974
- this._sock.send(passwordBytes);
1945
+ usernameBytes = legacyCrypto.encrypt({ name: "DES-CBC", iv: secret }, key, usernameBytes);
1946
+ passwordBytes = legacyCrypto.encrypt({ name: "DES-CBC", iv: secret }, key, passwordBytes);
1947
+ this._sock.sQpushBytes(B);
1948
+ this._sock.sQpushBytes(usernameBytes);
1949
+ this._sock.sQpushBytes(passwordBytes);
1950
+ this._sock.flush();
1951
this._rfbInitState = "SecurityResult";
1952
return true;
1953
}
@@ -1979,7 +1955,11 @@ export default class RFB extends EventTargetMixin {
1955
_negotiateAuthentication() {
1956
switch (this._rfbAuthScheme) {
1957
case securityTypeNone:
1982
- this._rfbInitState = 'SecurityResult';
1958
+ if (this._rfbVersion >= 3.8) {
1959
+ this._rfbInitState = 'SecurityResult';
1960
+ } else {
1961
+ this._rfbInitState = 'ClientInitialisation';
1962
+ }
1963
return true;
1964
1965
case securityTypeXVP:
@@ -2016,13 +1996,6 @@ export default class RFB extends EventTargetMixin {
1996
}
1997
1998
_handleSecurityResult() {
2019
- // There is no security choice, and hence no security result
2020
- // until RFB 3.7
2021
- if (this._rfbVersion < 3.7) {
2022
- this._rfbInitState = 'ClientInitialisation';
2023
- return true;
2024
- }
2025
-
1999
if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
2000
2001
const status = this._sock.rQshift32();
@@ -2158,6 +2131,7 @@ export default class RFB extends EventTargetMixin {
2131
encs.push(encodings.pseudoEncodingDesktopSize);
2132
encs.push(encodings.pseudoEncodingLastRect);
2133
encs.push(encodings.pseudoEncodingQEMUExtendedKeyEvent);
2134
+ encs.push(encodings.pseudoEncodingQEMULedEvent);
2135
encs.push(encodings.pseudoEncodingExtendedDesktopSize);
2136
encs.push(encodings.pseudoEncodingXvp);
2137
encs.push(encodings.pseudoEncodingFence);
@@ -2199,7 +2173,8 @@ export default class RFB extends EventTargetMixin {
2173
return this._handleSecurityReason();
2174
2175
case 'ClientInitialisation':
2202
- this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation
2176
+ this._sock.sQpush8(this._shared ? 1 : 0); // ClientInitialisation
2177
+ this._sock.flush();
2178
this._rfbInitState = 'ServerInitialisation';
2179
return true;
2180
@@ -2381,7 +2356,7 @@ export default class RFB extends EventTargetMixin {
2356
textData = textData.slice(0, -1);
2357
}
2358
2384
- textData = textData.replace("\r\n", "\n");
2359
+ textData = textData.replaceAll("\r\n", "\n");
2360
2361
this.dispatchEvent(new CustomEvent(
2362
"clipboard",
@@ -2512,19 +2487,11 @@ export default class RFB extends EventTargetMixin {
2487
2488
default:
2489
this._fail("Unexpected server message (type " + msgType + ")");
2515
- Log.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
2490
+ Log.Debug("sock.rQpeekBytes(30): " + this._sock.rQpeekBytes(30));
2491
return true;
2492
}
2493
}
2494
2520
- _onFlush() {
2521
- this._flushing = false;
2522
- // Resume processing
2523
- if (this._sock.rQlen > 0) {
2524
- this._handleMessage();
2525
- }
2526
- }
2527
-
2495
_framebufferUpdate() {
2496
if (this._FBU.rects === 0) {
2497
if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
@@ -2535,7 +2502,14 @@ export default class RFB extends EventTargetMixin {
2502
// to avoid building up an excessive queue
2503
if (this._display.pending()) {
2504
this._flushing = true;
2538
- this._display.flush();
2505
+ this._display.flush()
2506
+ .then(() => {
2507
+ this._flushing = false;
2508
+ // Resume processing
2509
+ if (!this._sock.rQwait("message", 1)) {
2510
+ this._handleMessage();
2511
+ }
2512
+ });
2513
return false;
2514
}
2515
}
@@ -2545,13 +2519,13 @@ export default class RFB extends EventTargetMixin {
2519
if (this._sock.rQwait("rect header", 12)) { return false; }
2520
/* New FramebufferUpdate */
2521
2548
- const hdr = this._sock.rQshiftBytes(12);
2549
- this._FBU.x = (hdr[0] << 8) + hdr[1];
2550
- this._FBU.y = (hdr[2] << 8) + hdr[3];
2551
- this._FBU.width = (hdr[4] << 8) + hdr[5];
2552
- this._FBU.height = (hdr[6] << 8) + hdr[7];
2553
- this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
2554
- (hdr[10] << 8) + hdr[11], 10);
2522
+ this._FBU.x = this._sock.rQshift16();
2523
+ this._FBU.y = this._sock.rQshift16();
2524
+ this._FBU.width = this._sock.rQshift16();
2525
+ this._FBU.height = this._sock.rQshift16();
2526
+ this._FBU.encoding = this._sock.rQshift32();
2527
+ /* Encodings are signed */
2528
+ this._FBU.encoding >>= 0;
2529
}
2530
2531
if (!this._handleRect()) {
@@ -2593,6 +2567,9 @@ export default class RFB extends EventTargetMixin {
2567
case encodings.pseudoEncodingExtendedDesktopSize:
2568
return this._handleExtendedDesktopSize();
2569
2570
+ case encodings.pseudoEncodingQEMULedEvent:
2571
+ return this._handleLedEvent();
2572
+
2573
default:
2574
return this._handleDataRect();
2575
}
@@ -2770,6 +2747,21 @@ export default class RFB extends EventTargetMixin {
2747
return true;
2748
}
2749
2750
+ _handleLedEvent() {
2751
+ if (this._sock.rQwait("LED Status", 1)) {
2752
+ return false;
2753
+ }
2754
+
2755
+ let data = this._sock.rQshift8();
2756
+ // ScrollLock state can be retrieved with data & 1. This is currently not needed.
2757
+ let numLock = data & 2 ? true : false;
2758
+ let capsLock = data & 4 ? true : false;
2759
+ this._remoteCapsLock = capsLock;
2760
+ this._remoteNumLock = numLock;
2761
+
2762
+ return true;
2763
+ }
2764
+
2765
_handleExtendedDesktopSize() {
2766
if (this._sock.rQwait("ExtendedDesktopSize", 4)) {
2767
return false;
@@ -2785,26 +2777,18 @@ export default class RFB extends EventTargetMixin {
2777
const firstUpdate = !this._supportsSetDesktopSize;
2778
this._supportsSetDesktopSize = true;
2779
2788
- // Normally we only apply the current resize mode after a
2789
- // window resize event. However there is no such trigger on the
2790
- // initial connect. And we don't know if the server supports
2791
- // resizing until we've gotten here.
2792
- if (firstUpdate) {
2793
- this._requestRemoteResize();
2794
- }
2795
-
2780
this._sock.rQskipBytes(1); // number-of-screens
2781
this._sock.rQskipBytes(3); // padding
2782
2783
for (let i = 0; i < numberOfScreens; i += 1) {
2784
// Save the id and flags of the first screen
2785
if (i === 0) {
2802
- this._screenID = this._sock.rQshiftBytes(4); // id
2803
- this._sock.rQskipBytes(2); // x-position
2804
- this._sock.rQskipBytes(2); // y-position
2805
- this._sock.rQskipBytes(2); // width
2806
- this._sock.rQskipBytes(2); // height
2807
- this._screenFlags = this._sock.rQshiftBytes(4); // flags
2786
+ this._screenID = this._sock.rQshift32(); // id
2787
+ this._sock.rQskipBytes(2); // x-position
2788
+ this._sock.rQskipBytes(2); // y-position
2789
+ this._sock.rQskipBytes(2); // width
2790
+ this._sock.rQskipBytes(2); // height
2791
+ this._screenFlags = this._sock.rQshift32(); // flags
2792
} else {
2793
this._sock.rQskipBytes(16);
2794
}
@@ -2842,6 +2826,14 @@ export default class RFB extends EventTargetMixin {
2826
this._resize(this._FBU.width, this._FBU.height);
2827
}
2828
2829
+ // Normally we only apply the current resize mode after a
2830
+ // window resize event. However there is no such trigger on the
2831
+ // initial connect. And we don't know if the server supports
2832
+ // resizing until we've gotten here.
2833
+ if (firstUpdate) {
2834
+ this._requestRemoteResize();
2835
+ }
2836
+
2837
return true;
2838
}
2839
@@ -2937,28 +2929,22 @@ export default class RFB extends EventTargetMixin {
2929
2930
static genDES(password, challenge) {
2931
const passwordChars = password.split('').map(c => c.charCodeAt(0));
2940
- return (new DES(passwordChars)).encrypt(challenge);
2932
+ const key = legacyCrypto.importKey(
2933
+ "raw", passwordChars, { name: "DES-ECB" }, false, ["encrypt"]);
2934
+ return legacyCrypto.encrypt({ name: "DES-ECB" }, key, challenge);
2935
}
2936
}
2937
2938
// Class Methods
2939
RFB.messages = {
2940
keyEvent(sock, keysym, down) {
2947
- const buff = sock._sQ;
2948
- const offset = sock._sQlen;
2941
+ sock.sQpush8(4); // msg-type
2942
+ sock.sQpush8(down);
2943
2950
- buff[offset] = 4; // msg-type
2951
- buff[offset + 1] = down;
2944
+ sock.sQpush16(0);
2945
2953
- buff[offset + 2] = 0;
2954
- buff[offset + 3] = 0;
2946
+ sock.sQpush32(keysym);
2947
2956
- buff[offset + 4] = (keysym >> 24);
2957
- buff[offset + 5] = (keysym >> 16);
2958
- buff[offset + 6] = (keysym >> 8);
2959
- buff[offset + 7] = keysym;
2960
-
2961
- sock._sQlen += 8;
2948
sock.flush();
2949
},
2950
@@ -2972,46 +2958,28 @@ RFB.messages = {
2958
return xtScanCode;
2959
}
2960
2975
- const buff = sock._sQ;
2976
- const offset = sock._sQlen;
2977
-
2978
- buff[offset] = 255; // msg-type
2979
- buff[offset + 1] = 0; // sub msg-type
2961
+ sock.sQpush8(255); // msg-type
2962
+ sock.sQpush8(0); // sub msg-type
2963
2981
- buff[offset + 2] = (down >> 8);
2982
- buff[offset + 3] = down;
2964
+ sock.sQpush16(down);
2965
2984
- buff[offset + 4] = (keysym >> 24);
2985
- buff[offset + 5] = (keysym >> 16);
2986
- buff[offset + 6] = (keysym >> 8);
2987
- buff[offset + 7] = keysym;
2966
+ sock.sQpush32(keysym);
2967
2968
const RFBkeycode = getRFBkeycode(keycode);
2969
2991
- buff[offset + 8] = (RFBkeycode >> 24);
2992
- buff[offset + 9] = (RFBkeycode >> 16);
2993
- buff[offset + 10] = (RFBkeycode >> 8);
2994
- buff[offset + 11] = RFBkeycode;
2970
+ sock.sQpush32(RFBkeycode);
2971
2996
- sock._sQlen += 12;
2972
sock.flush();
2973
},
2974
2975
pointerEvent(sock, x, y, mask) {
3001
- const buff = sock._sQ;
3002
- const offset = sock._sQlen;
3003
-
3004
- buff[offset] = 5; // msg-type
2976
+ sock.sQpush8(5); // msg-type
2977
3006
- buff[offset + 1] = mask;
2978
+ sock.sQpush8(mask);
2979
3008
- buff[offset + 2] = x >> 8;
3009
- buff[offset + 3] = x;
2980
+ sock.sQpush16(x);
2981
+ sock.sQpush16(y);
2982
3011
- buff[offset + 4] = y >> 8;
3012
- buff[offset + 5] = y;
3013
-
3014
- sock._sQlen += 6;
2983
sock.flush();
2984
},
2985
@@ -3111,14 +3079,11 @@ RFB.messages = {
3079
},
3080
3081
clientCutText(sock, data, extended = false) {
3114
- const buff = sock._sQ;
3115
- const offset = sock._sQlen;
3116
-
3117
- buff[offset] = 6; // msg-type
3082
+ sock.sQpush8(6); // msg-type
3083
3119
- buff[offset + 1] = 0; // padding
3120
- buff[offset + 2] = 0; // padding
3121
- buff[offset + 3] = 0; // padding
3084
+ sock.sQpush8(0); // padding
3085
+ sock.sQpush8(0); // padding
3086
+ sock.sQpush8(0); // padding
3087
3088
let length;
3089
if (extended) {
@@ -3127,121 +3092,63 @@ RFB.messages = {
3092
length = data.length;
3093
}
3094
3130
- buff[offset + 4] = length >> 24;
3131
- buff[offset + 5] = length >> 16;
3132
- buff[offset + 6] = length >> 8;
3133
- buff[offset + 7] = length;
3134
-
3135
- sock._sQlen += 8;
3136
-
3137
- // We have to keep track of from where in the data we begin creating the
3138
- // buffer for the flush in the next iteration.
3139
- let dataOffset = 0;
3140
-
3141
- let remaining = data.length;
3142
- while (remaining > 0) {
3143
-
3144
- let flushSize = Math.min(remaining, (sock._sQbufferSize - sock._sQlen));
3145
- for (let i = 0; i < flushSize; i++) {
3146
- buff[sock._sQlen + i] = data[dataOffset + i];
3147
- }
3148
-
3149
- sock._sQlen += flushSize;
3150
- sock.flush();
3151
-
3152
- remaining -= flushSize;
3153
- dataOffset += flushSize;
3154
- }
3155
-
3095
+ sock.sQpush32(length);
3096
+ sock.sQpushBytes(data);
3097
+ sock.flush();
3098
},
3099
3100
setDesktopSize(sock, width, height, id, flags) {
3159
- const buff = sock._sQ;
3160
- const offset = sock._sQlen;
3101
+ sock.sQpush8(251); // msg-type
3102
3162
- buff[offset] = 251; // msg-type
3163
- buff[offset + 1] = 0; // padding
3164
- buff[offset + 2] = width >> 8; // width
3165
- buff[offset + 3] = width;
3166
- buff[offset + 4] = height >> 8; // height
3167
- buff[offset + 5] = height;
3103
+ sock.sQpush8(0); // padding
3104
3169
- buff[offset + 6] = 1; // number-of-screens
3170
- buff[offset + 7] = 0; // padding
3105
+ sock.sQpush16(width);
3106
+ sock.sQpush16(height);
3107
+
3108
+ sock.sQpush8(1); // number-of-screens
3109
+
3110
+ sock.sQpush8(0); // padding
3111
3112
// screen array
3173
- buff[offset + 8] = id >> 24; // id
3174
- buff[offset + 9] = id >> 16;
3175
- buff[offset + 10] = id >> 8;
3176
- buff[offset + 11] = id;
3177
- buff[offset + 12] = 0; // x-position
3178
- buff[offset + 13] = 0;
3179
- buff[offset + 14] = 0; // y-position
3180
- buff[offset + 15] = 0;
3181
- buff[offset + 16] = width >> 8; // width
3182
- buff[offset + 17] = width;
3183
- buff[offset + 18] = height >> 8; // height
3184
- buff[offset + 19] = height;
3185
- buff[offset + 20] = flags >> 24; // flags
3186
- buff[offset + 21] = flags >> 16;
3187
- buff[offset + 22] = flags >> 8;
3188
- buff[offset + 23] = flags;
3189
-
3190
- sock._sQlen += 24;
3113
+ sock.sQpush32(id);
3114
+ sock.sQpush16(0); // x-position
3115
+ sock.sQpush16(0); // y-position
3116
+ sock.sQpush16(width);
3117
+ sock.sQpush16(height);
3118
+ sock.sQpush32(flags);
3119
+
3120
sock.flush();
3121
},
3122
3123
clientFence(sock, flags, payload) {
3195
- const buff = sock._sQ;
3196
- const offset = sock._sQlen;
3197
-
3198
- buff[offset] = 248; // msg-type
3199
-
3200
- buff[offset + 1] = 0; // padding
3201
- buff[offset + 2] = 0; // padding
3202
- buff[offset + 3] = 0; // padding
3203
-
3204
- buff[offset + 4] = flags >> 24; // flags
3205
- buff[offset + 5] = flags >> 16;
3206
- buff[offset + 6] = flags >> 8;
3207
- buff[offset + 7] = flags;
3124
+ sock.sQpush8(248); // msg-type
3125
3209
- const n = payload.length;
3126
+ sock.sQpush8(0); // padding
3127
+ sock.sQpush8(0); // padding
3128
+ sock.sQpush8(0); // padding
3129
3211
- buff[offset + 8] = n; // length
3130
+ sock.sQpush32(flags);
3131
3213
- for (let i = 0; i < n; i++) {
3214
- buff[offset + 9 + i] = payload.charCodeAt(i);
3215
- }
3132
+ sock.sQpush8(payload.length);
3133
+ sock.sQpushString(payload);
3134
3217
- sock._sQlen += 9 + n;
3135
sock.flush();
3136
},
3137
3138
enableContinuousUpdates(sock, enable, x, y, width, height) {
3222
- const buff = sock._sQ;
3223
- const offset = sock._sQlen;
3224
-
3225
- buff[offset] = 150; // msg-type
3226
- buff[offset + 1] = enable; // enable-flag
3227
-
3228
- buff[offset + 2] = x >> 8; // x
3229
- buff[offset + 3] = x;
3230
- buff[offset + 4] = y >> 8; // y
3231
- buff[offset + 5] = y;
3232
- buff[offset + 6] = width >> 8; // width
3233
- buff[offset + 7] = width;
3234
- buff[offset + 8] = height >> 8; // height
3235
- buff[offset + 9] = height;
3236
-
3237
- sock._sQlen += 10;
3139
+ sock.sQpush8(150); // msg-type
3140
+
3141
+ sock.sQpush8(enable);
3142
+
3143
+ sock.sQpush16(x);
3144
+ sock.sQpush16(y);
3145
+ sock.sQpush16(width);
3146
+ sock.sQpush16(height);
3147
+
3148
sock.flush();
3149
},
3150
3151
pixelFormat(sock, depth, trueColor) {
3242
- const buff = sock._sQ;
3243
- const offset = sock._sQlen;
3244
-
3152
let bpp;
3153
3154
if (depth > 16) {
@@ -3254,100 +3161,69 @@ RFB.messages = {
3161
3162
const bits = Math.floor(depth/3);
3163
3257
- buff[offset] = 0; // msg-type
3258
-
3259
- buff[offset + 1] = 0; // padding
3260
- buff[offset + 2] = 0; // padding
3261
- buff[offset + 3] = 0; // padding
3164
+ sock.sQpush8(0); // msg-type
3165
3263
- buff[offset + 4] = bpp; // bits-per-pixel
3264
- buff[offset + 5] = depth; // depth
3265
- buff[offset + 6] = 0; // little-endian
3266
- buff[offset + 7] = trueColor ? 1 : 0; // true-color
3166
+ sock.sQpush8(0); // padding
3167
+ sock.sQpush8(0); // padding
3168
+ sock.sQpush8(0); // padding
3169
3268
- buff[offset + 8] = 0; // red-max
3269
- buff[offset + 9] = (1 << bits) - 1; // red-max
3170
+ sock.sQpush8(bpp);
3171
+ sock.sQpush8(depth);
3172
+ sock.sQpush8(0); // little-endian
3173
+ sock.sQpush8(trueColor ? 1 : 0);
3174
3271
- buff[offset + 10] = 0; // green-max
3272
- buff[offset + 11] = (1 << bits) - 1; // green-max
3175
+ sock.sQpush16((1 << bits) - 1); // red-max
3176
+ sock.sQpush16((1 << bits) - 1); // green-max
3177
+ sock.sQpush16((1 << bits) - 1); // blue-max
3178
3274
- buff[offset + 12] = 0; // blue-max
3275
- buff[offset + 13] = (1 << bits) - 1; // blue-max
3179
+ sock.sQpush8(bits * 0); // red-shift
3180
+ sock.sQpush8(bits * 1); // green-shift
3181
+ sock.sQpush8(bits * 2); // blue-shift
3182
3277
- buff[offset + 14] = bits * 0; // red-shift
3278
- buff[offset + 15] = bits * 1; // green-shift
3279
- buff[offset + 16] = bits * 2; // blue-shift
3183
+ sock.sQpush8(0); // padding
3184
+ sock.sQpush8(0); // padding
3185
+ sock.sQpush8(0); // padding
3186
3281
- buff[offset + 17] = 0; // padding
3282
- buff[offset + 18] = 0; // padding
3283
- buff[offset + 19] = 0; // padding
3284
-
3285
- sock._sQlen += 20;
3187
sock.flush();
3188
},
3189
3190
clientEncodings(sock, encodings) {
3290
- const buff = sock._sQ;
3291
- const offset = sock._sQlen;
3292
-
3293
- buff[offset] = 2; // msg-type
3294
- buff[offset + 1] = 0; // padding
3191
+ sock.sQpush8(2); // msg-type
3192
3296
- buff[offset + 2] = encodings.length >> 8;
3297
- buff[offset + 3] = encodings.length;
3193
+ sock.sQpush8(0); // padding
3194
3299
- let j = offset + 4;
3195
+ sock.sQpush16(encodings.length);
3196
for (let i = 0; i < encodings.length; i++) {
3301
- const enc = encodings[i];
3302
- buff[j] = enc >> 24;
3303
- buff[j + 1] = enc >> 16;
3304
- buff[j + 2] = enc >> 8;
3305
- buff[j + 3] = enc;
3306
-
3307
- j += 4;
3197
+ sock.sQpush32(encodings[i]);
3198
}
3199
3310
- sock._sQlen += j - offset;
3200
sock.flush();
3201
},
3202
3203
fbUpdateRequest(sock, incremental, x, y, w, h) {
3315
- const buff = sock._sQ;
3316
- const offset = sock._sQlen;
3317
-
3204
if (typeof(x) === "undefined") { x = 0; }
3205
if (typeof(y) === "undefined") { y = 0; }
3206
3321
- buff[offset] = 3; // msg-type
3322
- buff[offset + 1] = incremental ? 1 : 0;
3323
-
3324
- buff[offset + 2] = (x >> 8) & 0xFF;
3325
- buff[offset + 3] = x & 0xFF;
3326
-
3327
- buff[offset + 4] = (y >> 8) & 0xFF;
3328
- buff[offset + 5] = y & 0xFF;
3207
+ sock.sQpush8(3); // msg-type
3208
3330
- buff[offset + 6] = (w >> 8) & 0xFF;
3331
- buff[offset + 7] = w & 0xFF;
3209
+ sock.sQpush8(incremental ? 1 : 0);
3210
3333
- buff[offset + 8] = (h >> 8) & 0xFF;
3334
- buff[offset + 9] = h & 0xFF;
3211
+ sock.sQpush16(x);
3212
+ sock.sQpush16(y);
3213
+ sock.sQpush16(w);
3214
+ sock.sQpush16(h);
3215
3336
- sock._sQlen += 10;
3216
sock.flush();
3217
},
3218
3219
xvpOp(sock, ver, op) {
3341
- const buff = sock._sQ;
3342
- const offset = sock._sQlen;
3220
+ sock.sQpush8(250); // msg-type
3221
3344
- buff[offset] = 250; // msg-type
3345
- buff[offset + 1] = 0; // padding
3222
+ sock.sQpush8(0); // padding
3223
3347
- buff[offset + 2] = ver;
3348
- buff[offset + 3] = op;
3224
+ sock.sQpush8(ver);
3225
+ sock.sQpush8(op);
3226
3350
- sock._sQlen += 4;
3227
sock.flush();
3228
}
3229
};
public/novnc/core/util/cursor.js
+3
-1
@@ -69,7 +69,9 @@ export default class Cursor {
69
this._target.removeEventListener('mousemove', this._eventHandlers.mousemove, options);
70
this._target.removeEventListener('mouseup', this._eventHandlers.mouseup, options);
71
72
- document.body.removeChild(this._canvas);
72
+ if (document.contains(this._canvas)) {
73
+ document.body.removeChild(this._canvas);
74
+ }
75
}
76
77
this._target = null;
public/novnc/core/websock.js
+70
-58
@@ -94,27 +94,7 @@ export default class Websock {
94
return "unknown";
95
}
96
97
- get sQ() {
98
- return this._sQ;
99
- }
100
-
101
- get rQ() {
102
- return this._rQ;
103
- }
104
-
105
- get rQi() {
106
- return this._rQi;
107
- }
108
-
109
- set rQi(val) {
110
- this._rQi = val;
111
- }
112
-
97
// Receive Queue
114
- get rQlen() {
115
- return this._rQlen - this._rQi;
116
- }
117
-
98
rQpeek8() {
99
return this._rQ[this._rQi];
100
}
@@ -141,42 +121,47 @@ export default class Websock {
121
for (let byte = bytes - 1; byte >= 0; byte--) {
122
res += this._rQ[this._rQi++] << (byte * 8);
123
}
144
- return res;
124
+ return res >>> 0;
125
}
126
127
rQshiftStr(len) {
148
- if (typeof(len) === 'undefined') { len = this.rQlen; }
128
let str = "";
129
// Handle large arrays in steps to avoid long strings on the stack
130
for (let i = 0; i < len; i += 4096) {
152
- let part = this.rQshiftBytes(Math.min(4096, len - i));
131
+ let part = this.rQshiftBytes(Math.min(4096, len - i), false);
132
str += String.fromCharCode.apply(null, part);
133
}
134
return str;
135
}
136
158
- rQshiftBytes(len) {
159
- if (typeof(len) === 'undefined') { len = this.rQlen; }
137
+ rQshiftBytes(len, copy=true) {
138
this._rQi += len;
161
- return new Uint8Array(this._rQ.buffer, this._rQi - len, len);
139
+ if (copy) {
140
+ return this._rQ.slice(this._rQi - len, this._rQi);
141
+ } else {
142
+ return this._rQ.subarray(this._rQi - len, this._rQi);
143
+ }
144
}
145
146
rQshiftTo(target, len) {
165
- if (len === undefined) { len = this.rQlen; }
147
// TODO: make this just use set with views when using a ArrayBuffer to store the rQ
148
target.set(new Uint8Array(this._rQ.buffer, this._rQi, len));
149
this._rQi += len;
150
}
151
171
- rQslice(start, end = this.rQlen) {
172
- return new Uint8Array(this._rQ.buffer, this._rQi + start, end - start);
152
+ rQpeekBytes(len, copy=true) {
153
+ if (copy) {
154
+ return this._rQ.slice(this._rQi, this._rQi + len);
155
+ } else {
156
+ return this._rQ.subarray(this._rQi, this._rQi + len);
157
+ }
158
}
159
160
// Check to see if we must wait for 'num' bytes (default to FBU.bytes)
161
// to be available in the receive queue. Return true if we need to
162
// wait (and possibly print a debug message), otherwise false.
163
rQwait(msg, num, goback) {
179
- if (this.rQlen < num) {
164
+ if (this._rQlen - this._rQi < num) {
165
if (goback) {
166
if (this._rQi < goback) {
167
throw new Error("rQwait cannot backup " + goback + " bytes");
@@ -190,21 +175,56 @@ export default class Websock {
175
176
// Send Queue
177
178
+ sQpush8(num) {
179
+ this._sQensureSpace(1);
180
+ this._sQ[this._sQlen++] = num;
181
+ }
182
+
183
+ sQpush16(num) {
184
+ this._sQensureSpace(2);
185
+ this._sQ[this._sQlen++] = (num >> 8) & 0xff;
186
+ this._sQ[this._sQlen++] = (num >> 0) & 0xff;
187
+ }
188
+
189
+ sQpush32(num) {
190
+ this._sQensureSpace(4);
191
+ this._sQ[this._sQlen++] = (num >> 24) & 0xff;
192
+ this._sQ[this._sQlen++] = (num >> 16) & 0xff;
193
+ this._sQ[this._sQlen++] = (num >> 8) & 0xff;
194
+ this._sQ[this._sQlen++] = (num >> 0) & 0xff;
195
+ }
196
+
197
+ sQpushString(str) {
198
+ let bytes = str.split('').map(chr => chr.charCodeAt(0));
199
+ this.sQpushBytes(new Uint8Array(bytes));
200
+ }
201
+
202
+ sQpushBytes(bytes) {
203
+ for (let offset = 0;offset < bytes.length;) {
204
+ this._sQensureSpace(1);
205
+
206
+ let chunkSize = this._sQbufferSize - this._sQlen;
207
+ if (chunkSize > bytes.length - offset) {
208
+ chunkSize = bytes.length - offset;
209
+ }
210
+
211
+ this._sQ.set(bytes.subarray(offset, offset + chunkSize), this._sQlen);
212
+ this._sQlen += chunkSize;
213
+ offset += chunkSize;
214
+ }
215
+ }
216
+
217
flush() {
218
if (this._sQlen > 0 && this.readyState === 'open') {
195
- this._websocket.send(this._encodeMessage());
219
+ this._websocket.send(new Uint8Array(this._sQ.buffer, 0, this._sQlen));
220
this._sQlen = 0;
221
}
222
}
223
200
- send(arr) {
201
- this._sQ.set(arr, this._sQlen);
202
- this._sQlen += arr.length;
203
- this.flush();
204
- }
205
-
206
- sendString(str) {
207
- this.send(str.split('').map(chr => chr.charCodeAt(0)));
224
+ _sQensureSpace(bytes) {
225
+ if (this._sQbufferSize - this._sQlen < bytes) {
226
+ this.flush();
227
+ }
228
}
229
230
// Event Handlers
@@ -283,17 +303,12 @@ export default class Websock {
303
}
304
305
// private methods
286
- _encodeMessage() {
287
- // Put in a binary arraybuffer
288
- // according to the spec, you can send ArrayBufferViews with the send method
289
- return new Uint8Array(this._sQ.buffer, 0, this._sQlen);
290
- }
306
307
// We want to move all the unread data to the start of the queue,
308
// e.g. compacting.
309
// The function also expands the receive que if needed, and for
310
// performance reasons we combine these two actions to avoid
296
- // unneccessary copying.
311
+ // unnecessary copying.
312
_expandCompactRQ(minFit) {
313
// if we're using less than 1/8th of the buffer even with the incoming bytes, compact in place
314
// instead of resizing
@@ -309,7 +324,7 @@ export default class Websock {
324
// we don't want to grow unboundedly
325
if (this._rQbufferSize > MAX_RQ_GROW_SIZE) {
326
this._rQbufferSize = MAX_RQ_GROW_SIZE;
312
- if (this._rQbufferSize - this.rQlen < minFit) {
327
+ if (this._rQbufferSize - (this._rQlen - this._rQi) < minFit) {
328
throw new Error("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit");
329
}
330
}
@@ -327,25 +342,22 @@ export default class Websock {
342
}
343
344
// push arraybuffer values onto the end of the receive que
330
- _DecodeMessage(data) {
331
- const u8 = new Uint8Array(data);
345
+ _recvMessage(e) {
346
+ if (this._rQlen == this._rQi) {
347
+ // All data has now been processed, this means we
348
+ // can reset the receive queue.
349
+ this._rQlen = 0;
350
+ this._rQi = 0;
351
+ }
352
+ const u8 = new Uint8Array(e.data);
353
if (u8.length > this._rQbufferSize - this._rQlen) {
354
this._expandCompactRQ(u8.length);
355
}
356
this._rQ.set(u8, this._rQlen);
357
this._rQlen += u8.length;
337
- }
358
339
- _recvMessage(e) {
340
- this._DecodeMessage(e.data);
341
- if (this.rQlen > 0) {
359
+ if (this._rQlen - this._rQi > 0) {
360
this._eventHandlers.message();
343
- if (this._rQlen == this._rQi) {
344
- // All data has now been processed, this means we
345
- // can reset the receive queue.
346
- this._rQlen = 0;
347
- this._rQi = 0;
348
- }
361
} else {
362
Log.Debug("Ignoring empty message");
363
}
public/novnc/package.json
new
+1
@@ -0,0 +1 @@
1
+{ "version": "1.5.0" }
\ No newline at end of file