appium-webdriveragent 16.13.1 → 16.13.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [16.13.3](https://github.com/appium/WebDriverAgent/compare/v16.13.2...v16.13.3) (2026-09-30)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
* reject stale sessions without retaining past responses ([#1282](https://github.com/appium/WebDriverAgent/issues/1282)) ([f245ffb](https://github.com/appium/WebDriverAgent/commit/f245ffb4e6906143384410cf312abc5bcedf5486))
|
|
6
|
+
|
|
7
|
+
## [16.13.2](https://github.com/appium/WebDriverAgent/compare/v16.13.1...v16.13.2) (2026-09-30)
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* process received HTTP bytes before handling EOF ([#1277](https://github.com/appium/WebDriverAgent/issues/1277)) ([203fc4f](https://github.com/appium/WebDriverAgent/commit/203fc4f012ab1d90caf0127fe85f119ad85cb0e8))
|
|
12
|
+
|
|
1
13
|
## [16.13.1](https://github.com/appium/WebDriverAgent/compare/v16.13.0...v16.13.1) (2026-09-30)
|
|
2
14
|
|
|
3
15
|
### Performance Improvements
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
<key>CFBundlePackageType</key>
|
|
16
16
|
<string>FMWK</string>
|
|
17
17
|
<key>CFBundleShortVersionString</key>
|
|
18
|
-
<string>16.13.
|
|
18
|
+
<string>16.13.3</string>
|
|
19
19
|
<key>CFBundleSignature</key>
|
|
20
20
|
<string>????</string>
|
|
21
21
|
<key>CFBundleVersion</key>
|
|
22
|
-
<string>16.13.
|
|
22
|
+
<string>16.13.3</string>
|
|
23
23
|
<key>NSPrincipalClass</key>
|
|
24
24
|
<string/>
|
|
25
25
|
</dict>
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
#import "FBLogger.h"
|
|
14
14
|
#import "FBResponsePayload.h"
|
|
15
15
|
#import "FBTCPSocket.h"
|
|
16
|
+
#import "FBSession.h"
|
|
16
17
|
|
|
17
18
|
static NSData *FBCRLFCRLFData(void)
|
|
18
19
|
{
|
|
@@ -131,10 +132,6 @@ static BOOL FBParseContentLength(NSString *value, NSUInteger *outLength)
|
|
|
131
132
|
// standalone or not (except DELETE /session itself - see -dispatchMethod:). See
|
|
132
133
|
// -abandonPendingRequestsForSessionID:. Guarded by @synchronized(self.pendingSessionRequests).
|
|
133
134
|
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSMutableSet<FBPendingRequest *> *> *pendingSessionRequests;
|
|
134
|
-
// Already-abandoned sessions mapped to the response they were abandoned with, so a request parsed
|
|
135
|
-
// after that point is answered at once instead of queueing for a session that is gone. Kept for
|
|
136
|
-
// the server's lifetime; ids are UUIDs. Guarded by @synchronized(self.pendingSessionRequests).
|
|
137
|
-
@property (nonatomic, strong) NSMutableDictionary<NSString *, RouteResponse *> *abandonedSessionResponses;
|
|
138
135
|
// When each connection started waiting for its current request. The reaper closes connections
|
|
139
136
|
// whose entry outlives FBIncompleteRequestTimeout; idle keep-alive connections have no entry and
|
|
140
137
|
// are exempt. Guarded by @synchronized(self.connectionBuffers).
|
|
@@ -163,7 +160,6 @@ static const int64_t FBStaleConnectionSweepIntervalSec = 10;
|
|
|
163
160
|
_connectionsAwaitingResponse = [NSMutableSet set];
|
|
164
161
|
_standaloneWaiters = [NSMutableDictionary dictionary];
|
|
165
162
|
_pendingSessionRequests = [NSMutableDictionary dictionary];
|
|
166
|
-
_abandonedSessionResponses = [NSMutableDictionary dictionary];
|
|
167
163
|
_incompleteRequestStarts = [NSMapTable mapTableWithKeyOptions:(NSPointerFunctionsOptions)(NSMapTableObjectPointerPersonality | NSMapTableStrongMemory)
|
|
168
164
|
valueOptions:(NSPointerFunctionsOptions)NSMapTableStrongMemory];
|
|
169
165
|
}
|
|
@@ -340,18 +336,28 @@ static const int64_t FBStaleConnectionSweepIntervalSec = 10;
|
|
|
340
336
|
|
|
341
337
|
- (void)didClientDisconnect:(nw_connection_t)client
|
|
342
338
|
{
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
339
|
+
// EOF must follow every receive already queued for this client. Otherwise it can
|
|
340
|
+
// remove the buffer before those bytes have even been appended and parsed.
|
|
341
|
+
__weak typeof(self) weakSelf = self;
|
|
342
|
+
dispatch_async(self.bufferProcessingQueue, ^{
|
|
343
|
+
__strong typeof(weakSelf) strongSelf = weakSelf;
|
|
344
|
+
if (nil == strongSelf) {
|
|
345
|
+
nw_connection_cancel(client);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
BOOL isAwaitingResponse;
|
|
349
|
+
@synchronized (strongSelf.connectionBuffers) {
|
|
350
|
+
isAwaitingResponse = [strongSelf.connectionsAwaitingResponse containsObject:client];
|
|
351
|
+
[strongSelf.connectionBuffers removeObjectForKey:client];
|
|
352
|
+
[strongSelf.pendingRequestHeaders removeObjectForKey:client];
|
|
353
|
+
[strongSelf.incompleteRequestStarts removeObjectForKey:client];
|
|
354
|
+
}
|
|
355
|
+
// A client that only stopped sending may still read the response to its in-flight request, so
|
|
356
|
+
// that response's send completion closes the connection instead (it finds no buffer left).
|
|
357
|
+
if (!isAwaitingResponse) {
|
|
358
|
+
nw_connection_cancel(client);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
355
361
|
}
|
|
356
362
|
|
|
357
363
|
- (void)client:(nw_connection_t)client didReceiveData:(NSData *)data
|
|
@@ -729,9 +735,15 @@ static const int64_t FBStaleConnectionSweepIntervalSec = 10;
|
|
|
729
735
|
- (nullable RouteResponse *)trackPendingRequest:(FBPendingRequest *)pendingRequest forSessionID:(NSString *)sessionID
|
|
730
736
|
{
|
|
731
737
|
@synchronized (self.pendingSessionRequests) {
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
738
|
+
// Checking the current session under this lock closes the admission/teardown
|
|
739
|
+
// race without retaining a tombstone and a full response for every past UUID.
|
|
740
|
+
// -kill clears the active session before posting its abandonment notification.
|
|
741
|
+
if (nil == [FBSession sessionWithIdentifier:sessionID]) {
|
|
742
|
+
RouteResponse *response = [RouteResponse new];
|
|
743
|
+
[FBResponseWithStatus([FBCommandStatus noSuchDriverErrorWithMessage:@"Session does not exist"
|
|
744
|
+
traceback:nil]) dispatchWithResponse:response];
|
|
745
|
+
[self applyDefaultHeadersToResponse:response];
|
|
746
|
+
return response;
|
|
735
747
|
}
|
|
736
748
|
NSMutableSet<FBPendingRequest *> *pendingRequests = self.pendingSessionRequests[sessionID];
|
|
737
749
|
if (nil == pendingRequests) {
|
|
@@ -766,8 +778,6 @@ static const int64_t FBStaleConnectionSweepIntervalSec = 10;
|
|
|
766
778
|
@synchronized (self.pendingSessionRequests) {
|
|
767
779
|
pendingRequests = [self.pendingSessionRequests[sessionID] copy];
|
|
768
780
|
[self.pendingSessionRequests removeObjectForKey:sessionID];
|
|
769
|
-
// Recorded before the lock is dropped, so requests admitted from here on are rejected.
|
|
770
|
-
self.abandonedSessionResponses[sessionID] = response;
|
|
771
781
|
}
|
|
772
782
|
for (FBPendingRequest *pendingRequest in pendingRequests) {
|
|
773
783
|
[self writeResponse:response toClient:pendingRequest.client];
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
#import <sys/socket.h>
|
|
14
14
|
|
|
15
15
|
#import "FBHTTPServer.h"
|
|
16
|
+
#import "FBSession-Private.h"
|
|
16
17
|
|
|
17
18
|
static atomic_int gSessionProbeHits;
|
|
18
19
|
|
|
@@ -23,10 +24,26 @@ static atomic_int gSessionProbeHits;
|
|
|
23
24
|
|
|
24
25
|
@implementation FBHTTPServerSessionTests
|
|
25
26
|
|
|
27
|
+
- (void)testAbandonmentDoesNotRetainResponsesAfterPendingRequestsDrain
|
|
28
|
+
{
|
|
29
|
+
__weak RouteResponse *weakResponse;
|
|
30
|
+
@autoreleasepool {
|
|
31
|
+
RouteResponse *response = [RouteResponse new];
|
|
32
|
+
[response respondWithString:@"deleted"];
|
|
33
|
+
weakResponse = response;
|
|
34
|
+
for (NSUInteger index = 0; index < 1000; index++) {
|
|
35
|
+
[self.server abandonPendingRequestsForSessionID:[NSString stringWithFormat:@"old-%lu", (unsigned long)index]
|
|
36
|
+
withResponse:response];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
XCTAssertNil(weakResponse);
|
|
40
|
+
}
|
|
41
|
+
|
|
26
42
|
- (void)setUp
|
|
27
43
|
{
|
|
28
44
|
[super setUp];
|
|
29
45
|
atomic_store(&gSessionProbeHits, 0);
|
|
46
|
+
[FBSession initWithApplication:nil].identifier = @"live-session";
|
|
30
47
|
self.server = [FBHTTPServer new];
|
|
31
48
|
[self.server get:@"/session/:sessionID/probe" withBlock:^(RouteRequest *request, RouteResponse *response) {
|
|
32
49
|
atomic_fetch_add(&gSessionProbeHits, 1);
|
|
@@ -42,6 +59,7 @@ static atomic_int gSessionProbeHits;
|
|
|
42
59
|
{
|
|
43
60
|
[self.server stop:NO];
|
|
44
61
|
self.server = nil;
|
|
62
|
+
[FBSession.activeSession kill];
|
|
45
63
|
[super tearDown];
|
|
46
64
|
}
|
|
47
65
|
|
|
@@ -93,14 +111,14 @@ static atomic_int gSessionProbeHits;
|
|
|
93
111
|
|
|
94
112
|
NSString *response = [self responseForRawPayload:(NSData * _Nonnull)[@"GET /session/dead-session/probe HTTP/1.1\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]
|
|
95
113
|
timeout:5.0];
|
|
96
|
-
XCTAssertTrue([response containsString:@"session
|
|
114
|
+
XCTAssertTrue([response containsString:@"invalid session id"], @"%@", response);
|
|
97
115
|
XCTAssertEqual(atomic_load(&gSessionProbeHits), 0, @"the route must not run for a deleted session");
|
|
98
116
|
}
|
|
99
117
|
|
|
100
118
|
- (void)testAbandonedSessionIsRememberedAfterManyLaterAbandonments
|
|
101
119
|
{
|
|
102
|
-
//
|
|
103
|
-
//
|
|
120
|
+
// A stale UUID must still be rejected after arbitrarily many other sessions,
|
|
121
|
+
// without needing to remember every deleted UUID.
|
|
104
122
|
RouteResponse *abandonedResponse = [RouteResponse new];
|
|
105
123
|
[abandonedResponse respondWithString:@"session-was-deleted"];
|
|
106
124
|
[self.server abandonPendingRequestsForSessionID:@"dead-session" withResponse:abandonedResponse];
|
|
@@ -113,7 +131,7 @@ static atomic_int gSessionProbeHits;
|
|
|
113
131
|
|
|
114
132
|
NSString *response = [self responseForRawPayload:(NSData * _Nonnull)[@"GET /session/dead-session/probe HTTP/1.1\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]
|
|
115
133
|
timeout:5.0];
|
|
116
|
-
XCTAssertTrue([response containsString:@"session
|
|
134
|
+
XCTAssertTrue([response containsString:@"invalid session id"], @"%@", response);
|
|
117
135
|
XCTAssertEqual(atomic_load(&gSessionProbeHits), 0, @"the route must not run for a deleted session");
|
|
118
136
|
}
|
|
119
137
|
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
#import <unistd.h>
|
|
15
15
|
|
|
16
16
|
#import "FBHTTPServer.h"
|
|
17
|
+
#import "FBTCPSocket.h"
|
|
17
18
|
|
|
18
19
|
static atomic_int gFramingProbeHits;
|
|
19
20
|
|
|
@@ -242,6 +243,32 @@ static NSString *FBCompleteResponse(NSData *data)
|
|
|
242
243
|
XCTAssertEqual([self serverSocketCountSettlingAt:baseline], baseline);
|
|
243
244
|
}
|
|
244
245
|
|
|
246
|
+
- (void)testEofWaitsForPreviouslyReceivedBytesToBeParsed
|
|
247
|
+
{
|
|
248
|
+
FBHTTPServer *server = [FBHTTPServer new];
|
|
249
|
+
__block NSUInteger calls = 0;
|
|
250
|
+
[server get:@"/probe" withBlock:^(RouteRequest *request, RouteResponse *response) {
|
|
251
|
+
calls++;
|
|
252
|
+
}];
|
|
253
|
+
dispatch_queue_t queue = [server valueForKey:@"bufferProcessingQueue"];
|
|
254
|
+
nw_connection_t client = nw_connection_create(nw_endpoint_create_host("127.0.0.1", "1"),
|
|
255
|
+
nw_parameters_create_secure_tcp(NW_PARAMETERS_DISABLE_PROTOCOL, NW_PARAMETERS_DEFAULT_CONFIGURATION));
|
|
256
|
+
id<FBTCPSocketDelegate> delegate = (id)server;
|
|
257
|
+
[delegate didClientConnect:client];
|
|
258
|
+
dispatch_suspend(queue);
|
|
259
|
+
@try {
|
|
260
|
+
NSData *data = [@"GET /probe HTTP/1.1\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding];
|
|
261
|
+
[delegate client:client didReceiveData:data];
|
|
262
|
+
[delegate didClientDisconnect:client];
|
|
263
|
+
} @finally {
|
|
264
|
+
dispatch_resume(queue);
|
|
265
|
+
}
|
|
266
|
+
dispatch_sync(queue, ^{});
|
|
267
|
+
XCTAssertEqual(calls, 1u);
|
|
268
|
+
[server stop:YES];
|
|
269
|
+
nw_connection_cancel(client);
|
|
270
|
+
}
|
|
271
|
+
|
|
245
272
|
- (void)testHalfClosedClientGetsItsResponseAndIsThenClosed
|
|
246
273
|
{
|
|
247
274
|
// A client may shut down its sending side right after the request and wait for the response.
|