summaryrefslogtreecommitdiff
path: root/www/js/clipboard.js
diff options
context:
space:
mode:
authorsss <sss@dark-alexandr.net>2023-01-17 00:38:19 +0300
committersss <sss@dark-alexandr.net>2023-01-17 00:38:19 +0300
commitcc3f33db7a8d3c4ad373e646b199808e01bc5d9b (patch)
treeec09d690c7656ab5f2cc72607e05fb359c24d8b2 /www/js/clipboard.js
added webrdp public code
Diffstat (limited to 'www/js/clipboard.js')
-rw-r--r--www/js/clipboard.js535
1 files changed, 535 insertions, 0 deletions
diff --git a/www/js/clipboard.js b/www/js/clipboard.js
new file mode 100644
index 0000000..77ea154
--- /dev/null
+++ b/www/js/clipboard.js
@@ -0,0 +1,535 @@
+'use strict';
+/** BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>, smake <smake at ya dot ru>.
+ *
+ */
+
+/**
+ * Main clipboard class, that dispatch clipboard events and handle their status
+ */
+
+
+class ClipboardHandler {
+ constructor(rdp) {
+ this.rdp = rdp;
+ this.sock = this.rdp.sock;
+ this.filelistIn = [];
+ this.filelistOut = [];
+ this.textIn = null;
+ this.textOut = null;
+ this.update();
+ return this;
+ }
+
+ get istransferring() {
+ /*
+ * check for any file is currently transferring
+ * returns strings "in", "out" or bool false
+ */
+ if (this.filelistOut.length > 0) {
+ for (var i = 0, f; f = this.filelistOut[i]; i++) {
+ if (f.isTransferring){
+ return "out";
+ }
+ }
+ }
+ if (this.filelistIn.length > 0) {
+ for (var i = 0, f; f = this.filelistIn[i]; i++) {
+ if (f.isTransferring){
+ return "in";
+ }
+ }
+ }
+ return false;
+ }
+
+ ClipoboardFileGet(fid) {
+ var ft = this.filelistIn[fid];
+ if (!ft) {
+ return;
+ }
+ if (this.istransferring) {
+ console.log("Transfer in progress, please await");
+ return;
+ }
+ if (this.sock.readyState == this.sock.OPEN) {
+ this.rdp.log.debug('requesting file transfer', 1);
+ var buf = ft.nextChunkRequest;
+ // console.log(buf.toHexdump());
+ this.sock.send(buf);
+ };
+
+ }
+
+ ClipoboardSendFilelist(files) {
+ if (!files.length) {
+ console.log("There in now escape(files)")
+ return;
+ }
+ if (this.istransferring) {
+ var msg = lables.files_in_transfer;
+ this.rdp.fireEvent('alert', msg, 2);
+ console.log("Transfer in progress, please await");
+ return;
+ }
+ this.filelistOut = [];
+ this.filelistIn = [];
+ for (var i = 0, f; f = files[i]; i++) {
+ /*
+ * Bug in microsoft RDP server
+ * https://support.microsoft.com/en-us/help/2258090/copying-files-larger-than-2-gb-over-a-remote-desktop-services-or-termi
+ */
+ if (f.size < 2147483647) {
+ var j = this.filelistOut.length
+ var ft_entry = new FileEntry(this.rdp,f,j);
+ this.filelistOut[j] = ft_entry;
+ this.checkFileReadable(ft_entry);
+ } else {
+ var msg = f.name + ": " + lables.file_lager_then_2g;
+ this.rdp.fireEvent('alert', msg, 2);
+ }
+ }
+
+ if (this.filelistOut.length == 0) {
+ // popUpMessage
+ console.log("There in now escape(files)")
+ return;
+ }
+
+ if (this.sock.readyState == this.sock.OPEN) {
+ this.rdp.log.debug('updating remote clipbord status', 1);
+ var buf = new ArrayBuffer(6);
+ var c = new Uint32Array(buf, 0, 1);
+ var count = new Uint8Array(buf, 4, 1);
+ var fmt = new Uint8Array(buf, 5, 1);
+ c[0] = 6; // ws_in_clipbrd_changed
+ count[0] = this.filelistOut.length;
+ fmt[0] = 3;
+ // console.log(buf.toHexdump());
+ this.sock.send(buf);
+ };
+ this.update();
+
+ }
+
+ getFile() {
+ if (this.rdp.sock.readyState == this.rdp.sock.OPEN) {
+ this.rdp.log.debug('requesting file transfer', 1);
+ var buf = this.nextChunkRequest;
+ console.log(buf.toHexdump());
+ this.rdp.sock.send(buf);
+ };
+ }
+
+ resetUi() {
+ var cbdinimg = document.querySelector("div#clipboardin > img");
+ cbdinimg.style.backgroundColor = "#eee";
+ cbdinimg.src = "images/cbd_empty.png";
+
+ var new_element = cbdinimg.cloneNode(true);
+ cbdinimg.parentNode.replaceChild(new_element, cbdinimg);
+ var cbdinimg = new_element;
+
+ var cbdoutimg = document.querySelector("div#clipboardout > img");
+ cbdoutimg.style.backgroundColor = "#eee";
+ cbdoutimg.src = "images/cbd_empty_out.png";
+
+ var new_element = cbdoutimg.cloneNode(true);
+ cbdoutimg.parentNode.replaceChild(new_element, cbdoutimg);
+ var cbdoutimg = new_element;
+
+ var pos = this.rdp.canvas.getPosition();
+ var size = this.rdp.canvas.getSize();
+
+ var cbdcontent = document.getElementById('cbdcontent');
+ cbdcontent.innerHTML = "";
+ cbdcontent.setStyle('max-height', size.y);
+ cbdcontent.setStyle('overflow-y', "auto");
+ cbdcontent.style.visibility = "hidden";
+ }
+
+ update() {
+ /*
+ * Re-draw
+ */
+
+ this.resetUi();
+ var cbdinimg = document.querySelector("div#clipboardin > img");
+ var cbdoutimg = document.querySelector("div#clipboardout > img");
+ var cbdcontent = document.getElementById('cbdcontent');
+ cbdcontent.addEventListener("mouseleave", function(evt) {
+ evt.target.style.visibility = "hidden";
+ });
+
+
+ cbdoutimg.cbd = this;
+ cbdoutimg.addEventListener("click", function(evt) {
+ var cbdcontent = document.getElementById('cbdcontent');
+ var cbd = evt.target.cbd;
+ if (cbd.istransferring) {
+ return;
+ }
+ cbd.filelistIn = [];
+ cbd.filelistOut = [];
+ cbd.textIn = null;
+ cbd.textOut = null;
+ cbd.update();
+ navigator.clipboard.readText().then(text => {
+ rdp.cbd.textOut = text;
+ rdp.cbd.update();
+ }).catch(err => {
+ cbdcontent.style.visibility = "visible";
+ var input = document.createElement('input');
+ cbdcontent.appendChild(input);
+ input.cbd = cbd;
+ input.addEventListener("paste", function(evt) {
+ var cbd = evt.target.cbd;
+ cbd.textOut = "" + evt.clipboardData.getData("text");
+ cbd.update();
+ });
+ input.focus();
+ console.error('Failed to read clipboard contents: ', err);
+ });
+
+ });
+
+ if (this.filelistIn.length > 0) {
+ if (this.istransferring == "in") {
+ cbdinimg.src = "images/cbd_wait.png";
+ } else {
+ cbdinimg.src = "images/cbd_files.png";
+ }
+
+ this.iconAddListners(cbdinimg);
+
+ for (var i =0; i < this.filelistIn.length; i++) {
+ var file = this.filelistIn[i];
+ cbdcontent.innerHTML += file.uiElement;
+ }
+
+ return;
+ } else if (this.filelistOut.length > 0) {
+ if (this.istransferring == "out") {
+ cbdoutimg.src = "images/cbd_wait.png";
+ } else {
+ cbdoutimg.src = "images/cbd_files.png";
+ }
+
+ this.iconAddListners(cbdoutimg);
+
+ var html = "";
+ for (var i =0; i < this.filelistOut.length; i++) {
+ var file = this.filelistOut[i];
+ cbdcontent.innerHTML += file.uiElement
+ }
+ } else if (this.textIn) {
+ cbdinimg.src = "images/cbd_text.png";
+ cbdoutimg.src = "images/cbd_empty_out.png";
+
+ this.iconAddListners(cbdinimg);
+
+ cbdcontent.innerHTML = '<input type="text" value="' + this.textIn + '">';
+ } else if (this.textOut) {
+ cbdinimg.src = "images/cbd_empty.png";
+ cbdoutimg.src = "images/cbd_text.png";
+
+ this.iconAddListners(cbdoutimg);
+ if (this.sock.readyState == this.sock.OPEN) {
+ this.rdp.log.debug('updating remote clipbord status', 1);
+ var buf = new ArrayBuffer(6);
+ var c = new Uint32Array(buf, 0, 1);
+ var count = new Uint8Array(buf, 4, 1);
+ var fmt = new Uint8Array(buf, 5, 1);
+ c[0] = 6; // ws_in_clipbrd_changed
+ count[0] = 1;
+ fmt[0] = 2;
+ // console.log(buf.toHexdump());
+ this.sock.send(buf);
+ };
+
+ cbdcontent.innerHTML = '<input type="text" value="' + this.textOut + '">';
+ } else {
+ cbdinimg.src = "images/cbd_empty.png";
+ cbdoutimg.src = "images/cbd_empty_out.png";
+ }
+
+ }
+
+ iconAddListners (obj) {
+ var cbdcontent = document.getElementById('cbdcontent');
+ obj.addEventListener("mouseover", function(evt) {
+ var cbdcontent = document.getElementById('cbdcontent');
+ cbdcontent.style.visibility = "visible";
+ evt.target.style.backgroundColor = "darkgreen";
+ });
+ obj.addEventListener("mouseout", function(evt) {
+ var fl = document.getElementById('cbdcontent');
+ evt.target.style.backgroundColor = "#eee";
+ });
+
+ }
+
+ ClipbrdInfoParse(fmts) {
+ var clipboardimg = document.querySelector("div#clipboardin > img");
+ if (this.istransferring) {
+ console.log("Transfer in progress, please await");
+ return;
+ }
+ var fmt = 0;
+ if (fmts.length > 1) {
+ for (var i = 0; i < fmts.length; ++i) {
+ if (fmts[i] == 3) {
+ fmt = 3;
+ }
+ }
+ }
+ clipboardimg.style.backgroundColor = "darkgreen";
+ switch (fmt) {
+ case 0: // clip_format_raw
+ case 1: // clip_format_text
+ case 2: // clip_format_utf8
+ if (this.sock.readyState == this.sock.OPEN) {
+ this.rdp.log.debug('getting raw buffer', 1);
+ var buf = new ArrayBuffer(5);
+ var c = new Uint32Array(buf, 0, 1);
+ var t = new Uint8Array(buf, 4, 1);
+ c[0] = 8; // ws_in_clipbrd_data_request
+ t[0] = 2;
+ this.sock.send(buf);
+ clipboardimg.src = "images/cbd_text.png";
+ setTimeout(function () {
+ clipboardimg.style.backgroundColor = "#eee";
+ }, 100);
+ };
+ break;
+ case 3: // clip_format_FileGroupDescriptorW
+ if (this.sock.readyState == this.sock.OPEN) {
+ this.rdp.log.debug('getting file list', 1);
+ var buf = new ArrayBuffer(5);
+ var c = new Uint32Array(buf, 0, 1);
+ var t = new Uint8Array(buf, 4, 1);
+ c[0] = 8; // ws_in_clipbrd_data_request
+ t[0] = 3;
+ this.sock.send(buf);
+ clipboardimg.src = "images/cbd_files.png";
+ setTimeout(function () {
+ clipboardimg.style.backgroundColor = "#eee";
+ }, 100);
+ };
+ break;
+ default:
+ this.innerHTML = lables.cbd_empty;
+ break;
+ }
+ return true;
+ }
+
+ sendClipboardInfo (type) {
+ if (this.istransferring) {
+ console.log("Transfer in progress, please await");
+ return;
+ }
+ switch(type)
+ {
+ case 0:
+ case 1:
+ case 2:
+ {
+ if (this.sock.readyState != this.sock.OPEN) {
+ break;
+ }
+ // if (!this.textOut) {
+ // return;
+ // }
+ var text = new TextEncoder().encode(this.textOut);
+
+ // var buf = new ArrayBuffer(4 + 1 + 4 + text.byteLength);
+ var buf = new ArrayBuffer(4 + 1 + text.byteLength);
+ var bufArr = new Uint8Array(buf);
+ var c = new Uint32Array(buf, 0, 1);
+ var type = new Uint8Array(buf, 4, 1);
+ // var size = new Uint8Array(buf, 5, 1);
+ var data = new Uint8Array(buf, 5);
+ var tmpSize = new Uint32Array(1);
+ c[0] = 7; // ws_in_clipbrd_data
+ type[0] = 2;
+ tmpSize[0] = text.byteLength
+ // size.set(tmpSize);
+ data.set(text);
+ console.log(buf.toHexdump());
+ this.sock.send(buf);
+ }
+ break;
+ case 3:
+ {
+
+ if (this.sock.readyState != this.sock.OPEN) {
+ break;
+ }
+ if(this.filelistOut.length == 0) {
+ console.log("no files in buffer");
+ break;
+ }
+ var ftlenght = 0;
+ for (var i =0; i < this.filelistOut.length; i++) {
+ var f = this.filelistOut[i];
+ ftlenght += f.raw_ft.length;
+ };
+
+ var ftOffset = 4 + 2 + 1;
+ var buf = new ArrayBuffer(ftOffset + ftlenght);
+ var bufArr = new Uint8Array(buf);
+ var c = new Uint32Array(buf, 0, 1);
+ var type = new Uint8Array(buf, 4, 1);
+ var count = new Uint16Array(1);
+ c[0] = 7; // ws_in_clipbrd_data
+ type[0] = 3;
+ count[0] = this.filelistOut.length;
+
+ // because of buf migth be not multiple Uint16Array, which throw exeption in js
+ bufArr.set(new Uint8Array(count.buffer), 4 + 1);
+
+ for (var i =0; i < this.filelistOut.length; i++) {
+ var f = this.filelistOut[i];
+ bufArr.set(f.raw_ft, ftOffset);
+ ftOffset += f.raw_ft.length;
+ };
+
+ // console.log(buf.toHexdump());
+ this.sock.send(buf);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ reciveClipboardInfo(data) {
+ /* type is:
+ * typedef enum
+ * {
+ * clip_format_unsupported = -1,
+ * clip_format_raw,
+ * clip_format_text,
+ * clip_format_unicode,
+ * clip_format_file_list
+ * }wrdp_enum_clip_format;
+ * NOTE: must be kept in sync with core
+ */
+ if (this.istransferring) {
+ console.log("Transfer in progress, please await");
+ return;
+ }
+ var type = new Uint8Array(data, 4, 1);
+ this.filelistIn = [];
+ this.filelistOut = [];
+ this.textIn = null;
+ this.textOut = null;
+ switch(type[0])
+ {
+ case 0:
+ case 1:
+ {
+ /* text is most probably in local rdp server
+ * endocing */
+ var buf = new Uint8Array(data, 5);
+ //var text = new TextDecoder("utf-8").decode(buf);
+ //Viva la hardcoding encoding
+ var text = new TextDecoder("windows-1251").decode(buf.filter(v => v != 0));
+ this.textIn = text;
+ navigator.clipboard.writeText(text).then(function() {
+ /* clipboard successfully set */
+ // console.log("buffer setted")
+ return;
+ }, function() {
+ /* clipboard write failed */
+ var msg = lables.files_in_transfer;
+ this.rdp.fireEvent('alert', msg, 2);
+ console.log("buffer not setted")
+ return;
+ });
+ this.update();
+ }
+ break;
+ case 2:
+ {
+ var buf = new Uint8Array(data, 5);
+ var text = new TextDecoder("utf-8").decode(buf.filter(v => v != 0));
+ this.textIn = text;
+ navigator.clipboard.writeText(text).then(function() {
+ /* clipboard successfully set */
+ // console.log("buffer setted");
+ return;
+ }, function() {
+ /* clipboard write failed */
+ console.log("buffer not setted")
+ return;
+ });
+ this.update();
+ }
+ break;
+ case 3:
+ {
+ /*
+ * typedef struct
+ * {
+ * uint16_t filename_len;
+ * uint32_t file_id;
+ * uint64_t file_size, file_offset;
+ * }wrdp_backend_ft_list_entry;
+ * NOTE: must be kept in sync with core
+ * NOTE: file_offset is omited here
+ */
+ var count = new Uint16Array(data.slice(5,7));
+ var list = data.slice(7);
+ var pos = 0;
+ for (var i = 0; i < count[0]; ++i)
+ {
+ var name_len = new Uint16Array(list.slice(pos, pos + 2));
+ var entry_size = 2 + 4 + 8 + name_len[0]
+ var id = new Uint32Array(list.slice(pos + 2, pos + 2 + 4))[0];
+ var ft_entry = new Uint8Array(list.slice(pos, pos + entry_size));
+ /* "name" in utf8 encoding */
+ this.filelistIn[id] = new FileEntry(this.rdp, ft_entry);
+ pos += entry_size;
+ this.update();
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ }
+
+ checkFileReadable(fe) {
+ var offset = 0;
+ var req_size = 8;
+
+ var blob = fe.file.slice(offset, offset + req_size);
+ var reader = new FileReader();
+ reader.fe = fe;
+ reader.onerror = function(e) {
+ var file = e.target.fe;
+ var msg = file.name + ": " + e.target.error.message;
+ file.rdp.cbd.filelistOut.pop(file);
+ file.rdp.fireEvent('alert', msg);
+ file.rdp.cbd.update();
+ if (file.rdp.sock.readyState == file.rdp.sock.OPEN) {
+ file.rdp.log.debug('updating remote clipbord status', 1);
+ var buf = new ArrayBuffer(6);
+ var c = new Uint32Array(buf, 0, 1);
+ var count = new Uint8Array(buf, 4, 1);
+ var fmt = new Uint8Array(buf, 5, 1);
+ c[0] = 6; // ws_in_clipbrd_changed
+ count[0] = file.rdp.cbd.filelistOut.length;
+ fmt[0] = 3;
+ // console.log(buf.toHexdump());
+ file.rdp.sock.send(buf);
+ };
+ };
+ reader.readAsArrayBuffer(blob);
+ }
+
+}