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