mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-14 16:31:03 +03:00
Keep browser cursor validation coverage in the existing Web fixture
This commit is contained in:
@@ -1,108 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter_hbb/consts.dart';
|
|
||||||
import 'package:flutter_hbb/models/model.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
|
|
||||||
import 'cursor_test_utils.dart';
|
|
||||||
|
|
||||||
const _side = 32;
|
|
||||||
const _rgbaChannels = 4;
|
|
||||||
const _sourceLimit = 4096;
|
|
||||||
const _invalidSizes = [
|
|
||||||
(0, _side),
|
|
||||||
(-1, _side),
|
|
||||||
(_side, 0),
|
|
||||||
(_side, -1),
|
|
||||||
(_sourceLimit + 1, 1),
|
|
||||||
(1, _sourceLimit + 1),
|
|
||||||
(_sourceLimit * 4, _sourceLimit * 4),
|
|
||||||
];
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
|
||||||
for (final platform in [
|
|
||||||
kPeerPlatformMacOS,
|
|
||||||
kPeerPlatformWindows,
|
|
||||||
kPeerPlatformLinux,
|
|
||||||
]) {
|
|
||||||
for (final density in [null, '0', '4']) {
|
|
||||||
test('cursor source validation $platform density=$density',
|
|
||||||
() => _checkPackets(platform, density));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _checkPackets(String platform, String? density) async {
|
|
||||||
final canvas = CursorTestCanvas(1, style: kRemoteViewStyleAdaptive, scale: 1);
|
|
||||||
final ffi = CursorTestFFI(canvas)..ffiModel.pi.platform = platform;
|
|
||||||
final cursor = CursorModel(WeakReference(ffi))..id = 'valid';
|
|
||||||
final messages = <String?>[];
|
|
||||||
final originalPrint = debugPrint;
|
|
||||||
debugPrint = (message, {wrapWidth}) => messages.add(message);
|
|
||||||
addTearDown(() {
|
|
||||||
debugPrint = originalPrint;
|
|
||||||
expect(cursor.parent.target, same(ffi));
|
|
||||||
cursor.disposeImages();
|
|
||||||
cursor.dispose();
|
|
||||||
canvas.dispose();
|
|
||||||
});
|
|
||||||
await cursor.updateCursorData(_event(density));
|
|
||||||
final previous = cursor.cache;
|
|
||||||
for (final length in [
|
|
||||||
0,
|
|
||||||
4,
|
|
||||||
_side * _side * _rgbaChannels - 1,
|
|
||||||
_side * _side * _rgbaChannels + 1
|
|
||||||
]) {
|
|
||||||
await _expectRejected(cursor, _event(density, length: length));
|
|
||||||
expect(messages, contains(contains('RGBA length')));
|
|
||||||
messages.clear();
|
|
||||||
}
|
|
||||||
for (final (width, height) in _invalidSizes) {
|
|
||||||
// Invalid JSON detects decoding before geometry validation, without risking
|
|
||||||
// the oversized allocation if the production size check regresses.
|
|
||||||
final packet = _event(density, width: width, height: height, length: 4)
|
|
||||||
..['colors'] = 'must not decode an invalid source size';
|
|
||||||
await _expectRejected(cursor, packet);
|
|
||||||
expect(messages, contains(contains('source size')));
|
|
||||||
messages.clear();
|
|
||||||
}
|
|
||||||
for (final (width, height) in [
|
|
||||||
(1, 1),
|
|
||||||
(_sourceLimit, 1),
|
|
||||||
(1, _sourceLimit)
|
|
||||||
]) {
|
|
||||||
cursor.id = 'valid';
|
|
||||||
await cursor
|
|
||||||
.updateCursorData(_event(density, width: width, height: height));
|
|
||||||
expect((cursor.image!.width, cursor.image!.height), (width, height));
|
|
||||||
expect(cursor.cache, isNot(same(previous)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _expectRejected(
|
|
||||||
CursorModel cursor, Map<String, String> packet) async {
|
|
||||||
final previous = cursor.cache;
|
|
||||||
final image = cursor.image;
|
|
||||||
final hotspot = (cursor.hotx, cursor.hoty);
|
|
||||||
cursor.id = 'invalid';
|
|
||||||
await cursor.updateCursorData({...packet, 'id': 'invalid'});
|
|
||||||
expect(cursor.cache, same(previous));
|
|
||||||
expect(cursor.image, same(image));
|
|
||||||
expect((cursor.hotx, cursor.hoty), hotspot);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, String> _event(String? density,
|
|
||||||
{int width = _side, int height = _side, int? length}) =>
|
|
||||||
{
|
|
||||||
'id': 'valid',
|
|
||||||
'width': '$width',
|
|
||||||
'height': '$height',
|
|
||||||
'hotx': '0',
|
|
||||||
'hoty': '0',
|
|
||||||
if (density != null) 'scale': density,
|
|
||||||
'colors': jsonEncode(
|
|
||||||
List<int>.filled(length ?? width * height * _rgbaChannels, 255)),
|
|
||||||
};
|
|
||||||
@@ -24,6 +24,7 @@ void main() {
|
|||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
_alphaTests();
|
_alphaTests();
|
||||||
_thinCursorTests();
|
_thinCursorTests();
|
||||||
|
_sourceValidationTests();
|
||||||
for (final style in [kRemoteViewStyleAdaptive, kRemoteViewStyleCustom]) {
|
for (final style in [kRemoteViewStyleAdaptive, kRemoteViewStyleCustom]) {
|
||||||
for (final dpr in [1.0, 2.0]) {
|
for (final dpr in [1.0, 2.0]) {
|
||||||
for (final (platform, density) in [
|
for (final (platform, density) in [
|
||||||
@@ -64,6 +65,47 @@ void main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _sourceValidationTests() {
|
||||||
|
const side = 48;
|
||||||
|
const sourceLimit = 4096;
|
||||||
|
const bytes = side * side * 4;
|
||||||
|
for (final density in [null, 4.0]) {
|
||||||
|
test('Web rejects invalid cursor sources and recovers, density=$density', () async {
|
||||||
|
final canvas = CursorTestCanvas(1, style: kRemoteViewStyleAdaptive, scale: 1);
|
||||||
|
addTearDown(canvas.dispose);
|
||||||
|
final cursor = await _loadCursor(CursorTestFFI(canvas), 'source-$density',
|
||||||
|
source: (side, side), pixelRatio: density);
|
||||||
|
final cache = cursor.cache;
|
||||||
|
final image = cursor.image;
|
||||||
|
final event = {
|
||||||
|
'id': 'source-$density', 'width': '$side', 'height': '$side',
|
||||||
|
'hotx': '7', 'hoty': '9',
|
||||||
|
if (density != null) 'scale': '$density',
|
||||||
|
'colors': jsonEncode(List<int>.filled(bytes, 255)),
|
||||||
|
};
|
||||||
|
// Invalid JSON proves geometry is checked before decoding or allocation.
|
||||||
|
const invalidPixels = 'must not decode an invalid source size';
|
||||||
|
for (final invalid in [
|
||||||
|
{'colors': '[1,2,3,4]'},
|
||||||
|
{'colors': jsonEncode(List<int>.filled(bytes + 1, 255))},
|
||||||
|
{'width': '0', 'colors': invalidPixels},
|
||||||
|
{'height': '-1', 'colors': invalidPixels},
|
||||||
|
{'width': '${sourceLimit * 4}', 'height': '${sourceLimit * 4}',
|
||||||
|
'colors': invalidPixels},
|
||||||
|
{'scale': '1e-300'},
|
||||||
|
{'scale': '0.001'},
|
||||||
|
]) {
|
||||||
|
await cursor.updateCursorData({...event, ...invalid});
|
||||||
|
expect(cursor.cache, same(cache));
|
||||||
|
expect(cursor.image, same(image));
|
||||||
|
expect((cursor.hotx, cursor.hoty), (7, 9));
|
||||||
|
}
|
||||||
|
await cursor.updateCursorData(event);
|
||||||
|
expect(cursor.cache, isNot(same(cache)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _thinCursorTests() {
|
void _thinCursorTests() {
|
||||||
for (final style in [kRemoteViewStyleAdaptive, kRemoteViewStyleCustom]) {
|
for (final style in [kRemoteViewStyleAdaptive, kRemoteViewStyleCustom]) {
|
||||||
for (final zoom in [false, true]) {
|
for (final zoom in [false, true]) {
|
||||||
|
|||||||
Reference in New Issue
Block a user