diff --git a/agents/meshcore.js b/agents/meshcore.js index 449270b0..5881e3b0 100644 --- a/agents/meshcore.js +++ b/agents/meshcore.js @@ -1865,8 +1865,7 @@ function onTunnelData(data) { if (this.httprequest.state == 0) { // Check if this is a relay connection if ((data == 'c') || (data == 'cr')) { this.httprequest.state = 1; /*sendConsoleText("Tunnel #" + this.httprequest.index + " now active", this.httprequest.sessionid);*/ } - } - else { + } else { // Handle tunnel data if (this.httprequest.protocol == 0) { // 1 = Terminal (admin), 2 = Desktop, 5 = Files, 6 = PowerShell (admin), 7 = Plugin Data Exchange, 8 = Terminal (user), 9 = PowerShell (user), 10 = FileTransfer // Take a look at the protocol @@ -2648,7 +2647,7 @@ function onTunnelData(data) { var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path; this.httprequest.uploadFilePath = filepath; this.httprequest.uploadFileSize = 0; - try { this.httprequest.uploadFile = fs.openSync(filepath, 'wbN'); } catch (e) { this.write(Buffer.from(JSON.stringify({ action: 'uploaderror', reqid: cmd.reqid }))); break; } + try { this.httprequest.uploadFile = fs.openSync(filepath, cmd.append ? 'abN' : 'wbN'); } catch (e) { this.write(Buffer.from(JSON.stringify({ action: 'uploaderror', reqid: cmd.reqid }))); break; } this.httprequest.uploadFileid = cmd.reqid; if (this.httprequest.uploadFile) { this.write(Buffer.from(JSON.stringify({ action: 'uploadstart', reqid: this.httprequest.uploadFileid }))); } break; @@ -2681,6 +2680,15 @@ function onTunnelData(data) { } break; } + case 'uploadhash': + { + // Hash a file + var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path; + var h = null; + try { h = getSHA384FileHash(filepath); } catch (ex) { sendConsoleText(ex); } + this.write(Buffer.from(JSON.stringify({ action: 'uploadhash', reqid: cmd.reqid, path: cmd.path, name: cmd.name, tag: cmd.tag, hash: (h ? h.toString('hex') : null) }))); + break + } case 'copy': { // Copy a bunch of files from scpath to dspath diff --git a/emails/translations/device-notify_nl.html b/emails/translations/device-notify_nl.html index 89c491a7..7257df35 100644 --- a/emails/translations/device-notify_nl.html +++ b/emails/translations/device-notify_nl.html @@ -1,20 +1,20 @@ -
[[[SERVERNAME]]] - Device Notification
+
[[[SERVERNAME]]] - Apparaatmelding
- [[[SERVERNAME]]] - Device Notification + [[[SERVERNAME]]] - Apparaatmelding

- The following devices have changed their connection state. + De volgende apparaten hebben hun verbindingsstatus gewijzigd.

- Connected devices: + Verbonden apparaten:

[[[CONNECTIONS]]] @@ -22,7 +22,7 @@

- Disconnected devices: + Losgekoppelde apparaten:

[[[DISCONNECTIONS]]] @@ -30,7 +30,7 @@

- To unsubscribe, Klik hier within 1 hour of getting this message. + Uitschrijven, Klik hier binnen 1 uur na ontvangst van dit bericht.

\ No newline at end of file diff --git a/emails/translations/device-notify_nl.txt b/emails/translations/device-notify_nl.txt index ed4a6ac5..b0c34639 100644 --- a/emails/translations/device-notify_nl.txt +++ b/emails/translations/device-notify_nl.txt @@ -1,22 +1,22 @@ -[[[SERVERNAME]]] - Device Notification +[[[SERVERNAME]]] - Apparaatmelding ~ -The following devices have changed their connection state. +De volgende apparaten hebben hun verbindingsstatus gewijzigd. ~ ~ ~ -Connected devices: +Verbonden apparaten: ~ ~[[[CONNECTIONS]]] ~ ~ ~ ~ -Disconnected devices: +Losgekoppelde apparaten: ~ ~[[[DISCONNECTIONS]]] ~ ~ ~ -To unsubscribe, load this link within 1 hour of getting this message: [[[SERVERURL]]][[[UNSUBSCRIBELINK]]] +Om je af te melden, open je deze link binnen 1 uur nadat je dit bericht hebt ontvangen: [[[SERVERURL]]][[[UNSUBSCRIBELINK]]] ~ \ No newline at end of file diff --git a/public/scripts/common-0.0.1.js b/public/scripts/common-0.0.1.js index 8f934f51..c8bc207a 100644 --- a/public/scripts/common-0.0.1.js +++ b/public/scripts/common-0.0.1.js @@ -43,19 +43,19 @@ function ArrayElementMove(arr, from, to) { arr.splice(to, 0, arr.splice(from, 1) // Print object for HTML function ObjectToStringEx(x, c) { var r = ""; - if (x != 0 && (!x || x == null)) return "(Null)"; - if (x instanceof Array) { for (var i in x) { r += '
' + gap(c) + "Item #" + i + ": " + ObjectToStringEx(x[i], c + 1); } } - else if (x instanceof Object) { for (var i in x) { r += '
' + gap(c) + i + " = " + ObjectToStringEx(x[i], c + 1); } } + if (x != 0 && (!x || x == null)) return '(Null)'; + if (x instanceof Array) { for (var i in x) { r += '
' + gap(c) + 'Item #' + i + ": " + ObjectToStringEx(x[i], c + 1); } } + else if (x instanceof Object) { for (var i in x) { r += '
' + gap(c) + i + ' = ' + ObjectToStringEx(x[i], c + 1); } } else { r += EscapeHtml(x); } return r; } // Print object for console function ObjectToStringEx2(x, c) { - var r = ""; - if (x != 0 && (!x || x == null)) return "(Null)"; - if (x instanceof Array) { for (var i in x) { r += '\r\n' + gap2(c) + "Item #" + i + ": " + ObjectToStringEx2(x[i], c + 1); } } - else if (x instanceof Object) { for (var i in x) { r += '\r\n' + gap2(c) + i + " = " + ObjectToStringEx2(x[i], c + 1); } } + var r = ''; + if (x != 0 && (!x || x == null)) return '(Null)'; + if (x instanceof Array) { for (var i in x) { r += '\r\n' + gap2(c) + 'Item #' + i + ': ' + ObjectToStringEx2(x[i], c + 1); } } + else if (x instanceof Object) { for (var i in x) { r += '\r\n' + gap2(c) + i + ' = ' + ObjectToStringEx2(x[i], c + 1); } } else { r += EscapeHtml(x); } return r; } @@ -70,7 +70,7 @@ function ObjectToString2(x) { return ObjectToStringEx2(x, 0); } // Convert a hex string to a raw string function hex2rstr(d) { - if (typeof d != "string" || d.length == 0) return ''; + if (typeof d != 'string' || d.length == 0) return ''; var r = '', m = ('' + d).match(/../g), t; while (t = m.shift()) r += String.fromCharCode('0x' + t); return r @@ -107,7 +107,7 @@ function random(max) { return Math.floor(Math.random() * max); } function trademarks(x) { return x.replace(/\(R\)/g, '®').replace(/\(TM\)/g, '™'); } // Pad a number with zeros on the left -function zeroPad(num, c) { if (c == null) { c = 2; } var s = "00000000" + num; return s.substr(s.length - c); } +function zeroPad(num, c) { if (c == null) { c = 2; } var s = '00000000' + num; return s.substr(s.length - c); } // String validation function isAlphaNumeric(str) { if (typeof str == 'number') { return true; } return (str.match(/^[A-Za-z0-9]+$/) != null); }; @@ -135,10 +135,10 @@ function parseUriArgs() { // From: https://stackoverflow.com/questions/5573096/detecting-webp-support function check_webp_feature(feature, callback) { var kTestImages = { - lossy: "UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA"//, - //lossless: "UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==", - //alpha: "UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==", - //animation: "UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA" + lossy: 'UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA'//, + //lossless: 'UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==', + //alpha: 'UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==', + //animation: 'UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA' }; var img = new Image(); img.onload = function () { @@ -148,5 +148,5 @@ function check_webp_feature(feature, callback) { img.onerror = function () { callback(feature, false); }; - img.src = "data:image/webp;base64," + kTestImages[feature]; + img.src = 'data:image/webp;base64,' + kTestImages[feature]; } \ No newline at end of file diff --git a/translate/translate.json b/translate/translate.json index 0e914433..469c4633 100644 --- a/translate/translate.json +++ b/translate/translate.json @@ -68697,4 +68697,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/views/default.handlebars b/views/default.handlebars index db3d0dd1..814da103 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -9689,7 +9689,7 @@ function p13folderup(x) { if (x == null) { p13filetreelocation.pop(); } else { while (p13filetreelocation.length > x) { p13filetreelocation.pop(); } } p13targetpath = p13filetreelocation.join('/'); - files.sendText({ action: 'ls', reqid: 1, path: p13targetpath }); + if (files) { files.sendText({ action: 'ls', reqid: 1, path: p13targetpath }); } return false; } @@ -10004,6 +10004,12 @@ p13uploadNextFile(); } + // Perform SHA-384 hashing + const byteToHex = []; + for (var n = 0; n <= 0xff; ++n) { var hexOctet = n.toString(16).padStart(2, '0'); byteToHex.push(hexOctet); } + function arrayBufferToHex(arrayBuffer) { return Array.prototype.map.call( new Uint8Array(arrayBuffer), n => byteToHex[n] ).join(''); } + function performHash(data, f) { window.crypto.subtle.digest('SHA-384', data).then(function (v) { f(arrayBufferToHex(v)); }, function() { f(null); }); } + // Push the next file function p13uploadNextFile() { uploadFile.xfilePtr++; @@ -10018,7 +10024,17 @@ uploadFile.xreader = new FileReader(); uploadFile.xreader.onload = function () { uploadFile.xdata = uploadFile.xreader.result; - files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xdata.byteLength })); + + // If the remote file already exists and is smaller then our file, see if we can resume the trasfer + var f = null; + for (var i in p13filetree.dir) { if (p13filetree.dir[i].n == file.name) { f = p13filetree.dir[i]; } } + if ((f != null) && (f.s <= uploadFile.xreader.result.byteLength)) { + performHash(uploadFile.xreader.result.slice(0, f.s), function(hash) { + files.sendText(JSON.stringify({ action: 'uploadhash', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, tag: { h: hash.toUpperCase(), s: f.s, skip: f.s == uploadFile.xreader.result.byteLength } })); + }); + } else { + files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xdata.byteLength })); + } }; uploadFile.xreader.readAsArrayBuffer(file); } else { @@ -10052,6 +10068,22 @@ case 'uploadack': { p13uploadNextPart(false); break; } case 'uploaddone': { if (uploadFile.xfiles.length > uploadFile.xfilePtr + 1) { p13uploadNextFile(); } else { p13uploadFileTransferDone(); } break; } case 'uploaderror': { p13uploadFileCancel(); break; } + case 'uploadhash': { + var file = uploadFile.xfiles[uploadFile.xfilePtr]; + if (file) { + if (cmd.tag.h === cmd.hash) { + if (cmd.tag.skip) { + p13uploadNextFile(); + } else { + uploadFile.xptr = cmd.tag.s; + files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xdata.byteLength, append: true })); + } + } else { + files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xdata.byteLength, append: false })); + } + } + break; + } } }