appium-webdriveragent 16.2.2 → 16.4.0

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.
Files changed (66) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/PrivateHeaders/XCTest/XCSynthesizedEventRecord.h +3 -3
  3. package/PrivateHeaders/XCTest/XCUIApplication.h +1 -1
  4. package/PrivateHeaders/XCTest/XCUIDevice.h +0 -1
  5. package/PrivateHeaders/XCTest/XCUIElement.h +1 -1
  6. package/Scripts/build.sh +6 -0
  7. package/WebDriverAgent.xcodeproj/project.pbxproj +1482 -2
  8. package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/IntegrationApp_watchOS.xcscheme +76 -0
  9. package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/IntegrationTests_watchOS.xcscheme +86 -0
  10. package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/WebDriverAgentLib_watchOS.xcscheme +85 -0
  11. package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/WebDriverAgentRunner.xcscheme +1 -1
  12. package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/WebDriverAgentRunner_watchOS.xcscheme +128 -0
  13. package/WebDriverAgentLib/Categories/XCUIApplication+FBAlert.m +5 -0
  14. package/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m +2 -0
  15. package/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.m +1 -1
  16. package/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h +34 -0
  17. package/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m +76 -4
  18. package/WebDriverAgentLib/Categories/XCUIDevice+FBRotation.h +1 -1
  19. package/WebDriverAgentLib/Categories/XCUIDevice+FBRotation.m +1 -1
  20. package/WebDriverAgentLib/Categories/XCUIElement+FBPickerWheel.m +1 -1
  21. package/WebDriverAgentLib/Categories/XCUIElement+FBTyping.m +11 -0
  22. package/WebDriverAgentLib/Commands/FBCustomCommands.m +55 -4
  23. package/WebDriverAgentLib/Commands/FBElementCommands.m +6 -5
  24. package/WebDriverAgentLib/Commands/FBOrientationCommands.m +1 -1
  25. package/WebDriverAgentLib/Commands/FBSessionCommands.m +29 -5
  26. package/WebDriverAgentLib/Info.plist +2 -2
  27. package/WebDriverAgentLib/Routing/FBTCPSocket.h +70 -0
  28. package/WebDriverAgentLib/Routing/FBTCPSocket.m +199 -0
  29. package/WebDriverAgentLib/Routing/FBWebServer.m +33 -3
  30. package/WebDriverAgentLib/Routing/WatchOS/FBWatchHTTPServer.h +68 -0
  31. package/WebDriverAgentLib/Routing/WatchOS/FBWatchHTTPServer.m +376 -0
  32. package/WebDriverAgentLib/Routing/WatchOS/RouteRequest.h +30 -0
  33. package/WebDriverAgentLib/Routing/WatchOS/RouteRequest.m +25 -0
  34. package/WebDriverAgentLib/Routing/WatchOS/RouteResponse.h +30 -0
  35. package/WebDriverAgentLib/Routing/WatchOS/RouteResponse.m +54 -0
  36. package/WebDriverAgentLib/Utilities/FBActiveAppDetectionPoint.m +8 -0
  37. package/WebDriverAgentLib/Utilities/FBBaseActionsSynthesizer.h +1 -1
  38. package/WebDriverAgentLib/Utilities/FBBaseActionsSynthesizer.m +1 -1
  39. package/WebDriverAgentLib/Utilities/FBConfiguration.h +1 -1
  40. package/WebDriverAgentLib/Utilities/FBConfiguration.m +3 -3
  41. package/WebDriverAgentLib/Utilities/FBImageProcessor.m +7 -2
  42. package/WebDriverAgentLib/Utilities/FBMacros.h +14 -5
  43. package/WebDriverAgentLib/Utilities/FBMathUtils.h +1 -1
  44. package/WebDriverAgentLib/Utilities/FBMathUtils.m +1 -1
  45. package/WebDriverAgentLib/Utilities/FBPasteboard.h +1 -1
  46. package/WebDriverAgentLib/Utilities/FBPasteboard.m +1 -1
  47. package/WebDriverAgentLib/Utilities/FBSettingsHandler.m +2 -2
  48. package/WebDriverAgentLib/Utilities/FBW3CActionsSynthesizer.h +1 -1
  49. package/WebDriverAgentLib/Utilities/FBW3CActionsSynthesizer.m +1 -1
  50. package/WebDriverAgentTests/IntegrationApp_watchOS/ContentView.swift +31 -0
  51. package/WebDriverAgentTests/IntegrationApp_watchOS/WatchSpikeApp.swift +18 -0
  52. package/WebDriverAgentTests/IntegrationTests/FBForceTouchTests.m +3 -0
  53. package/WebDriverAgentTests/IntegrationTests/FBPasteboardTests.m +3 -2
  54. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAAlertIntegrationTests.swift +23 -0
  55. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAAppLifecycleIntegrationTests.swift +57 -0
  56. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAClickIntegrationTests.swift +31 -0
  57. package/WebDriverAgentTests/IntegrationTests_watchOS/WDADeviceIntegrationTests.swift +113 -0
  58. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAElementAttributeIntegrationTests.swift +57 -0
  59. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAFindIntegrationTests.swift +126 -0
  60. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAScreenshotAndSourceIntegrationTests.swift +52 -0
  61. package/WebDriverAgentTests/IntegrationTests_watchOS/WDASessionIntegrationTests.swift +72 -0
  62. package/WebDriverAgentTests/IntegrationTests_watchOS/WDATypingIntegrationTests.swift +57 -0
  63. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAUnknownCommandIntegrationTests.swift +24 -0
  64. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAWatchHTTPClient.swift +95 -0
  65. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAWatchIntegrationTestCase.swift +61 -0
  66. package/package.json +1 -1
@@ -8,17 +8,21 @@
8
8
 
9
9
  #import "FBWebServer.h"
10
10
 
11
+ #if TARGET_OS_WATCH
12
+ #import "FBWatchHTTPServer.h"
13
+ #else
11
14
  #import "RoutingConnection.h"
12
15
  #import "RoutingHTTPServer.h"
16
+ #import "FBMjpegServer.h"
17
+ #import "FBTCPSocket.h"
18
+ #endif
13
19
 
14
20
  #import "FBCommandHandler.h"
15
21
  #import "FBErrorBuilder.h"
16
22
  #import "FBExceptionHandler.h"
17
- #import "FBMjpegServer.h"
18
23
  #import "FBRouteRequest.h"
19
24
  #import "FBRuntimeUtils.h"
20
25
  #import "FBSession.h"
21
- #import "FBTCPSocket.h"
22
26
  #import "FBUnknownCommands.h"
23
27
  #import "FBConfiguration.h"
24
28
  #import "FBLogger.h"
@@ -28,6 +32,7 @@
28
32
  static NSString *const FBServerURLBeginMarker = @"ServerURLHere->";
29
33
  static NSString *const FBServerURLEndMarker = @"<-ServerURLHere";
30
34
 
35
+ #if !TARGET_OS_WATCH
31
36
  @interface FBHTTPConnection : RoutingConnection
32
37
  @end
33
38
 
@@ -45,21 +50,28 @@ static NSString *const FBServerURLEndMarker = @"<-ServerURLHere";
45
50
  }
46
51
 
47
52
  @end
53
+ #endif
48
54
 
49
55
 
50
56
  @interface FBWebServer ()
51
57
  @property (nonatomic, strong) FBExceptionHandler *exceptionHandler;
58
+ #if TARGET_OS_WATCH
59
+ @property (nonatomic, strong) FBWatchHTTPServer *server;
60
+ #else
52
61
  @property (nonatomic, strong) RoutingHTTPServer *server;
53
- @property (atomic, assign) BOOL keepAlive;
54
62
  @property (nonatomic, nullable) FBTCPSocket *screenshotsBroadcaster;
55
63
  @property (nonatomic, nullable, strong) FBMjpegServer *mjpegServer;
64
+ #endif
65
+ @property (atomic, assign) BOOL keepAlive;
56
66
  @end
57
67
 
58
68
  @implementation FBWebServer
59
69
 
60
70
  - (void)dealloc
61
71
  {
72
+ #if !TARGET_OS_WATCH
62
73
  [self stopScreenshotsBroadcaster];
74
+ #endif
63
75
  }
64
76
 
65
77
  + (NSArray<Class<FBCommandHandler>> *)collectCommandHandlerClasses
@@ -84,7 +96,9 @@ static NSString *const FBServerURLEndMarker = @"<-ServerURLHere";
84
96
  if (![self startHTTPServer]) {
85
97
  return;
86
98
  }
99
+ #if !TARGET_OS_WATCH
87
100
  [self initScreenshotsBroadcaster];
101
+ #endif
88
102
 
89
103
  self.keepAlive = YES;
90
104
  NSRunLoop *runLoop = [NSRunLoop mainRunLoop];
@@ -94,22 +108,30 @@ static NSString *const FBServerURLEndMarker = @"<-ServerURLHere";
94
108
 
95
109
  - (BOOL)startHTTPServer
96
110
  {
111
+ #if TARGET_OS_WATCH
112
+ self.server = [[FBWatchHTTPServer alloc] init];
113
+ #else
97
114
  self.server = [[RoutingHTTPServer alloc] init];
115
+ #endif
98
116
  [self.server setRouteQueue:dispatch_get_main_queue()];
99
117
  [self.server setDefaultHeader:@"Server" value:@"WebDriverAgent/1.0"];
100
118
  [self.server setDefaultHeader:@"Access-Control-Allow-Origin" value:@"*"];
101
119
  [self.server setDefaultHeader:@"Access-Control-Allow-Headers" value:@"Content-Type, X-Requested-With"];
120
+ #if !TARGET_OS_WATCH
102
121
  [self.server setConnectionClass:[FBHTTPConnection self]];
122
+ #endif
103
123
 
104
124
  [self registerRouteHandlers:[self.class collectCommandHandlerClasses]];
105
125
  [self registerServerKeyRouteHandlers];
106
126
 
107
127
  NSRange serverPortRange = FBConfiguration.sharedInstance.bindingPortRange;
108
128
  NSString *bindingIP = FBConfiguration.sharedInstance.bindingIPAddress;
129
+ #if !TARGET_OS_WATCH
109
130
  if (bindingIP != nil) {
110
131
  [self.server setInterface:bindingIP];
111
132
  [FBLogger logFmt:@"Using custom binding IP address: %@", bindingIP];
112
133
  }
134
+ #endif
113
135
 
114
136
  NSError *error;
115
137
  BOOL serverStarted = NO;
@@ -141,6 +163,7 @@ static NSString *const FBServerURLEndMarker = @"<-ServerURLHere";
141
163
  return YES;
142
164
  }
143
165
 
166
+ #if !TARGET_OS_WATCH
144
167
  - (void)initScreenshotsBroadcaster
145
168
  {
146
169
  [self readMjpegSettingsFromEnv];
@@ -186,11 +209,14 @@ static NSString *const FBServerURLEndMarker = @"<-ServerURLHere";
186
209
  FBConfiguration.sharedInstance.mjpegServerScreenshotQuality = [screenshotQuality integerValue];
187
210
  }
188
211
  }
212
+ #endif
189
213
 
190
214
  - (void)stopServing
191
215
  {
192
216
  [FBSession.activeSession kill];
217
+ #if !TARGET_OS_WATCH
193
218
  [self stopScreenshotsBroadcaster];
219
+ #endif
194
220
  if (self.server.isRunning) {
195
221
  [self.server stop:NO];
196
222
  }
@@ -199,7 +225,11 @@ static NSString *const FBServerURLEndMarker = @"<-ServerURLHere";
199
225
  self.keepAlive = NO;
200
226
  }
201
227
 
228
+ #if TARGET_OS_WATCH
229
+ - (BOOL)attemptToStartServer:(FBWatchHTTPServer *)server onPort:(NSInteger)port withError:(NSError **)error
230
+ #else
202
231
  - (BOOL)attemptToStartServer:(RoutingHTTPServer *)server onPort:(NSInteger)port withError:(NSError **)error
232
+ #endif
203
233
  {
204
234
  server.port = (UInt16)port;
205
235
  NSError *innerError = nil;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
9
+ // RoutingHTTPServer/CocoaHTTPServer need BSD sockets (via GCDAsyncSocket), which watchOS
10
+ // forbids - see FBTCPSocket.h/.m. This is a minimal HTTP/1.1 server on top of the watchOS
11
+ // FBTCPSocket, mirroring just enough of RoutingHTTPServer's API for FBWebServer.m to swap
12
+ // servers with a single #if TARGET_OS_WATCH.
13
+ //
14
+ // No chunked encoding, range requests, or pipelining - just request line + headers +
15
+ // Content-Length body, and ":param" path matching like RoutingHTTPServer.m.
16
+
17
+ @import Foundation;
18
+
19
+ #import "RouteRequest.h"
20
+ #import "RouteResponse.h"
21
+
22
+ NS_ASSUME_NONNULL_BEGIN
23
+
24
+ @interface FBWatchHTTPServer : NSObject
25
+
26
+ /*! The port the server is (or will be) listening on */
27
+ @property (nonatomic) uint16_t port;
28
+
29
+ /*! Whether the server is currently listening for connections */
30
+ @property (nonatomic, readonly) BOOL isRunning;
31
+
32
+ /**
33
+ Sets the dispatch queue on which route blocks are invoked. Pass NULL to invoke them
34
+ synchronously on the socket's own queue.
35
+ */
36
+ - (void)setRouteQueue:(nullable dispatch_queue_t)queue;
37
+
38
+ /**
39
+ Sets a header which is added to every response, unless overridden by the route itself.
40
+ */
41
+ - (void)setDefaultHeader:(NSString *)field value:(NSString *)value;
42
+
43
+ /**
44
+ Registers a route handler for the given HTTP method and path pattern (":param" segments are
45
+ captured into the request's `params`, matching RoutingHTTPServer's convention).
46
+ */
47
+ - (void)handleMethod:(NSString *)method
48
+ withPath:(NSString *)path
49
+ block:(void (^)(RouteRequest *request, RouteResponse *response))block;
50
+
51
+ /**
52
+ Convenience for -handleMethod:@"GET" withPath:path block:block.
53
+ */
54
+ - (void)get:(NSString *)path withBlock:(void (^)(RouteRequest *request, RouteResponse *response))block;
55
+
56
+ /**
57
+ Starts listening on `port`.
58
+ */
59
+ - (BOOL)start:(NSError **)error;
60
+
61
+ /**
62
+ Stops listening and disconnects all clients.
63
+ */
64
+ - (void)stop:(BOOL)immediately;
65
+
66
+ @end
67
+
68
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,376 @@
1
+ /**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
9
+ #import "FBWatchHTTPServer.h"
10
+
11
+ #import "FBConfiguration.h"
12
+ #import "FBTCPSocket.h"
13
+
14
+ static NSData *FBCRLFCRLFData(void)
15
+ {
16
+ static NSData *data;
17
+ static dispatch_once_t onceToken;
18
+ dispatch_once(&onceToken, ^{
19
+ data = [@"\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding];
20
+ });
21
+ return data;
22
+ }
23
+
24
+ // -dataUsingEncoding:NSUTF8StringEncoding never actually returns nil; this just keeps the cast
25
+ // out of every call site below.
26
+ static NSData * _Nonnull FBUTF8Data(NSString *string)
27
+ {
28
+ return (NSData * _Nonnull)[string dataUsingEncoding:NSUTF8StringEncoding];
29
+ }
30
+
31
+ @interface FBWatchHTTPRoute : NSObject
32
+ @property (nonatomic, copy) NSString *verb;
33
+ @property (nonatomic, strong) NSRegularExpression *regex;
34
+ @property (nonatomic, copy, nullable) NSArray<NSString *> *keys;
35
+ @property (nonatomic, copy) void (^block)(RouteRequest *request, RouteResponse *response);
36
+ @end
37
+
38
+ @implementation FBWatchHTTPRoute
39
+ @end
40
+
41
+
42
+ @interface FBWatchHTTPServer () <FBTCPSocketDelegate>
43
+
44
+ @property (nonatomic, nullable, strong) FBTCPSocket *socket;
45
+ @property (nonatomic, strong) NSMutableArray<FBWatchHTTPRoute *> *routes;
46
+ @property (nonatomic, strong) NSMutableDictionary<NSString *, NSString *> *defaultHeaders;
47
+ @property (nonatomic, nullable) dispatch_queue_t routeQueue;
48
+ // nw_connection_t isn't NSCopying, so it can't be an NSDictionary key - use NSMapTable instead.
49
+ @property (nonatomic, strong) NSMapTable<id, NSMutableData *> *connectionBuffers;
50
+
51
+ @end
52
+
53
+ @implementation FBWatchHTTPServer
54
+
55
+ - (instancetype)init
56
+ {
57
+ if ((self = [super init])) {
58
+ _routes = [NSMutableArray array];
59
+ _defaultHeaders = [NSMutableDictionary dictionary];
60
+ _connectionBuffers = [NSMapTable mapTableWithKeyOptions:(NSPointerFunctionsOptions)(NSMapTableObjectPointerPersonality | NSMapTableStrongMemory)
61
+ valueOptions:(NSPointerFunctionsOptions)NSMapTableStrongMemory];
62
+ }
63
+ return self;
64
+ }
65
+
66
+ - (void)setRouteQueue:(nullable dispatch_queue_t)queue
67
+ {
68
+ _routeQueue = queue;
69
+ }
70
+
71
+ - (void)setDefaultHeader:(NSString *)field value:(NSString *)value
72
+ {
73
+ self.defaultHeaders[field] = value;
74
+ }
75
+
76
+ #pragma mark - Route registration
77
+
78
+ - (FBWatchHTTPRoute *)compiledRouteWithPath:(NSString *)path
79
+ {
80
+ FBWatchHTTPRoute *route = [FBWatchHTTPRoute new];
81
+ NSMutableArray<NSString *> *keys = [NSMutableArray array];
82
+
83
+ // Escape regex-significant characters before substituting :param placeholders, like
84
+ // RoutingHTTPServer.m does.
85
+ NSRegularExpression *escapeRegex = [NSRegularExpression regularExpressionWithPattern:@"[.+()]"
86
+ options:(NSRegularExpressionOptions)0
87
+ error:nil];
88
+ NSString *escapedPath = [escapeRegex stringByReplacingMatchesInString:path
89
+ options:(NSMatchingOptions)0
90
+ range:NSMakeRange(0, path.length)
91
+ withTemplate:@"\\\\$0"];
92
+
93
+ NSRegularExpression *paramRegex = [NSRegularExpression regularExpressionWithPattern:@"(:(\\w+)|\\*)"
94
+ options:(NSRegularExpressionOptions)0
95
+ error:nil];
96
+ NSMutableString *regexPath = [NSMutableString stringWithString:escapedPath];
97
+ __block NSInteger diff = 0;
98
+ [paramRegex enumerateMatchesInString:escapedPath
99
+ options:(NSMatchingOptions)0
100
+ range:NSMakeRange(0, escapedPath.length)
101
+ usingBlock:^(NSTextCheckingResult * _Nullable result, NSMatchingFlags flags, BOOL * _Nonnull stop) {
102
+ NSRange replacementRange = NSMakeRange(diff + result.range.location, result.range.length);
103
+ NSString *capturedString = [escapedPath substringWithRange:result.range];
104
+ NSString *replacementString;
105
+ if ([capturedString isEqualToString:@"*"]) {
106
+ [keys addObject:@"wildcards"];
107
+ replacementString = @"(.*?)";
108
+ } else {
109
+ NSString *keyString = [escapedPath substringWithRange:[result rangeAtIndex:2]];
110
+ [keys addObject:keyString];
111
+ replacementString = @"([^/]+)";
112
+ }
113
+ [regexPath replaceCharactersInRange:replacementRange withString:replacementString];
114
+ diff += replacementString.length - result.range.length;
115
+ }];
116
+
117
+ NSString *anchoredPattern = [NSString stringWithFormat:@"^%@$", regexPath];
118
+ route.regex = [NSRegularExpression regularExpressionWithPattern:anchoredPattern
119
+ options:NSRegularExpressionCaseInsensitive
120
+ error:nil];
121
+ route.keys = keys.count > 0 ? keys.copy : nil;
122
+ return route;
123
+ }
124
+
125
+ - (void)handleMethod:(NSString *)method
126
+ withPath:(NSString *)path
127
+ block:(void (^)(RouteRequest *request, RouteResponse *response))block
128
+ {
129
+ FBWatchHTTPRoute *route = [self compiledRouteWithPath:path];
130
+ route.verb = method.uppercaseString;
131
+ route.block = block;
132
+ [self.routes addObject:route];
133
+ }
134
+
135
+ - (void)get:(NSString *)path withBlock:(void (^)(RouteRequest *request, RouteResponse *response))block
136
+ {
137
+ [self handleMethod:@"GET" withPath:path block:block];
138
+ }
139
+
140
+ #pragma mark - Lifecycle
141
+
142
+ - (BOOL)start:(NSError **)error
143
+ {
144
+ FBTCPSocket *socket = [[FBTCPSocket alloc] initWithPort:self.port];
145
+ socket.delegate = self;
146
+ if (![socket startWithError:error]) {
147
+ return NO;
148
+ }
149
+ self.socket = socket;
150
+ _isRunning = YES;
151
+ return YES;
152
+ }
153
+
154
+ - (void)stop:(BOOL)immediately
155
+ {
156
+ [self.socket stop];
157
+ self.socket = nil;
158
+ @synchronized (self.connectionBuffers) {
159
+ [self.connectionBuffers removeAllObjects];
160
+ }
161
+ _isRunning = NO;
162
+ }
163
+
164
+ #pragma mark - FBTCPSocketDelegate
165
+
166
+ - (void)didClientConnect:(nw_connection_t)newClient
167
+ {
168
+ @synchronized (self.connectionBuffers) {
169
+ [self.connectionBuffers setObject:[NSMutableData data] forKey:newClient];
170
+ }
171
+ }
172
+
173
+ - (void)didClientDisconnect:(nw_connection_t)client
174
+ {
175
+ @synchronized (self.connectionBuffers) {
176
+ [self.connectionBuffers removeObjectForKey:client];
177
+ }
178
+ }
179
+
180
+ - (void)client:(nw_connection_t)client didReceiveData:(NSData *)data
181
+ {
182
+ NSMutableData *buffer;
183
+ @synchronized (self.connectionBuffers) {
184
+ buffer = [self.connectionBuffers objectForKey:client];
185
+ if (nil == buffer) {
186
+ return;
187
+ }
188
+ [buffer appendData:data];
189
+ }
190
+ [self processBufferForClient:client];
191
+ }
192
+
193
+ #pragma mark - HTTP parsing
194
+
195
+ - (void)processBufferForClient:(nw_connection_t)client
196
+ {
197
+ while (YES) {
198
+ NSMutableData *buffer;
199
+ @synchronized (self.connectionBuffers) {
200
+ buffer = [self.connectionBuffers objectForKey:client];
201
+ }
202
+ if (nil == buffer) {
203
+ return;
204
+ }
205
+
206
+ NSRange headerEndRange = [buffer rangeOfData:FBCRLFCRLFData() options:(NSDataSearchOptions)0 range:NSMakeRange(0, buffer.length)];
207
+ if (NSNotFound == headerEndRange.location) {
208
+ return;
209
+ }
210
+
211
+ NSData *headerData = [buffer subdataWithRange:NSMakeRange(0, headerEndRange.location)];
212
+ NSString *headerString = [[NSString alloc] initWithData:headerData encoding:NSUTF8StringEncoding];
213
+ NSArray<NSString *> *lines = [headerString componentsSeparatedByString:@"\r\n"];
214
+ if (lines.count < 1) {
215
+ [self closeClient:client];
216
+ return;
217
+ }
218
+
219
+ NSArray<NSString *> *requestLineParts = [lines.firstObject componentsSeparatedByString:@" "];
220
+ if (requestLineParts.count < 2) {
221
+ [self closeClient:client];
222
+ return;
223
+ }
224
+ NSString *method = requestLineParts[0].uppercaseString;
225
+ NSString *pathAndQuery = requestLineParts[1];
226
+
227
+ NSMutableDictionary<NSString *, NSString *> *requestHeaders = [NSMutableDictionary dictionary];
228
+ for (NSUInteger i = 1; i < lines.count; i++) {
229
+ NSString *line = lines[i];
230
+ NSRange colonRange = [line rangeOfString:@":"];
231
+ if (NSNotFound == colonRange.location) {
232
+ continue;
233
+ }
234
+ NSString *name = [line substringToIndex:colonRange.location];
235
+ NSString *value = [[line substringFromIndex:colonRange.location + 1]
236
+ stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceCharacterSet];
237
+ requestHeaders[name.lowercaseString] = value;
238
+ }
239
+
240
+ NSUInteger contentLength = (NSUInteger)requestHeaders[@"content-length"].integerValue;
241
+ if (contentLength > FBConfiguration.sharedInstance.httpRequestBodySizeLimit) {
242
+ // Mirrors FBHTTPConnection's maxRequestBodySize enforcement on iOS/tvOS. Closes the
243
+ // connection after responding, since the rest of the oversized body is still incoming.
244
+ RouteResponse *tooLarge = [RouteResponse new];
245
+ tooLarge.statusCode = kHTTPStatusCodeRequestEntityTooLarge;
246
+ [tooLarge respondWithString:@"Request Entity Too Large"];
247
+ [self writeResponse:tooLarge toClient:client thenCloseConnection:YES];
248
+ return;
249
+ }
250
+ NSUInteger bodyStart = headerEndRange.location + headerEndRange.length;
251
+ NSUInteger totalRequestLength = bodyStart + contentLength;
252
+ if (buffer.length < totalRequestLength) {
253
+ // Wait for the rest of the body to arrive.
254
+ return;
255
+ }
256
+
257
+ NSData *body = contentLength > 0 ? [buffer subdataWithRange:NSMakeRange(bodyStart, contentLength)] : [NSData data];
258
+
259
+ @synchronized (self.connectionBuffers) {
260
+ [buffer replaceBytesInRange:NSMakeRange(0, totalRequestLength) withBytes:NULL length:0];
261
+ }
262
+
263
+ [self dispatchMethod:method pathAndQuery:pathAndQuery body:body client:client];
264
+ }
265
+ }
266
+
267
+ - (void)dispatchMethod:(NSString *)method pathAndQuery:(NSString *)pathAndQuery body:(NSData *)body client:(nw_connection_t)client
268
+ {
269
+ NSURLComponents *requestTarget = [NSURLComponents componentsWithString:pathAndQuery];
270
+ NSString *path = requestTarget.path ?: pathAndQuery;
271
+
272
+ for (FBWatchHTTPRoute *route in self.routes) {
273
+ if (![route.verb isEqualToString:method]) {
274
+ continue;
275
+ }
276
+ NSTextCheckingResult *result = [route.regex firstMatchInString:path options:(NSMatchingOptions)0 range:NSMakeRange(0, path.length)];
277
+ if (nil == result) {
278
+ continue;
279
+ }
280
+
281
+ NSMutableDictionary<NSString *, NSString *> *params = [NSMutableDictionary dictionary];
282
+ for (NSURLQueryItem *queryItem in requestTarget.queryItems) {
283
+ params[queryItem.name] = queryItem.value ?: @"";
284
+ }
285
+ if (route.keys.count > 0 && result.numberOfRanges == route.keys.count + 1) {
286
+ NSUInteger index = 1;
287
+ for (NSString *key in route.keys) {
288
+ params[key] = [path substringWithRange:[result rangeAtIndex:index]];
289
+ index++;
290
+ }
291
+ }
292
+
293
+ NSURL *url = [NSURL URLWithString:path] ?: [NSURL URLWithString:@"/"];
294
+ RouteRequest *request = [[RouteRequest alloc] initWithURL:url params:params.copy body:body];
295
+ RouteResponse *response = [RouteResponse new];
296
+ [self.defaultHeaders enumerateKeysAndObjectsUsingBlock:^(NSString *field, NSString *value, BOOL *stop) {
297
+ [response setHeader:field value:value];
298
+ }];
299
+
300
+ void (^invoke)(void) = ^{
301
+ route.block(request, response);
302
+ [self writeResponse:response toClient:client];
303
+ };
304
+ dispatch_queue_t routeQueue = self.routeQueue;
305
+ if (routeQueue) {
306
+ dispatch_async((dispatch_queue_t _Nonnull)routeQueue, invoke);
307
+ } else {
308
+ invoke();
309
+ }
310
+ return;
311
+ }
312
+
313
+ RouteResponse *notFound = [RouteResponse new];
314
+ notFound.statusCode = kHTTPStatusCodeNotFound;
315
+ [notFound respondWithString:@"Not Found"];
316
+ [self writeResponse:notFound toClient:client];
317
+ }
318
+
319
+ - (void)writeResponse:(RouteResponse *)response toClient:(nw_connection_t)client
320
+ {
321
+ [self writeResponse:response toClient:client thenCloseConnection:NO];
322
+ }
323
+
324
+ - (void)writeResponse:(RouteResponse *)response toClient:(nw_connection_t)client thenCloseConnection:(BOOL)shouldClose
325
+ {
326
+ NSMutableData *payload = [NSMutableData data];
327
+ NSString *statusLine = [NSString stringWithFormat:@"HTTP/1.1 %ld %@\r\n",
328
+ (long)response.statusCode, [self reasonPhraseForStatusCode:response.statusCode]];
329
+ [payload appendData:FBUTF8Data(statusLine)];
330
+
331
+ NSData *body = response.responseData ?: [NSData data];
332
+ NSMutableDictionary<NSString *, NSString *> *headers = response.headers.mutableCopy;
333
+ if (nil == headers[@"Content-Length"]) {
334
+ headers[@"Content-Length"] = [NSString stringWithFormat:@"%lu", (unsigned long)body.length];
335
+ }
336
+ [headers enumerateKeysAndObjectsUsingBlock:^(NSString *field, NSString *value, BOOL *stop) {
337
+ NSString *headerLine = [NSString stringWithFormat:@"%@: %@\r\n", field, value];
338
+ [payload appendData:FBUTF8Data(headerLine)];
339
+ }];
340
+ [payload appendData:FBUTF8Data(@"\r\n")];
341
+ [payload appendData:body];
342
+
343
+ if (shouldClose) {
344
+ __weak typeof(self) weakSelf = self;
345
+ [self.socket writeData:payload toClient:client completion:^{
346
+ [weakSelf closeClient:client];
347
+ }];
348
+ } else {
349
+ [self.socket writeData:payload toClient:client];
350
+ }
351
+ }
352
+
353
+ - (void)closeClient:(nw_connection_t)client
354
+ {
355
+ @synchronized (self.connectionBuffers) {
356
+ [self.connectionBuffers removeObjectForKey:client];
357
+ }
358
+ nw_connection_cancel(client);
359
+ }
360
+
361
+ - (NSString *)reasonPhraseForStatusCode:(HTTPStatusCode)statusCode
362
+ {
363
+ // if/else, not switch, to avoid having to list all ~90 HTTPStatusCode cases for -Wswitch-enum.
364
+ if (kHTTPStatusCodeOK == statusCode) {
365
+ return @"OK";
366
+ } else if (kHTTPStatusCodeBadRequest == statusCode) {
367
+ return @"Bad Request";
368
+ } else if (kHTTPStatusCodeNotFound == statusCode) {
369
+ return @"Not Found";
370
+ } else if (kHTTPStatusCodeInternalServerError == statusCode) {
371
+ return @"Internal Server Error";
372
+ }
373
+ return @"Status";
374
+ }
375
+
376
+ @end
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
9
+ // Minimal, watchOS-only stand-in for Vendor/RoutingHTTPServer/RouteRequest.h, which is not
10
+ // available on watchOS because RoutingHTTPServer/CocoaHTTPServer/CocoaAsyncSocket cannot be
11
+ // built there (see FBWatchHTTPServer.h). Exposes just the surface FBWebServer's route blocks
12
+ // and FBRoute.decorateRequest: read.
13
+
14
+ @import Foundation;
15
+
16
+ NS_ASSUME_NONNULL_BEGIN
17
+
18
+ @interface RouteRequest : NSObject
19
+
20
+ @property (nonatomic, copy, readonly) NSURL *url;
21
+ @property (nonatomic, copy, readonly) NSDictionary<NSString *, NSString *> *params;
22
+ @property (nonatomic, copy, readonly) NSData *body;
23
+
24
+ - (instancetype)initWithURL:(NSURL *)url
25
+ params:(NSDictionary<NSString *, NSString *> *)params
26
+ body:(NSData *)body;
27
+
28
+ @end
29
+
30
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
9
+ #import "RouteRequest.h"
10
+
11
+ @implementation RouteRequest
12
+
13
+ - (instancetype)initWithURL:(NSURL *)url
14
+ params:(NSDictionary<NSString *, NSString *> *)params
15
+ body:(NSData *)body
16
+ {
17
+ if ((self = [super init])) {
18
+ _url = url.copy;
19
+ _params = params.copy;
20
+ _body = body.copy;
21
+ }
22
+ return self;
23
+ }
24
+
25
+ @end
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
9
+ // Minimal, watchOS-only stand-in for Vendor/RoutingHTTPServer/RouteResponse.h, reproducing just
10
+ // what FBRoute.m/FBResponseJSONPayload.m call on it. See FBWatchHTTPServer.h.
11
+
12
+ @import Foundation;
13
+ #import <WebDriverAgentLib/FBHTTPStatusCodes.h>
14
+
15
+ NS_ASSUME_NONNULL_BEGIN
16
+
17
+ @interface RouteResponse : NSObject
18
+
19
+ @property (nonatomic) HTTPStatusCode statusCode;
20
+ @property (nonatomic, copy, readonly) NSDictionary<NSString *, NSString *> *headers;
21
+ @property (nonatomic, copy, readonly, nullable) NSData *responseData;
22
+
23
+ - (void)setHeader:(NSString *)field value:(NSString *)value;
24
+ - (void)respondWithString:(NSString *)string;
25
+ - (void)respondWithString:(NSString *)string encoding:(NSStringEncoding)encoding;
26
+ - (void)respondWithData:(NSData *)data;
27
+
28
+ @end
29
+
30
+ NS_ASSUME_NONNULL_END