mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-09-10 17:45:42 +00:00
14fb1d9aec
Bump axios and brace-expansion; add mesh rate limits; harden OIDC redirect and ConnLimiter clamp for static analysis.
159 lines
5.3 KiB
JavaScript
159 lines
5.3 KiB
JavaScript
'use strict';
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const vm = require('vm');
|
|
|
|
function loadRdclientModules() {
|
|
const sandbox = {
|
|
console,
|
|
TextDecoder,
|
|
TextEncoder,
|
|
Uint8Array,
|
|
Blob: typeof Blob !== 'undefined' ? Blob : undefined,
|
|
DecompressionStream: typeof DecompressionStream !== 'undefined' ? DecompressionStream : undefined,
|
|
CompressionStream: typeof CompressionStream !== 'undefined' ? CompressionStream : undefined,
|
|
Response: typeof Response !== 'undefined' ? Response : undefined,
|
|
navigator: { clipboard: { writeText: jest.fn(), write: jest.fn() } },
|
|
DOMParser: class {
|
|
parseFromString(html) {
|
|
let text = String(html);
|
|
let prev;
|
|
do {
|
|
prev = text;
|
|
text = text.replace(/<[^>]+>/g, '');
|
|
} while (text !== prev);
|
|
return { body: { textContent: text } };
|
|
}
|
|
},
|
|
window: {},
|
|
globalThis: {},
|
|
};
|
|
sandbox.window = sandbox;
|
|
sandbox.globalThis = sandbox;
|
|
|
|
const base = path.join(__dirname, '..', 'public/js/rdclient');
|
|
vm.runInNewContext(
|
|
fs.readFileSync(path.join(base, 'compress.js'), 'utf8') + '\nglobalThis.RDCompress = RDCompress;',
|
|
sandbox,
|
|
{ filename: 'compress.js' }
|
|
);
|
|
vm.runInNewContext(
|
|
fs.readFileSync(path.join(base, 'clipboard.js'), 'utf8') + '\nglobalThis.RDClipboard = RDClipboard;',
|
|
sandbox,
|
|
{ filename: 'clipboard.js' }
|
|
);
|
|
return sandbox;
|
|
}
|
|
|
|
describe('RDClipboard helpers', () => {
|
|
let RDClipboard;
|
|
let RDCompress;
|
|
|
|
beforeAll(() => {
|
|
const sandbox = loadRdclientModules();
|
|
RDClipboard = sandbox.RDClipboard;
|
|
RDCompress = sandbox.RDCompress;
|
|
});
|
|
|
|
it('skips RustDesk owner special format', () => {
|
|
expect(RDClipboard.shouldSkipEntry({
|
|
format: 'Special',
|
|
specialName: 'dyn.com.rustdesk.owner',
|
|
content: new Uint8Array([1, 2, 3])
|
|
})).toBe(true);
|
|
expect(RDClipboard.shouldSkipEntry({
|
|
format: 31,
|
|
special_name: 'XML Spreadsheet',
|
|
content: new Uint8Array([1])
|
|
})).toBe(false);
|
|
});
|
|
|
|
it('stripHtml removes tags and keeps text', () => {
|
|
expect(RDClipboard.stripHtml('<p>Hello <b>world</b></p>')).toBe('Hello world');
|
|
});
|
|
|
|
it('pickBestText prefers Text over Html and Rtf', () => {
|
|
const entries = [
|
|
{ format: 'html', text: 'html plain', html: '<i>x</i>' },
|
|
{ format: 'text', text: 'plain text' },
|
|
{ format: 'rtf', text: 'rtf plain' }
|
|
];
|
|
expect(RDClipboard.pickBestText(entries)).toBe('plain text');
|
|
expect(RDClipboard.pickBestText([
|
|
{ format: 'html', text: 'from html', html: '<b>x</b>' },
|
|
{ format: 'rtf', text: 'from rtf' }
|
|
])).toBe('from html');
|
|
});
|
|
|
|
it('decodeEntry returns UTF-8 text when not compressed', async () => {
|
|
const text = 'Remote clipboard line';
|
|
const decoded = await RDClipboard.decodeEntry({
|
|
compress: false,
|
|
format: 'Text',
|
|
content: new TextEncoder().encode(text)
|
|
});
|
|
expect(decoded).toEqual({ format: 'text', text });
|
|
});
|
|
|
|
it('decodeEntries ignores owner special entries', async () => {
|
|
const decoded = await RDClipboard.decodeEntries([
|
|
{
|
|
compress: false,
|
|
format: 'Special',
|
|
specialName: 'dyn.com.rustdesk.owner',
|
|
content: new Uint8Array([9, 9, 9])
|
|
},
|
|
{
|
|
compress: false,
|
|
format: 'Text',
|
|
content: new TextEncoder().encode('kept')
|
|
}
|
|
]);
|
|
expect(decoded).toHaveLength(1);
|
|
expect(decoded[0].text).toBe('kept');
|
|
});
|
|
});
|
|
|
|
describe('RDCompress helpers', () => {
|
|
let RDCompress;
|
|
|
|
beforeAll(() => {
|
|
RDCompress = loadRdclientModules().RDCompress;
|
|
});
|
|
|
|
it('detects zstd magic bytes', () => {
|
|
expect(RDCompress.isZstdMagic(new Uint8Array([0x28, 0xb5, 0x2f, 0xfd, 0x00]))).toBe(true);
|
|
expect(RDCompress.isZstdMagic(new Uint8Array([1, 2, 3, 4]))).toBe(false);
|
|
});
|
|
|
|
it('returns raw bytes when not compressed and force is false', async () => {
|
|
const raw = new Uint8Array([72, 105]);
|
|
const out = await RDCompress.decompressZstd(raw);
|
|
expect(Array.from(out)).toEqual([72, 105]);
|
|
});
|
|
});
|
|
|
|
describe('RDClient clipboard protobuf field', () => {
|
|
const protobuf = require('protobufjs');
|
|
|
|
it('round-trips multi_clipboards on Message', async () => {
|
|
const root = await protobuf.load([
|
|
path.join(__dirname, '../protos/message.proto')
|
|
]);
|
|
const Message = root.lookupType('hbb.Message');
|
|
const msg = Message.create({
|
|
multiClipboards: {
|
|
clipboards: [{
|
|
compress: false,
|
|
format: 0,
|
|
content: Buffer.from('hello', 'utf8')
|
|
}]
|
|
}
|
|
});
|
|
const decoded = Message.decode(Message.encode(msg).finish());
|
|
expect(decoded.multiClipboards.clipboards).toHaveLength(1);
|
|
expect(Buffer.from(decoded.multiClipboards.clipboards[0].content).toString('utf8')).toBe('hello');
|
|
});
|
|
});
|