説明なし

RNFetchBlobRequest.m 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. //
  2. // RNFetchBlobRequest.m
  3. // RNFetchBlob
  4. //
  5. // Created by Artur Chrusciel on 15.01.18.
  6. // Copyright © 2018 wkh237.github.io. All rights reserved.
  7. //
  8. #import "RNFetchBlobRequest.h"
  9. #import "RNFetchBlobFS.h"
  10. #import "RNFetchBlobConst.h"
  11. #import "RNFetchBlobReqBuilder.h"
  12. #if __has_include(<React/RCTLog.h>)
  13. #import <React/RCTLog.h>
  14. #else
  15. #import "RCTLog.h"
  16. #endif
  17. #import "IOS7Polyfill.h"
  18. #import <CommonCrypto/CommonDigest.h>
  19. NSMapTable * taskTable;
  20. __attribute__((constructor))
  21. static void initialize_tables() {
  22. if(taskTable == nil)
  23. {
  24. taskTable = [[NSMapTable alloc] init];
  25. }
  26. }
  27. typedef NS_ENUM(NSUInteger, ResponseFormat) {
  28. UTF8,
  29. BASE64,
  30. AUTO
  31. };
  32. @interface RNFetchBlobRequest ()
  33. {
  34. BOOL respFile;
  35. BOOL isNewPart;
  36. BOOL isIncrement;
  37. NSMutableData * partBuffer;
  38. NSString * destPath;
  39. NSOutputStream * writeStream;
  40. long bodyLength;
  41. NSInteger respStatus;
  42. NSMutableArray * redirects;
  43. ResponseFormat responseFormat;
  44. BOOL followRedirect;
  45. BOOL backgroundTask;
  46. BOOL uploadTask;
  47. }
  48. @end
  49. @implementation RNFetchBlobRequest
  50. @synthesize taskId;
  51. @synthesize expectedBytes;
  52. @synthesize receivedBytes;
  53. @synthesize respData;
  54. @synthesize callback;
  55. @synthesize bridge;
  56. @synthesize options;
  57. @synthesize error;
  58. - (NSString *)md5:(NSString *)input {
  59. const char* str = [input UTF8String];
  60. unsigned char result[CC_MD5_DIGEST_LENGTH];
  61. CC_MD5(str, (CC_LONG)strlen(str), result);
  62. NSMutableString *ret = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH*2];
  63. for (int i = 0; i<CC_MD5_DIGEST_LENGTH; i++) {
  64. [ret appendFormat:@"%02x",result[i]];
  65. }
  66. return ret;
  67. }
  68. // send HTTP request
  69. - (void) sendRequest:(__weak NSDictionary * _Nullable )options
  70. contentLength:(long) contentLength
  71. bridge:(RCTBridge * _Nullable)bridgeRef
  72. taskId:(NSString * _Nullable)taskId
  73. withRequest:(__weak NSURLRequest * _Nullable)req
  74. taskOperationQueue:(NSOperationQueue * _Nonnull)operationQueue
  75. callback:(_Nullable RCTResponseSenderBlock) callback
  76. {
  77. self.taskId = taskId;
  78. self.respData = [[NSMutableData alloc] initWithLength:0];
  79. self.callback = callback;
  80. self.bridge = bridgeRef;
  81. self.expectedBytes = 0;
  82. self.receivedBytes = 0;
  83. self.options = options;
  84. backgroundTask = [[options valueForKey:@"IOSBackgroundTask"] boolValue];
  85. uploadTask = [options valueForKey:@"IOSUploadTask"] == nil ? NO : [[options valueForKey:@"IOSUploadTask"] boolValue];
  86. NSString * filepath = [options valueForKey:@"uploadFilePath"];
  87. if (uploadTask && ![[NSFileManager defaultManager] fileExistsAtPath:[NSURL URLWithString:filepath].path]) {
  88. RCTLog(@"[RNFetchBlobRequest] sendRequest uploadTask file doesn't exist %@", filepath);
  89. callback(@[@"uploadTask file doesn't exist", @"", [NSNull null]]);
  90. return;
  91. }
  92. // when followRedirect not set in options, defaults to TRUE
  93. followRedirect = [options valueForKey:@"followRedirect"] == nil ? YES : [[options valueForKey:@"followRedirect"] boolValue];
  94. isIncrement = [[options valueForKey:@"increment"] boolValue];
  95. redirects = [[NSMutableArray alloc] init];
  96. if (req.URL) {
  97. [redirects addObject:req.URL.absoluteString];
  98. }
  99. // set response format
  100. NSString * rnfbResp = [req.allHTTPHeaderFields valueForKey:@"RNFB-Response"];
  101. if ([[rnfbResp lowercaseString] isEqualToString:@"base64"]) {
  102. responseFormat = BASE64;
  103. } else if ([[rnfbResp lowercaseString] isEqualToString:@"utf8"]) {
  104. responseFormat = UTF8;
  105. } else {
  106. responseFormat = AUTO;
  107. }
  108. NSString * path = [self.options valueForKey:CONFIG_FILE_PATH];
  109. NSString * key = [self.options valueForKey:CONFIG_KEY];
  110. bodyLength = contentLength;
  111. // the session trust any SSL certification
  112. NSURLSessionConfiguration *defaultConfigObject;
  113. defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
  114. if (backgroundTask) {
  115. defaultConfigObject = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:taskId];
  116. }
  117. // request timeout, -1 if not set in options
  118. float timeout = [options valueForKey:@"timeout"] == nil ? -1 : [[options valueForKey:@"timeout"] floatValue];
  119. if (timeout > 0) {
  120. defaultConfigObject.timeoutIntervalForRequest = timeout/1000;
  121. }
  122. defaultConfigObject.HTTPMaximumConnectionsPerHost = 10;
  123. _session = [NSURLSession sessionWithConfiguration:defaultConfigObject delegate:self delegateQueue:operationQueue];
  124. if (path || [self.options valueForKey:CONFIG_USE_TEMP]) {
  125. respFile = YES;
  126. NSString* cacheKey = taskId;
  127. if (key) {
  128. cacheKey = [self md5:key];
  129. if (!cacheKey) {
  130. cacheKey = taskId;
  131. }
  132. destPath = [RNFetchBlobFS getTempPath:cacheKey withExtension:[self.options valueForKey:CONFIG_FILE_EXT]];
  133. if ([[NSFileManager defaultManager] fileExistsAtPath:destPath]) {
  134. callback(@[[NSNull null], RESP_TYPE_PATH, destPath]);
  135. return;
  136. }
  137. }
  138. if (path) {
  139. destPath = path;
  140. } else {
  141. destPath = [RNFetchBlobFS getTempPath:cacheKey withExtension:[self.options valueForKey:CONFIG_FILE_EXT]];
  142. }
  143. } else {
  144. respData = [[NSMutableData alloc] init];
  145. respFile = NO;
  146. }
  147. __block NSURLSessionTask * task;
  148. if(uploadTask)
  149. {
  150. task = [_session uploadTaskWithRequest:req fromFile:[NSURL URLWithString:filepath]];
  151. }
  152. else
  153. {
  154. task = [_session dataTaskWithRequest:req];
  155. }
  156. [taskTable setObject:task forKey:taskId];
  157. [task resume];
  158. // network status indicator
  159. if ([[options objectForKey:CONFIG_INDICATOR] boolValue]) {
  160. dispatch_async(dispatch_get_main_queue(), ^{
  161. [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
  162. });
  163. }
  164. }
  165. ////////////////////////////////////////
  166. //
  167. // NSURLSession delegates
  168. //
  169. ////////////////////////////////////////
  170. #pragma mark NSURLSession delegate methods
  171. #pragma mark - Received Response
  172. // set expected content length on response received
  173. - (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
  174. {
  175. NSLog(@"sess didReceiveResponse");
  176. expectedBytes = [response expectedContentLength];
  177. NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
  178. NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
  179. NSString * respType = @"";
  180. respStatus = statusCode;
  181. if ([response respondsToSelector:@selector(allHeaderFields)])
  182. {
  183. NSDictionary *headers = [httpResponse allHeaderFields];
  184. NSString * respCType = [[RNFetchBlobReqBuilder getHeaderIgnoreCases:@"Content-Type" fromHeaders:headers] lowercaseString];
  185. if (self.isServerPush) {
  186. if (partBuffer) {
  187. [self.bridge.eventDispatcher
  188. sendDeviceEventWithName:EVENT_SERVER_PUSH
  189. body:@{
  190. @"taskId": taskId,
  191. @"chunk": [partBuffer base64EncodedStringWithOptions:0],
  192. }
  193. ];
  194. }
  195. partBuffer = [[NSMutableData alloc] init];
  196. completionHandler(NSURLSessionResponseAllow);
  197. return;
  198. } else {
  199. self.isServerPush = [[respCType lowercaseString] RNFBContainsString:@"multipart/x-mixed-replace;"];
  200. }
  201. if(respCType)
  202. {
  203. NSArray * extraBlobCTypes = [options objectForKey:CONFIG_EXTRA_BLOB_CTYPE];
  204. if ([respCType RNFBContainsString:@"text/"]) {
  205. respType = @"text";
  206. } else if ([respCType RNFBContainsString:@"application/json"]) {
  207. respType = @"json";
  208. } else if(extraBlobCTypes) { // If extra blob content type is not empty, check if response type matches
  209. for (NSString * substr in extraBlobCTypes) {
  210. if ([respCType RNFBContainsString:[substr lowercaseString]]) {
  211. respType = @"blob";
  212. respFile = YES;
  213. destPath = [RNFetchBlobFS getTempPath:taskId withExtension:nil];
  214. break;
  215. }
  216. }
  217. } else {
  218. respType = @"blob";
  219. // for XMLHttpRequest, switch response data handling strategy automatically
  220. if ([options valueForKey:@"auto"]) {
  221. respFile = YES;
  222. destPath = [RNFetchBlobFS getTempPath:taskId withExtension:@""];
  223. }
  224. }
  225. } else {
  226. respType = @"text";
  227. }
  228. #pragma mark - handling cookies
  229. // # 153 get cookies
  230. if (response.URL) {
  231. NSHTTPCookieStorage * cookieStore = [NSHTTPCookieStorage sharedHTTPCookieStorage];
  232. NSArray<NSHTTPCookie *> * cookies = [NSHTTPCookie cookiesWithResponseHeaderFields: headers forURL:response.URL];
  233. if (cookies.count) {
  234. [cookieStore setCookies:cookies forURL:response.URL mainDocumentURL:nil];
  235. }
  236. }
  237. [self.bridge.eventDispatcher
  238. sendDeviceEventWithName: EVENT_STATE_CHANGE
  239. body:@{
  240. @"taskId": taskId,
  241. @"state": @"2",
  242. @"headers": headers,
  243. @"redirects": redirects,
  244. @"respType" : respType,
  245. @"timeout" : @NO,
  246. @"status": [NSNumber numberWithInteger:statusCode]
  247. }
  248. ];
  249. } else {
  250. NSLog(@"oops");
  251. }
  252. completionHandler(NSURLSessionResponseAllow);
  253. }
  254. // download progress handler
  255. - (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
  256. {
  257. // For #143 handling multipart/x-mixed-replace response
  258. if (self.isServerPush)
  259. {
  260. [partBuffer appendData:data];
  261. return ;
  262. }
  263. NSNumber * received = [NSNumber numberWithLong:[data length]];
  264. receivedBytes += [received longValue];
  265. NSString * chunkString = @"";
  266. if (isIncrement) {
  267. chunkString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  268. }
  269. [respData appendData:data];
  270. if (expectedBytes == 0) {
  271. return;
  272. }
  273. NSNumber * now =[NSNumber numberWithFloat:((float)receivedBytes/(float)expectedBytes)];
  274. if ([self.progressConfig shouldReport:now]) {
  275. [self.bridge.eventDispatcher
  276. sendDeviceEventWithName:EVENT_PROGRESS
  277. body:@{
  278. @"taskId": taskId,
  279. @"written": [NSString stringWithFormat:@"%lld", (long long) receivedBytes],
  280. @"total": [NSString stringWithFormat:@"%lld", (long long) expectedBytes],
  281. @"chunk": chunkString
  282. }
  283. ];
  284. }
  285. }
  286. - (void) cancelRequest:(NSString *)taskId
  287. {
  288. NSURLSessionDataTask * task = [taskTable objectForKey:taskId];
  289. if(task != nil && task.state == NSURLSessionTaskStateRunning)
  290. [task cancel];
  291. }
  292. - (void) URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error
  293. {
  294. RCTLog(@"[RNFetchBlobRequest] session didBecomeInvalidWithError %@", [error description]);
  295. if ([session isEqual:session]) {
  296. session = nil;
  297. }
  298. }
  299. - (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
  300. {
  301. RCTLog(@"[RNFetchBlobRequest] session didCompleteWithError %@", [error description]);
  302. self.error = error;
  303. NSString * errMsg;
  304. NSString * respStr;
  305. NSString * rnfbRespType;
  306. dispatch_async(dispatch_get_main_queue(), ^{
  307. [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
  308. });
  309. if (error) {
  310. if (error.domain == NSURLErrorDomain && error.code == NSURLErrorCancelled) {
  311. errMsg = @"task cancelled";
  312. } else {
  313. errMsg = [error localizedDescription];
  314. }
  315. }
  316. if (respFile) {
  317. [writeStream close];
  318. rnfbRespType = RESP_TYPE_PATH;
  319. respStr = destPath;
  320. } else { // base64 response
  321. // #73 fix unicode data encoding issue :
  322. // when response type is BASE64, we should first try to encode the response data to UTF8 format
  323. // if it turns out not to be `nil` that means the response data contains valid UTF8 string,
  324. // in order to properly encode the UTF8 string, use URL encoding before BASE64 encoding.
  325. NSString * utf8 = [[NSString alloc] initWithData:respData encoding:NSUTF8StringEncoding];
  326. if (responseFormat == BASE64) {
  327. rnfbRespType = RESP_TYPE_BASE64;
  328. respStr = [respData base64EncodedStringWithOptions:0];
  329. } else if (responseFormat == UTF8) {
  330. rnfbRespType = RESP_TYPE_UTF8;
  331. respStr = utf8;
  332. } else {
  333. if (utf8) {
  334. rnfbRespType = RESP_TYPE_UTF8;
  335. respStr = utf8;
  336. } else {
  337. rnfbRespType = RESP_TYPE_BASE64;
  338. respStr = [respData base64EncodedStringWithOptions:0];
  339. }
  340. }
  341. }
  342. callback(@[
  343. errMsg ?: [NSNull null],
  344. rnfbRespType ?: @"",
  345. respStr ?: [NSNull null]
  346. ]);
  347. @synchronized(taskTable)
  348. {
  349. if([taskTable objectForKey:taskId] == nil)
  350. NSLog(@"object released by ARC.");
  351. else
  352. [taskTable removeObjectForKey:taskId];
  353. }
  354. respData = nil;
  355. receivedBytes = 0;
  356. [session finishTasksAndInvalidate];
  357. }
  358. // upload progress handler
  359. - (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesWritten totalBytesExpectedToSend:(int64_t)totalBytesExpectedToWrite
  360. {
  361. if (totalBytesExpectedToWrite == 0) {
  362. return;
  363. }
  364. NSNumber * now = [NSNumber numberWithFloat:((float)totalBytesWritten/(float)totalBytesExpectedToWrite)];
  365. if ([self.uploadProgressConfig shouldReport:now]) {
  366. [self.bridge.eventDispatcher
  367. sendDeviceEventWithName:EVENT_PROGRESS_UPLOAD
  368. body:@{
  369. @"taskId": taskId,
  370. @"written": [NSString stringWithFormat:@"%ld", (long) totalBytesWritten],
  371. @"total": [NSString stringWithFormat:@"%ld", (long) totalBytesExpectedToWrite]
  372. }
  373. ];
  374. }
  375. }
  376. - (void) URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable credantial))completionHandler
  377. {
  378. if ([[options valueForKey:CONFIG_TRUSTY] boolValue]) {
  379. completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
  380. } else {
  381. completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
  382. }
  383. }
  384. - (void) URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
  385. {
  386. RCTLog(@"[RNFetchBlobRequest] session done in background");
  387. dispatch_async(dispatch_get_main_queue(), ^{
  388. id<UIApplicationDelegate> appDelegate = [UIApplication sharedApplication].delegate;
  389. SEL selector = NSSelectorFromString(@"backgroundTransferCompletionHandler");
  390. if ([appDelegate respondsToSelector:selector]) {
  391. void(^completionHandler)() = [appDelegate performSelector:selector];
  392. if (completionHandler != nil) {
  393. completionHandler();
  394. completionHandler = nil;
  395. }
  396. }
  397. });
  398. }
  399. - (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler
  400. {
  401. if (followRedirect) {
  402. if (request.URL) {
  403. [redirects addObject:[request.URL absoluteString]];
  404. }
  405. completionHandler(request);
  406. } else {
  407. completionHandler(nil);
  408. }
  409. }
  410. @end