1/* 2 * Copyright (c) Meta Platforms, Inc. and affiliates. 3 * 4 * This source code is licensed under the MIT license found in the 5 * LICENSE file in the root directory of this source tree. 6 */ 7 8 9#import <mutex> 10 11#import <ABI47_0_0FBReactNativeSpec/ABI47_0_0FBReactNativeSpec.h> 12#import <ABI47_0_0React/ABI47_0_0RCTAssert.h> 13#import <ABI47_0_0React/ABI47_0_0RCTConvert.h> 14#import <ABI47_0_0React/ABI47_0_0RCTLog.h> 15#import <ABI47_0_0React/ABI47_0_0RCTNetworkTask.h> 16#import <ABI47_0_0React/ABI47_0_0RCTNetworking.h> 17#import <ABI47_0_0React/ABI47_0_0RCTUtils.h> 18 19#import <ABI47_0_0React/ABI47_0_0RCTHTTPRequestHandler.h> 20 21#import "ABI47_0_0RCTNetworkPlugins.h" 22 23typedef ABI47_0_0RCTURLRequestCancellationBlock (^ABI47_0_0RCTHTTPQueryResult)(NSError *error, NSDictionary<NSString *, id> *result); 24 25NSString *const ABI47_0_0RCTNetworkingPHUploadHackScheme = @"ph-upload"; 26 27@interface ABI47_0_0RCTNetworking () <ABI47_0_0NativeNetworkingIOSSpec> 28 29- (ABI47_0_0RCTURLRequestCancellationBlock)processDataForHTTPQuery:(NSDictionary<NSString *, id> *)data 30 callback:(ABI47_0_0RCTHTTPQueryResult)callback; 31@end 32 33/** 34 * Helper to convert FormData payloads into multipart/formdata requests. 35 */ 36@interface ABI47_0_0RCTHTTPFormDataHelper : NSObject 37 38@property (nonatomic, weak) ABI47_0_0RCTNetworking *networker; 39 40@end 41 42@implementation ABI47_0_0RCTHTTPFormDataHelper 43{ 44 NSMutableArray<NSDictionary<NSString *, id> *> *_parts; 45 NSMutableData *_multipartBody; 46 ABI47_0_0RCTHTTPQueryResult _callback; 47 NSString *_boundary; 48} 49 50static NSString *ABI47_0_0RCTGenerateFormBoundary() 51{ 52 const size_t boundaryLength = 70; 53 const char *boundaryChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_."; 54 55 char *bytes = (char*)malloc(boundaryLength); 56 if (!bytes) { 57 // CWE - 391 : Unchecked error condition 58 // https://www.cvedetails.com/cwe-details/391/Unchecked-Error-Condition.html 59 // https://eli.thegreenplace.net/2009/10/30/handling-out-of-memory-conditions-in-c 60 abort(); 61 } 62 size_t charCount = strlen(boundaryChars); 63 for (int i = 0; i < boundaryLength; i++) { 64 bytes[i] = boundaryChars[arc4random_uniform((u_int32_t)charCount)]; 65 } 66 return [[NSString alloc] initWithBytesNoCopy:bytes length:boundaryLength encoding:NSUTF8StringEncoding freeWhenDone:YES]; 67} 68 69- (ABI47_0_0RCTURLRequestCancellationBlock)process:(NSArray<NSDictionary *> *)formData 70 callback:(ABI47_0_0RCTHTTPQueryResult)callback 71{ 72 ABI47_0_0RCTAssertThread(_networker.methodQueue, @"process: must be called on method queue"); 73 74 if (formData.count == 0) { 75 return callback(nil, nil); 76 } 77 78 _parts = [formData mutableCopy]; 79 _callback = callback; 80 _multipartBody = [NSMutableData new]; 81 _boundary = ABI47_0_0RCTGenerateFormBoundary(); 82 83 for (NSUInteger i = 0; i < _parts.count; i++) { 84 NSString *uri = _parts[i][@"uri"]; 85 if (uri && [[uri substringToIndex:@"ph:".length] caseInsensitiveCompare:@"ph:"] == NSOrderedSame) { 86 uri = [ABI47_0_0RCTNetworkingPHUploadHackScheme stringByAppendingString:[uri substringFromIndex:@"ph".length]]; 87 NSMutableDictionary *mutableDict = [_parts[i] mutableCopy]; 88 mutableDict[@"uri"] = uri; 89 _parts[i] = mutableDict; 90 } 91 } 92 93 return [_networker processDataForHTTPQuery:_parts[0] callback:^(NSError *error, NSDictionary<NSString *, id> *result) { 94 return [self handleResult:result error:error]; 95 }]; 96} 97 98- (ABI47_0_0RCTURLRequestCancellationBlock)handleResult:(NSDictionary<NSString *, id> *)result 99 error:(NSError *)error 100{ 101 ABI47_0_0RCTAssertThread(_networker.methodQueue, @"handleResult: must be called on method queue"); 102 103 if (error) { 104 return _callback(error, nil); 105 } 106 107 // Start with boundary. 108 [_multipartBody appendData:[[NSString stringWithFormat:@"--%@\r\n", _boundary] 109 dataUsingEncoding:NSUTF8StringEncoding]]; 110 111 // Print headers. 112 NSMutableDictionary<NSString *, NSString *> *headers = [_parts[0][@"headers"] mutableCopy]; 113 NSString *partContentType = result[@"contentType"]; 114 if (partContentType != nil) { 115 headers[@"content-type"] = partContentType; 116 } 117 [headers enumerateKeysAndObjectsUsingBlock:^(NSString *parameterKey, NSString *parameterValue, BOOL *stop) { 118 [self->_multipartBody appendData:[[NSString stringWithFormat:@"%@: %@\r\n", parameterKey, parameterValue] 119 dataUsingEncoding:NSUTF8StringEncoding]]; 120 }]; 121 122 // Add the body. 123 [_multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; 124 [_multipartBody appendData:result[@"body"]]; 125 [_multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; 126 127 [_parts removeObjectAtIndex:0]; 128 if (_parts.count) { 129 return [_networker processDataForHTTPQuery:_parts[0] callback:^(NSError *err, NSDictionary<NSString *, id> *res) { 130 return [self handleResult:res error:err]; 131 }]; 132 } 133 134 // We've processed the last item. Finish and return. 135 [_multipartBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", _boundary] 136 dataUsingEncoding:NSUTF8StringEncoding]]; 137 NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", _boundary]; 138 return _callback(nil, @{@"body": _multipartBody, @"contentType": contentType}); 139} 140 141@end 142 143/** 144 * Bridge module that provides the JS interface to the network stack. 145 */ 146@implementation ABI47_0_0RCTNetworking 147{ 148 NSMutableDictionary<NSNumber *, ABI47_0_0RCTNetworkTask *> *_tasksByRequestID; 149 std::mutex _handlersLock; 150 NSArray<id<ABI47_0_0RCTURLRequestHandler>> *_handlers; 151 NSArray<id<ABI47_0_0RCTURLRequestHandler>> * (^_handlersProvider)(ABI47_0_0RCTModuleRegistry *); 152 NSMutableArray<id<ABI47_0_0RCTNetworkingRequestHandler>> *_requestHandlers; 153 NSMutableArray<id<ABI47_0_0RCTNetworkingResponseHandler>> *_responseHandlers; 154} 155 156@synthesize methodQueue = _methodQueue; 157 158ABI47_0_0RCT_EXPORT_MODULE() 159 160+ (BOOL)requiresMainQueueSetup 161{ 162 return YES; 163} 164 165- (instancetype)init 166{ 167 return [super initWithDisabledObservation]; 168} 169 170- (instancetype)initWithHandlersProvider:(NSArray<id<ABI47_0_0RCTURLRequestHandler>> * (^)(ABI47_0_0RCTModuleRegistry *moduleRegistry))getHandlers 171{ 172 if (self = [super initWithDisabledObservation]) { 173 _handlersProvider = getHandlers; 174 } 175 return self; 176} 177 178- (void)invalidate 179{ 180 [super invalidate]; 181 182 std::lock_guard<std::mutex> lock(_handlersLock); 183 184 for (NSNumber *requestID in _tasksByRequestID) { 185 [_tasksByRequestID[requestID] cancel]; 186 } 187 [_tasksByRequestID removeAllObjects]; 188 _handlers = nil; 189 _requestHandlers = nil; 190 _responseHandlers = nil; 191} 192 193- (NSArray<NSString *> *)supportedEvents 194{ 195 return @[@"didCompleteNetworkResponse", 196 @"didReceiveNetworkResponse", 197 @"didSendNetworkData", 198 @"didReceiveNetworkIncrementalData", 199 @"didReceiveNetworkDataProgress", 200 @"didReceiveNetworkData"]; 201} 202 203- (id<ABI47_0_0RCTURLRequestHandler>)handlerForRequest:(NSURLRequest *)request 204{ 205 if (!request.URL) { 206 return nil; 207 } 208 NSArray<id<ABI47_0_0RCTURLRequestHandler>> *handlers = [self prioritizedHandlers]; 209 210 if (ABI47_0_0RCT_DEBUG) { 211 // Check for handler conflicts 212 float previousPriority = 0; 213 id<ABI47_0_0RCTURLRequestHandler> previousHandler = nil; 214 for (id<ABI47_0_0RCTURLRequestHandler> handler in handlers) { 215 float priority = [handler respondsToSelector:@selector(handlerPriority)] ? [handler handlerPriority] : 0; 216 if (previousHandler && priority < previousPriority) { 217 return previousHandler; 218 } 219 if ([handler canHandleRequest:request]) { 220 if (previousHandler) { 221 if (priority == previousPriority) { 222 ABI47_0_0RCTLogError(@"The ABI47_0_0RCTURLRequestHandlers %@ and %@ both reported that" 223 " they can handle the request %@, and have equal priority" 224 " (%g). This could result in non-deterministic behavior.", 225 handler, previousHandler, request, priority); 226 } 227 } else { 228 previousHandler = handler; 229 previousPriority = priority; 230 } 231 } 232 } 233 return previousHandler; 234 } 235 236 // Normal code path 237 for (id<ABI47_0_0RCTURLRequestHandler> handler in handlers) { 238 if ([handler canHandleRequest:request]) { 239 return handler; 240 } 241 } 242 return nil; 243} 244 245- (NSArray<id<ABI47_0_0RCTURLRequestHandler>> *)prioritizedHandlers 246{ 247 std::lock_guard<std::mutex> lock(_handlersLock); 248 if (_handlers) { 249 return _handlers; 250 } 251 252 NSArray<id<ABI47_0_0RCTURLRequestHandler>> *newHandlers = _handlersProvider 253 ? _handlersProvider(self.moduleRegistry) 254 : [self.bridge modulesConformingToProtocol:@protocol(ABI47_0_0RCTURLRequestHandler)]; 255 256 // Get handlers, sorted in reverse priority order (highest priority first) 257 newHandlers = [newHandlers sortedArrayUsingComparator:^NSComparisonResult(id<ABI47_0_0RCTURLRequestHandler> a, id<ABI47_0_0RCTURLRequestHandler> b) { 258 float priorityA = [a respondsToSelector:@selector(handlerPriority)] ? [a handlerPriority] : 0; 259 float priorityB = [b respondsToSelector:@selector(handlerPriority)] ? [b handlerPriority] : 0; 260 if (priorityA > priorityB) { 261 return NSOrderedAscending; 262 } else if (priorityA < priorityB) { 263 return NSOrderedDescending; 264 } else { 265 return NSOrderedSame; 266 } 267 }]; 268 269 _handlers = newHandlers; 270 return newHandlers; 271} 272 273- (NSDictionary<NSString *, id> *)stripNullsInRequestHeaders:(NSDictionary<NSString *, id> *)headers 274{ 275 NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:headers.count]; 276 for (NSString *key in headers.allKeys) { 277 id val = headers[key]; 278 if (val != [NSNull null]) { 279 result[key] = val; 280 } 281 } 282 283 return result; 284} 285 286- (ABI47_0_0RCTURLRequestCancellationBlock)buildRequest:(NSDictionary<NSString *, id> *)query 287 completionBlock:(void (^)(NSURLRequest *request))block 288{ 289 ABI47_0_0RCTAssertThread(_methodQueue, @"buildRequest: must be called on method queue"); 290 291 NSURL *URL = [ABI47_0_0RCTConvert NSURL:query[@"url"]]; // this is marked as nullable in JS, but should not be null 292 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; 293 request.HTTPMethod = [ABI47_0_0RCTConvert NSString:ABI47_0_0RCTNilIfNull(query[@"method"])].uppercaseString ?: @"GET"; 294 request.HTTPShouldHandleCookies = [ABI47_0_0RCTConvert BOOL:query[@"withCredentials"]]; 295 296 if (request.HTTPShouldHandleCookies == YES) { 297 // Load and set the cookie header. 298 NSArray<NSHTTPCookie *> *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:URL]; 299 request.allHTTPHeaderFields = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies]; 300 } 301 302 // Set supplied headers. 303 NSDictionary *headers = [ABI47_0_0RCTConvert NSDictionary:query[@"headers"]]; 304 [headers enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) { 305 if (value) { 306 [request addValue:[ABI47_0_0RCTConvert NSString:value] forHTTPHeaderField:key]; 307 } 308 }]; 309 310 request.timeoutInterval = [ABI47_0_0RCTConvert NSTimeInterval:query[@"timeout"]]; 311 NSDictionary<NSString *, id> *data = [ABI47_0_0RCTConvert NSDictionary:ABI47_0_0RCTNilIfNull(query[@"data"])]; 312 NSString *trackingName = data[@"trackingName"]; 313 if (trackingName) { 314 [NSURLProtocol setProperty:trackingName 315 forKey:@"trackingName" 316 inRequest:request]; 317 } 318 return [self processDataForHTTPQuery:data callback:^(NSError *error, NSDictionary<NSString *, id> *result) { 319 if (error) { 320 ABI47_0_0RCTLogError(@"Error processing request body: %@", error); 321 // Ideally we'd circle back to JS here and notify an error/abort on the request. 322 return (ABI47_0_0RCTURLRequestCancellationBlock)nil; 323 } 324 request.HTTPBody = result[@"body"]; 325 NSString *dataContentType = result[@"contentType"]; 326 NSString *requestContentType = [request valueForHTTPHeaderField:@"Content-Type"]; 327 BOOL isMultipart = [dataContentType hasPrefix:@"multipart"]; 328 329 // For multipart requests we need to override caller-specified content type with one 330 // from the data object, because it contains the boundary string 331 if (dataContentType && ([requestContentType length] == 0 || isMultipart)) { 332 [request setValue:dataContentType forHTTPHeaderField:@"Content-Type"]; 333 } 334 335 // Gzip the request body 336 if ([request.allHTTPHeaderFields[@"Content-Encoding"] isEqualToString:@"gzip"]) { 337 request.HTTPBody = ABI47_0_0RCTGzipData(request.HTTPBody, -1 /* default */); 338 [request setValue:(@(request.HTTPBody.length)).description forHTTPHeaderField:@"Content-Length"]; 339 } 340 341 dispatch_async(self->_methodQueue, ^{ 342 block(request); 343 }); 344 345 return (ABI47_0_0RCTURLRequestCancellationBlock)nil; 346 }]; 347} 348 349- (BOOL)canHandleRequest:(NSURLRequest *)request 350{ 351 return [self handlerForRequest:request] != nil; 352} 353 354/** 355 * Process the 'data' part of an HTTP query. 356 * 357 * 'data' can be a JSON value of the following forms: 358 * 359 * - {"string": "..."}: a simple JS string that will be UTF-8 encoded and sent as the body 360 * 361 * - {"uri": "some-uri://..."}: reference to a system resource, e.g. an image in the asset library 362 * 363 * - {"formData": [...]}: list of data payloads that will be combined into a multipart/form-data request 364 * 365 * - {"blob": {...}}: an object representing a blob 366 * 367 * If successful, the callback be called with a result dictionary containing the following (optional) keys: 368 * 369 * - @"body" (NSData): the body of the request 370 * 371 * - @"contentType" (NSString): the content type header of the request 372 * 373 */ 374- (ABI47_0_0RCTURLRequestCancellationBlock)processDataForHTTPQuery:(nullable NSDictionary<NSString *, id> *)query callback: 375(ABI47_0_0RCTURLRequestCancellationBlock (^)(NSError *error, NSDictionary<NSString *, id> *result))callback 376{ 377 ABI47_0_0RCTAssertThread(_methodQueue, @"processDataForHTTPQuery: must be called on method queue"); 378 379 if (!query) { 380 return callback(nil, nil); 381 } 382 for (id<ABI47_0_0RCTNetworkingRequestHandler> handler in _requestHandlers) { 383 if ([handler canHandleNetworkingRequest:query]) { 384 // @lint-ignore FBOBJCUNTYPEDCOLLECTION1 385 NSDictionary *body = [handler handleNetworkingRequest:query]; 386 if (body) { 387 return callback(nil, body); 388 } 389 } 390 } 391 NSData *body = [ABI47_0_0RCTConvert NSData:query[@"string"]]; 392 if (body) { 393 return callback(nil, @{@"body": body}); 394 } 395 NSString *base64String = [ABI47_0_0RCTConvert NSString:query[@"base64"]]; 396 if (base64String) { 397 NSData *data = [[NSData alloc] initWithBase64EncodedString:base64String options:0]; 398 return callback(nil, @{@"body": data}); 399 } 400 NSURLRequest *request = [ABI47_0_0RCTConvert NSURLRequest:query[@"uri"]]; 401 if (request) { 402 403 __block ABI47_0_0RCTURLRequestCancellationBlock cancellationBlock = nil; 404 ABI47_0_0RCTNetworkTask *task = [self networkTaskWithRequest:request completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) { 405 dispatch_async(self->_methodQueue, ^{ 406 cancellationBlock = callback(error, data ? @{@"body": data, @"contentType": ABI47_0_0RCTNullIfNil(response.MIMEType)} : nil); 407 }); 408 }]; 409 410 [task start]; 411 412 __weak ABI47_0_0RCTNetworkTask *weakTask = task; 413 return ^{ 414 [weakTask cancel]; 415 if (cancellationBlock) { 416 cancellationBlock(); 417 } 418 }; 419 } 420 NSArray<NSDictionary *> *formData = [ABI47_0_0RCTConvert NSDictionaryArray:query[@"formData"]]; 421 if (formData) { 422 ABI47_0_0RCTHTTPFormDataHelper *formDataHelper = [ABI47_0_0RCTHTTPFormDataHelper new]; 423 formDataHelper.networker = self; 424 return [formDataHelper process:formData callback:callback]; 425 } 426 // Nothing in the data payload, at least nothing we could understand anyway. 427 // Ignore and treat it as if it were null. 428 return callback(nil, nil); 429} 430 431+ (NSString *)decodeTextData:(NSData *)data fromResponse:(NSURLResponse *)response withCarryData:(NSMutableData *)inputCarryData 432{ 433 NSStringEncoding encoding = NSUTF8StringEncoding; 434 if (response.textEncodingName) { 435 CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName); 436 encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding); 437 } 438 439 NSMutableData *currentCarryData = inputCarryData ?: [NSMutableData new]; 440 [currentCarryData appendData:data]; 441 442 // Attempt to decode text 443 NSString *encodedResponse = [[NSString alloc] initWithData:currentCarryData encoding:encoding]; 444 445 if (!encodedResponse && data.length > 0) { 446 if (encoding == NSUTF8StringEncoding && inputCarryData) { 447 // If decode failed, we attempt to trim broken character bytes from the data. 448 // At this time, only UTF-8 support is enabled. Multibyte encodings, such as UTF-16 and UTF-32, require a lot of additional work 449 // to determine wether BOM was included in the first data packet. If so, save it, and attach it to each new data packet. If not, 450 // an encoding has to be selected with a suitable byte order (for ARM iOS, it would be little endianness). 451 452 CFStringEncoding cfEncoding = CFStringConvertNSStringEncodingToEncoding(encoding); 453 // Taking a single unichar is not good enough, due to Unicode combining character sequences or characters outside the BMP. 454 // See https://www.objc.io/issues/9-strings/unicode/#common-pitfalls 455 // We'll attempt with a sequence of two characters, the most common combining character sequence and characters outside the BMP (emojis). 456 CFIndex maxCharLength = CFStringGetMaximumSizeForEncoding(2, cfEncoding); 457 458 NSUInteger removedBytes = 1; 459 460 while (removedBytes < maxCharLength) { 461 encodedResponse = [[NSString alloc] initWithData:[currentCarryData subdataWithRange:NSMakeRange(0, currentCarryData.length - removedBytes)] 462 encoding:encoding]; 463 464 if (encodedResponse != nil) { 465 break; 466 } 467 468 removedBytes += 1; 469 } 470 } else { 471 // We don't have an encoding, or the encoding is incorrect, so now we try to guess 472 [NSString stringEncodingForData:data 473 encodingOptions:@{ NSStringEncodingDetectionSuggestedEncodingsKey: @[ @(encoding) ] } 474 convertedString:&encodedResponse 475 usedLossyConversion:NULL]; 476 } 477 } 478 479 if (inputCarryData) { 480 NSUInteger encodedResponseLength = [encodedResponse dataUsingEncoding:encoding].length; 481 482 // Ensure a valid subrange exists within currentCarryData 483 if (currentCarryData.length >= encodedResponseLength) { 484 NSData *newCarryData = [currentCarryData subdataWithRange:NSMakeRange(encodedResponseLength, currentCarryData.length - encodedResponseLength)]; 485 [inputCarryData setData:newCarryData]; 486 } else { 487 [inputCarryData setLength:0]; 488 } 489 } 490 491 return encodedResponse; 492} 493 494- (void)sendData:(NSData *)data 495 responseType:(NSString *)responseType 496 response:(NSURLResponse *)response 497 forTask:(ABI47_0_0RCTNetworkTask *)task 498{ 499 ABI47_0_0RCTAssertThread(_methodQueue, @"sendData: must be called on method queue"); 500 501 id responseData = nil; 502 for (id<ABI47_0_0RCTNetworkingResponseHandler> handler in _responseHandlers) { 503 if ([handler canHandleNetworkingResponse:responseType]) { 504 responseData = [handler handleNetworkingResponse:response data:data]; 505 break; 506 } 507 } 508 509 if (!responseData) { 510 if (data.length == 0) { 511 return; 512 } 513 514 if ([responseType isEqualToString:@"text"]) { 515 // No carry storage is required here because the entire data has been loaded. 516 responseData = [ABI47_0_0RCTNetworking decodeTextData:data fromResponse:task.response withCarryData:nil]; 517 if (!responseData) { 518 ABI47_0_0RCTLogWarn(@"Received data was not a string, or was not a recognised encoding."); 519 return; 520 } 521 } else if ([responseType isEqualToString:@"base64"]) { 522 responseData = [data base64EncodedStringWithOptions:0]; 523 } else { 524 ABI47_0_0RCTLogWarn(@"Invalid responseType: %@", responseType); 525 return; 526 } 527 } 528 529 [self sendEventWithName:@"didReceiveNetworkData" body:@[task.requestID, responseData]]; 530} 531 532- (void)sendRequest:(NSURLRequest *)request 533 responseType:(NSString *)responseType 534 incrementalUpdates:(BOOL)incrementalUpdates 535 responseSender:(ABI47_0_0RCTResponseSenderBlock)responseSender 536{ 537 ABI47_0_0RCTAssertThread(_methodQueue, @"sendRequest: must be called on method queue"); 538 __weak __typeof(self) weakSelf = self; 539 __block ABI47_0_0RCTNetworkTask *task; 540 ABI47_0_0RCTURLRequestProgressBlock uploadProgressBlock = ^(int64_t progress, int64_t total) { 541 NSArray *responseJSON = @[task.requestID, @((double)progress), @((double)total)]; 542 [weakSelf sendEventWithName:@"didSendNetworkData" body:responseJSON]; 543 }; 544 545 ABI47_0_0RCTURLRequestResponseBlock responseBlock = ^(NSURLResponse *response) { 546 NSDictionary<NSString *, NSString *> *headers; 547 NSInteger status; 548 if ([response isKindOfClass:[NSHTTPURLResponse class]]) { // Might be a local file request 549 NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; 550 headers = httpResponse.allHeaderFields ?: @{}; 551 status = httpResponse.statusCode; 552 } else { 553 headers = response.MIMEType ? @{@"Content-Type": response.MIMEType} : @{}; 554 status = 200; 555 } 556 id responseURL = response.URL ? response.URL.absoluteString : [NSNull null]; 557 NSArray<id> *responseJSON = @[task.requestID, @(status), headers, responseURL]; 558 [weakSelf sendEventWithName:@"didReceiveNetworkResponse" body:responseJSON]; 559 }; 560 561 // XHR does not allow you to peek at xhr.response before the response is 562 // finished. Only when xhr.responseType is set to ''/'text', consumers may 563 // peek at xhr.responseText. So unless the requested responseType is 'text', 564 // we only send progress updates and not incremental data updates to JS here. 565 ABI47_0_0RCTURLRequestIncrementalDataBlock incrementalDataBlock = nil; 566 ABI47_0_0RCTURLRequestProgressBlock downloadProgressBlock = nil; 567 if (incrementalUpdates) { 568 if ([responseType isEqualToString:@"text"]) { 569 570 // We need this to carry over bytes, which could not be decoded into text (such as broken UTF-8 characters). 571 // The incremental data block holds the ownership of this object, and will be released upon release of the block. 572 NSMutableData *incrementalDataCarry = [NSMutableData new]; 573 574 incrementalDataBlock = ^(NSData *data, int64_t progress, int64_t total) { 575 NSUInteger initialCarryLength = incrementalDataCarry.length; 576 577 NSString *responseString = [ABI47_0_0RCTNetworking decodeTextData:data 578 fromResponse:task.response 579 withCarryData:incrementalDataCarry]; 580 if (!responseString) { 581 ABI47_0_0RCTLogWarn(@"Received data was not a string, or was not a recognised encoding."); 582 return; 583 } 584 585 // Update progress to include the previous carry length and reduce the current carry length. 586 NSArray<id> *responseJSON = @[task.requestID, 587 responseString, 588 @(progress + initialCarryLength - incrementalDataCarry.length), 589 @(total)]; 590 591 [weakSelf sendEventWithName:@"didReceiveNetworkIncrementalData" body:responseJSON]; 592 }; 593 } else { 594 downloadProgressBlock = ^(int64_t progress, int64_t total) { 595 NSArray<id> *responseJSON = @[task.requestID, @(progress), @(total)]; 596 [weakSelf sendEventWithName:@"didReceiveNetworkDataProgress" body:responseJSON]; 597 }; 598 } 599 } 600 601 ABI47_0_0RCTURLRequestCompletionBlock completionBlock = 602 ^(NSURLResponse *response, NSData *data, NSError *error) { 603 __typeof(self) strongSelf = weakSelf; 604 if (!strongSelf) { 605 return; 606 } 607 608 // Unless we were sending incremental (text) chunks to JS, all along, now 609 // is the time to send the request body to JS. 610 if (!(incrementalUpdates && [responseType isEqualToString:@"text"])) { 611 [strongSelf sendData:data 612 responseType:responseType 613 response:response 614 forTask:task]; 615 } 616 NSArray *responseJSON = @[task.requestID, 617 ABI47_0_0RCTNullIfNil(error.localizedDescription), 618 error.code == kCFURLErrorTimedOut ? @YES : @NO 619 ]; 620 621 [strongSelf sendEventWithName:@"didCompleteNetworkResponse" body:responseJSON]; 622 [strongSelf->_tasksByRequestID removeObjectForKey:task.requestID]; 623 }; 624 625 task = [self networkTaskWithRequest:request completionBlock:completionBlock]; 626 task.downloadProgressBlock = downloadProgressBlock; 627 task.incrementalDataBlock = incrementalDataBlock; 628 task.responseBlock = responseBlock; 629 task.uploadProgressBlock = uploadProgressBlock; 630 631 if (task.requestID) { 632 if (!_tasksByRequestID) { 633 _tasksByRequestID = [NSMutableDictionary new]; 634 } 635 _tasksByRequestID[task.requestID] = task; 636 responseSender(@[task.requestID]); 637 } 638 639 [task start]; 640} 641 642#pragma mark - Public API 643 644- (void)addRequestHandler:(id<ABI47_0_0RCTNetworkingRequestHandler>)handler 645{ 646 if (!_requestHandlers) { 647 _requestHandlers = [NSMutableArray new]; 648 } 649 [_requestHandlers addObject:handler]; 650} 651 652- (void)addResponseHandler:(id<ABI47_0_0RCTNetworkingResponseHandler>)handler 653{ 654 if (!_responseHandlers) { 655 _responseHandlers = [NSMutableArray new]; 656 } 657 [_responseHandlers addObject:handler]; 658} 659 660- (void)removeRequestHandler:(id<ABI47_0_0RCTNetworkingRequestHandler>)handler 661{ 662 [_requestHandlers removeObject:handler]; 663} 664 665- (void)removeResponseHandler:(id<ABI47_0_0RCTNetworkingResponseHandler>)handler 666{ 667 [_responseHandlers removeObject:handler]; 668} 669 670- (ABI47_0_0RCTNetworkTask *)networkTaskWithRequest:(NSURLRequest *)request completionBlock:(ABI47_0_0RCTURLRequestCompletionBlock)completionBlock 671{ 672 id<ABI47_0_0RCTURLRequestHandler> handler = [self handlerForRequest:request]; 673 if (!handler) { 674 ABI47_0_0RCTLogError(@"No suitable URL request handler found for %@", request.URL); 675 return nil; 676 } 677 678 ABI47_0_0RCTNetworkTask *task = [[ABI47_0_0RCTNetworkTask alloc] initWithRequest:request 679 handler:handler 680 callbackQueue:_methodQueue]; 681 task.completionBlock = completionBlock; 682 return task; 683} 684 685#pragma mark - JS API 686 687ABI47_0_0RCT_EXPORT_METHOD(sendRequest:(ABI47_0_0JS::NativeNetworkingIOS::SpecSendRequestQuery &)query 688 callback:(ABI47_0_0RCTResponseSenderBlock)responseSender) 689{ 690 NSDictionary *queryDict = @{ 691 @"method": query.method(), 692 @"url": query.url(), 693 @"data": query.data(), 694 @"headers": query.headers(), 695 @"responseType": query.responseType(), 696 @"incrementalUpdates": @(query.incrementalUpdates()), 697 @"timeout": @(query.timeout()), 698 @"withCredentials": @(query.withCredentials()), 699 }; 700 701 // TODO: buildRequest returns a cancellation block, but there's currently 702 // no way to invoke it, if, for example the request is cancelled while 703 // loading a large file to build the request body 704 [self buildRequest:queryDict completionBlock:^(NSURLRequest *request) { 705 NSString *responseType = [ABI47_0_0RCTConvert NSString:queryDict[@"responseType"]]; 706 BOOL incrementalUpdates = [ABI47_0_0RCTConvert BOOL:queryDict[@"incrementalUpdates"]]; 707 [self sendRequest:request 708 responseType:responseType 709 incrementalUpdates:incrementalUpdates 710 responseSender:responseSender]; 711 }]; 712} 713 714ABI47_0_0RCT_EXPORT_METHOD(abortRequest:(double)requestID) 715{ 716 [_tasksByRequestID[[NSNumber numberWithDouble:requestID]] cancel]; 717 [_tasksByRequestID removeObjectForKey:[NSNumber numberWithDouble:requestID]]; 718} 719 720ABI47_0_0RCT_EXPORT_METHOD(clearCookies:(ABI47_0_0RCTResponseSenderBlock)responseSender) 721{ 722 NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; 723 if (!storage.cookies.count) { 724 responseSender(@[@NO]); 725 return; 726 } 727 728 for (NSHTTPCookie *cookie in storage.cookies) { 729 [storage deleteCookie:cookie]; 730 } 731 responseSender(@[@YES]); 732} 733 734- (std::shared_ptr<ABI47_0_0facebook::ABI47_0_0React::TurboModule>)getTurboModule:(const ABI47_0_0facebook::ABI47_0_0React::ObjCTurboModule::InitParams &)params 735{ 736 return std::make_shared<ABI47_0_0facebook::ABI47_0_0React::NativeNetworkingIOSSpecJSI>(params); 737} 738 739@end 740 741@implementation ABI47_0_0RCTBridge (ABI47_0_0RCTNetworking) 742 743- (ABI47_0_0RCTNetworking *)networking 744{ 745 return [self moduleForClass:[ABI47_0_0RCTNetworking class]]; 746} 747 748@end 749 750Class ABI47_0_0RCTNetworkingCls(void) { 751 return ABI47_0_0RCTNetworking.class; 752} 753