RCTNetworking.mm 27 KB

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