'use strict'; /** BSD-2-Clause license * * Copyright (c) 2018-2023 NST , sss , smake . * */ /** * 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 = ''; } 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 = ''; } 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); } }