Skip to content

Commit 5c36b0a

Browse files
committed
Improve remote tester line decoding and iOS min version
Introduces a custom ChunkedLineDecoder for robust UTF-8 line splitting with max length enforcement in Dart and Python remote tester implementations. Updates iOS minimum deployment target to 13.0 in project files. Refactors remote tester logic to handle large messages and prevent buffer overflows.
1 parent da0ca85 commit 5c36b0a

File tree

8 files changed

+139
-46
lines changed

8 files changed

+139
-46
lines changed

client/integration_test/app_test.dart

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,13 @@ void main() {
4747

4848
app.main(args);
4949

50-
await Future.delayed(const Duration(milliseconds: 500));
51-
await widgetTester?.pump(duration: const Duration(seconds: 1));
52-
await widgetTester?.pumpAndSettle(
53-
duration: const Duration(milliseconds: 100));
50+
if (testerServerUrl.isEmpty) {
51+
await Future.delayed(const Duration(milliseconds: 500));
52+
await widgetTester?.pump(duration: const Duration(seconds: 1));
53+
await widgetTester?.pumpAndSettle(
54+
duration: const Duration(milliseconds: 100),
55+
);
56+
}
5457
await widgetTester?.waitForTeardown();
5558
});
5659
});

client/ios/Flutter/AppFrameworkInfo.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@
2121
<key>CFBundleVersion</key>
2222
<string>1.0</string>
2323
<key>MinimumOSVersion</key>
24-
<string>12.0</string>
24+
<string>13.0</string>
2525
</dict>
2626
</plist>

client/ios/Podfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Uncomment this line to define a global platform for your project
2-
# platform :ios, '12.0'
2+
# platform :ios, '13.0'
33

44
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
55
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

client/ios/Podfile.lock

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ PODS:
6262
- FlutterMacOS
6363
- permission_handler_apple (9.3.0):
6464
- Flutter
65-
- record_ios (1.0.0):
65+
- record_ios (1.1.0):
6666
- Flutter
6767
- rive_common (0.0.1):
6868
- Flutter
@@ -169,7 +169,7 @@ SPEC CHECKSUMS:
169169
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
170170
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
171171
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
172-
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
172+
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
173173
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
174174
Google-Mobile-Ads-SDK: 1dfb0c3cb46c7e2b00b0f4de74a1e06d9ea25d67
175175
google_mobile_ads: 535223588a6791b7a3cc3513a1bc7b89d12f3e62
@@ -180,7 +180,7 @@ SPEC CHECKSUMS:
180180
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
181181
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
182182
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
183-
record_ios: fee1c924aa4879b882ebca2b4bce6011bcfc3d8b
183+
record_ios: f75fa1d57f840012775c0e93a38a7f3ceea1a374
184184
rive_common: dd421daaf9ae69f0125aa761dd96abd278399952
185185
SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8
186186
sensors_plus: 6a11ed0c2e1d0bd0b20b4029d3bad27d96e0c65b
@@ -190,8 +190,8 @@ SPEC CHECKSUMS:
190190
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
191191
volume_controller: 3657a1f65bedb98fa41ff7dc5793537919f31b12
192192
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
193-
webview_flutter_wkwebview: 1821ceac936eba6f7984d89a9f3bcb4dea99ebb2
193+
webview_flutter_wkwebview: 8ebf4fded22593026f7dbff1fbff31ea98573c8d
194194

195-
PODFILE CHECKSUM: 6e0773c9c44c19ccfa69850451666ad1d1af99d1
195+
PODFILE CHECKSUM: 462a5b249f9f1900cbd87af7b6af48272dc2df5a
196196

197197
COCOAPODS: 1.14.3

client/ios/Runner.xcodeproj/project.pbxproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@
360360
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
361361
GCC_WARN_UNUSED_FUNCTION = YES;
362362
GCC_WARN_UNUSED_VARIABLE = YES;
363-
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
363+
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
364364
MTL_ENABLE_DEBUG_INFO = NO;
365365
SDKROOT = iphoneos;
366366
SUPPORTED_PLATFORMS = iphoneos;
@@ -441,7 +441,7 @@
441441
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
442442
GCC_WARN_UNUSED_FUNCTION = YES;
443443
GCC_WARN_UNUSED_VARIABLE = YES;
444-
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
444+
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
445445
MTL_ENABLE_DEBUG_INFO = YES;
446446
ONLY_ACTIVE_ARCH = YES;
447447
SDKROOT = iphoneos;
@@ -490,7 +490,7 @@
490490
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
491491
GCC_WARN_UNUSED_FUNCTION = YES;
492492
GCC_WARN_UNUSED_VARIABLE = YES;
493-
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
493+
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
494494
MTL_ENABLE_DEBUG_INFO = NO;
495495
SDKROOT = iphoneos;
496496
SUPPORTED_PLATFORMS = iphoneos;
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import 'dart:async';
2+
import 'dart:convert';
3+
import 'dart:typed_data';
4+
5+
/// Stream transformer which decodes UTF-8 bytes into newline-delimited strings
6+
/// while enforcing a maximum line length.
7+
class ChunkedLineDecoder extends StreamTransformerBase<Uint8List, String> {
8+
final int maxLineLength;
9+
10+
const ChunkedLineDecoder({this.maxLineLength = 16 * 1024 * 1024});
11+
12+
@override
13+
Stream<String> bind(Stream<Uint8List> stream) {
14+
final stringStream = stream.cast<List<int>>().transform(utf8.decoder);
15+
late StreamSubscription<String> subscription;
16+
late StreamController<String> controller;
17+
var pending = "";
18+
19+
void emitLine(String line) {
20+
if (line.length > maxLineLength) {
21+
throw StateError(
22+
"Line length exceeds allowed limit of $maxLineLength characters.",
23+
);
24+
}
25+
controller.add(line);
26+
}
27+
28+
controller = StreamController<String>(
29+
onListen: () {
30+
subscription = stringStream.listen(
31+
(chunk) {
32+
pending += chunk;
33+
while (true) {
34+
final newlineIndex = pending.indexOf('\n');
35+
if (newlineIndex == -1) {
36+
if (pending.length > maxLineLength) {
37+
controller.addError(StateError(
38+
"Line length exceeds allowed limit of $maxLineLength characters.",
39+
));
40+
subscription.cancel();
41+
}
42+
break;
43+
}
44+
final line = pending.substring(0, newlineIndex);
45+
emitLine(line);
46+
pending = pending.substring(newlineIndex + 1);
47+
}
48+
},
49+
onError: controller.addError,
50+
onDone: () {
51+
if (pending.isNotEmpty) {
52+
if (pending.length > maxLineLength) {
53+
controller.addError(StateError(
54+
"Line length exceeds allowed limit of $maxLineLength characters.",
55+
));
56+
} else {
57+
controller.add(pending);
58+
}
59+
}
60+
controller.close();
61+
},
62+
cancelOnError: false,
63+
);
64+
},
65+
onPause: () => subscription.pause(),
66+
onResume: () => subscription.resume(),
67+
onCancel: () => subscription.cancel(),
68+
);
69+
70+
return controller.stream;
71+
}
72+
}

packages/flet_integration_test/lib/src/remote_widget_tester.dart

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'dart:async';
22
import 'dart:convert';
33

44
import 'package:flet/flet.dart';
5+
import 'package:flet_integration_test/src/chunked_line_decoder.dart';
56
import 'package:flutter/widgets.dart';
67
import 'package:flutter_test/flutter_test.dart';
78
import 'package:integration_test/integration_test.dart';
@@ -42,25 +43,25 @@ class RemoteWidgetTester extends FlutterWidgetTester {
4243
}
4344

4445
void _startListening() {
45-
final lines = utf8.decoder.bind(_socket).transform(const LineSplitter());
46-
_subscription = lines.listen(
47-
(line) {
48-
_commandQueue = _commandQueue.then((_) => _processLine(line));
49-
},
50-
onError: (error, stackTrace) {
51-
if (!_connectionClosed.isCompleted) {
52-
_connectionClosed.completeError(error, stackTrace);
53-
}
54-
_closeSilently();
55-
},
56-
onDone: () {
57-
_closeSilently();
58-
if (!_connectionClosed.isCompleted) {
59-
_connectionClosed.complete();
60-
}
61-
},
62-
cancelOnError: true,
63-
);
46+
final stream = _socket
47+
.transform(const ChunkedLineDecoder(maxLineLength: 16 * 1024 * 1024));
48+
_subscription = stream.listen(null, onError: (error, stackTrace) {
49+
if (!_connectionClosed.isCompleted) {
50+
_connectionClosed.completeError(error, stackTrace);
51+
}
52+
_closeSilently();
53+
}, onDone: () {
54+
_closeSilently();
55+
if (!_connectionClosed.isCompleted) {
56+
_connectionClosed.complete();
57+
}
58+
}, cancelOnError: true);
59+
_subscription!.onData((line) {
60+
_subscription?.pause();
61+
final Future<void> pending = _processLine(line);
62+
_commandQueue = _commandQueue.then((_) => pending);
63+
pending.whenComplete(() => _subscription?.resume());
64+
});
6465
}
6566

6667
Future<void> _processLine(String line) async {

sdk/python/packages/flet/src/flet/testing/remote_tester.py

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -82,21 +82,38 @@ async def _handle_client(
8282

8383
async def _read_loop(self):
8484
assert self._reader is not None
85+
buffer = bytearray()
86+
max_line_length = 16 * 1024 * 1024
8587
while True:
86-
line = await self._reader.readline()
87-
if not line:
88+
chunk = await self._reader.read(4096)
89+
if not chunk:
90+
if buffer:
91+
message = json.loads(buffer.decode("utf-8"))
92+
self._dispatch_message(message)
8893
break
89-
message = json.loads(line.decode("utf-8"))
90-
request_id = message.get("id")
91-
future = self._pending.pop(request_id, None)
92-
if future is None:
93-
continue
94-
if "error" in message:
95-
future.set_exception(
96-
RemoteTesterError(message["error"], message.get("stack"))
97-
)
98-
else:
99-
future.set_result(message.get("result"))
94+
buffer.extend(chunk)
95+
while True:
96+
newline_index = buffer.find(b"\n")
97+
if newline_index == -1:
98+
if len(buffer) > max_line_length:
99+
raise ValueError("Incoming message exceeds allowed size.")
100+
break
101+
line = buffer[:newline_index]
102+
del buffer[: newline_index + 1]
103+
message = json.loads(line.decode("utf-8"))
104+
self._dispatch_message(message)
105+
106+
def _dispatch_message(self, message: Any):
107+
request_id = message.get("id")
108+
future = self._pending.pop(request_id, None)
109+
if future is None:
110+
return
111+
if "error" in message:
112+
future.set_exception(
113+
RemoteTesterError(message["error"], message.get("stack"))
114+
)
115+
else:
116+
future.set_result(message.get("result"))
100117

101118
def _cleanup_connection(self):
102119
for future in self._pending.values():

0 commit comments

Comments
 (0)