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