Files
BetterDesk/web-nodejs/tests/localFiles.base64.test.js
UNITRONIX 6274790bba fix(rdclient): recover Cliprdr stack and stop 0KB remote files (Refs #350)
Bring native Cliprdr/desktop file IPC from divergent orphan history onto current
dev, including base64 coerce and FILEGROUPDESCRIPTOR flags matching RustDesk.
2026-08-06 22:05:58 +02:00

109 lines
3.6 KiB
JavaScript

'use strict';
/**
* Regression: Tauri desktop file IPC returns base64 strings.
* `new Uint8Array(base64String)` is length 0 in JS — that produced 0 KB remote files.
*/
const fs = require('fs');
const path = require('path');
const vm = require('vm');
function loadLocalFiles() {
const sandbox = {
console,
Uint8Array,
ArrayBuffer,
btoa: typeof btoa === 'function'
? btoa
: (s) => Buffer.from(s, 'binary').toString('base64'),
atob: typeof atob === 'function'
? atob
: (s) => {
// Match browser atob: reject invalid characters.
if (/[^A-Za-z0-9+/=]/.test(String(s))) {
throw new DOMException('Invalid character', 'InvalidCharacterError');
}
return Buffer.from(String(s), 'base64').toString('binary');
},
DOMException: typeof DOMException !== 'undefined'
? DOMException
: class DOMException extends Error {
constructor(message, name) {
super(message);
this.name = name || 'Error';
}
},
window: {
__BETTERDESK_RDCLIENT_DESKTOP__: false,
__TAURI__: null,
isSecureContext: false,
showDirectoryPicker: undefined
}
};
sandbox.window = Object.assign(sandbox.window, sandbox);
vm.runInNewContext(
fs.readFileSync(
path.join(__dirname, '..', 'public/js/rdclient/local-files.js'),
'utf8'
),
sandbox,
{ filename: 'local-files.js' }
);
return sandbox.window.LocalFiles;
}
describe('LocalFiles base64 IPC coercion', () => {
let LocalFiles;
beforeAll(() => {
LocalFiles = loadLocalFiles();
});
it('documents the Uint8Array(string) trap (length 0)', () => {
const b64 = Buffer.from('hello-file-bytes').toString('base64');
expect(new Uint8Array(b64).length).toBe(0);
});
it('decodes base64 strings from desktop_read_file_chunk', () => {
const raw = Buffer.from('hello-file-bytes');
const b64 = raw.toString('base64');
const out = LocalFiles.base64ToBytes(b64);
expect(Buffer.from(out).toString()).toBe('hello-file-bytes');
});
it('accepts legacy number-array IPC payloads', () => {
const out = LocalFiles.coerceBinaryPayload([72, 105]);
expect(Array.from(out)).toEqual([72, 105]);
});
it('round-trips bytesToBase64 ↔ base64ToBytes', () => {
const src = new Uint8Array([1, 2, 3, 250, 255]);
const b64 = LocalFiles.bytesToBase64(src);
expect(Array.from(LocalFiles.base64ToBytes(b64))).toEqual([1, 2, 3, 250, 255]);
});
it('rejects invalid base64 instead of returning empty', () => {
expect(() => LocalFiles.coerceBinaryPayload('%%%not-base64%%%')).toThrow(/base64/i);
});
});
describe('RDCompress.normalizeBytes string trap', () => {
const fs2 = require('fs');
const vm2 = require('vm');
it('does not treat base64 strings as byte arrays', () => {
const sandbox = { console, Uint8Array, ArrayBuffer };
vm2.runInNewContext(
fs2.readFileSync(
path.join(__dirname, '..', 'public/js/rdclient/compress.js'),
'utf8'
) + '\nglobalThis.RDCompress = RDCompress;',
sandbox,
{ filename: 'compress.js' }
);
const b64 = Buffer.from('payload').toString('base64');
expect(sandbox.RDCompress.normalizeBytes(b64).length).toBe(0);
expect(sandbox.RDCompress.normalizeBytes([1, 2, 3]).length).toBe(3);
});
});