Bez popisu

RNFetchBlobReqBuilder.m 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. //
  2. // RNFetchBlobReqBuilder.m
  3. // RNFetchBlob
  4. //
  5. // Created by Ben Hsieh on 2016/7/9.
  6. // Copyright © 2016年 wkh237. All rights reserved.
  7. //
  8. #import <Foundation/Foundation.h>
  9. #import "RNFetchBlobReqBuilder.h"
  10. #import "RNFetchBlobNetwork.h"
  11. #import "RNFetchBlobConst.h"
  12. #import "RNFetchBlobFS.h"
  13. #import "RCTLog.h"
  14. #import "IOS7Polyfill.h"
  15. @interface RNFetchBlobReqBuilder()
  16. {
  17. }
  18. @end
  19. @implementation RNFetchBlobReqBuilder
  20. // Fetch blob data request
  21. +(void) buildMultipartRequest:(NSDictionary *)options
  22. taskId:(NSString *)taskId
  23. method:(NSString *)method
  24. url:(NSString *)url
  25. headers:(NSDictionary *)headers
  26. form:(NSArray *)form
  27. onComplete:(void(^)(NSURLRequest * req, long bodyLength))onComplete
  28. {
  29. // NSString * encodedUrl = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
  30. NSString * encodedUrl = url;
  31. // send request
  32. __block NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString: encodedUrl]];
  33. __block NSMutableDictionary *mheaders = [[NSMutableDictionary alloc] initWithDictionary:[RNFetchBlobNetwork normalizeHeaders:headers]];
  34. NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
  35. NSNumber * timeStampObj = [NSNumber numberWithDouble: timeStamp];
  36. // generate boundary
  37. __block NSString * boundary = [NSString stringWithFormat:@"RNFetchBlob%d", timeStampObj];
  38. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  39. __block NSMutableData * postData = [[NSMutableData alloc] init];
  40. // combine multipart/form-data body
  41. [[self class] buildFormBody:form boundary:boundary onComplete:^(NSData *formData) {
  42. if(formData != nil) {
  43. [postData appendData:formData];
  44. // close form data
  45. [postData appendData: [[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
  46. [request setHTTPBody:postData];
  47. }
  48. // set content-length
  49. [mheaders setValue:[NSString stringWithFormat:@"%d",[postData length]] forKey:@"Content-Length"];
  50. [mheaders setValue:[NSString stringWithFormat:@"100-continue",[postData length]] forKey:@"Expect"];
  51. // appaned boundary to content-type
  52. [mheaders setValue:[NSString stringWithFormat:@"multipart/form-data; charset=utf-8; boundary=%@", boundary] forKey:@"content-type"];
  53. [request setHTTPMethod: method];
  54. [request setAllHTTPHeaderFields:mheaders];
  55. onComplete(request, [formData length]);
  56. }];
  57. });
  58. }
  59. // Fetch blob data request
  60. +(void) buildOctetRequest:(NSDictionary *)options
  61. taskId:(NSString *)taskId
  62. method:(NSString *)method
  63. url:(NSString *)url
  64. headers:(NSDictionary *)headers
  65. body:(NSString *)body
  66. onComplete:(void(^)(__weak NSURLRequest * req, long bodyLength))onComplete
  67. {
  68. // NSString * encodedUrl = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
  69. NSString * encodedUrl = url;
  70. // send request
  71. __block NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
  72. initWithURL:[NSURL URLWithString: encodedUrl]];
  73. __block NSMutableDictionary *mheaders = [[NSMutableDictionary alloc] initWithDictionary:[RNFetchBlobNetwork normalizeHeaders:headers]];
  74. // move heavy task to another thread
  75. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  76. NSMutableData * blobData;
  77. long size = -1;
  78. // if method is POST or PUT, convert data string format
  79. if([[method lowercaseString] isEqualToString:@"post"] || [[method lowercaseString] isEqualToString:@"put"]) {
  80. // generate octet-stream body
  81. if(body != nil) {
  82. __block NSString * cType = [[self class] getHeaderIgnoreCases:@"content-type" fromHeaders:mheaders];
  83. __block NSString * transferEncoding = [[self class] getHeaderIgnoreCases:@"transfer-encoding" fromHeaders:mheaders];
  84. // when headers does not contain a key named "content-type" (case ignored), use default content type
  85. if(cType == nil)
  86. {
  87. [mheaders setValue:@"application/octet-stream" forKey:@"Content-Type"];
  88. }
  89. // when body is a string contains file path prefix, try load file from the path
  90. if([body hasPrefix:FILE_PREFIX]) {
  91. __block NSString * orgPath = [body substringFromIndex:[FILE_PREFIX length]];
  92. orgPath = [RNFetchBlobFS getPathOfAsset:orgPath];
  93. if([orgPath hasPrefix:AL_PREFIX])
  94. {
  95. [RNFetchBlobFS readFile:orgPath encoding:@"utf8" resolver:nil rejecter:nil onComplete:^(NSData *content) {
  96. [request setHTTPBody:content];
  97. [request setHTTPMethod: method];
  98. [request setAllHTTPHeaderFields:mheaders];
  99. onComplete(request, [content length]);
  100. }];
  101. return;
  102. }
  103. size = [[[NSFileManager defaultManager] attributesOfItemAtPath:orgPath error:nil] fileSize];
  104. if(transferEncoding != nil && [[transferEncoding lowercaseString] isEqualToString:@"chunked"])
  105. {
  106. [request setHTTPBodyStream: [NSInputStream inputStreamWithFileAtPath:orgPath ]];
  107. }
  108. else
  109. {
  110. __block NSData * bodyBytes = [NSData dataWithContentsOfFile:orgPath ];
  111. [request setHTTPBody:bodyBytes];
  112. }
  113. }
  114. // otherwise convert it as BASE64 data string
  115. else {
  116. __block NSString * cType = [[self class]getHeaderIgnoreCases:@"content-type" fromHeaders:mheaders];
  117. // when content-type is application/octet* decode body string using BASE64 decoder
  118. if([[cType lowercaseString] hasPrefix:@"application/octet"] || [[cType lowercaseString] RNFBContainsString:@";base64"])
  119. {
  120. __block NSString * ncType = [[cType stringByReplacingOccurrencesOfString:@";base64" withString:@""]stringByReplacingOccurrencesOfString:@";BASE64" withString:@""];
  121. if([mheaders valueForKey:@"content-type"] != nil)
  122. [mheaders setValue:ncType forKey:@"content-type"];
  123. if([mheaders valueForKey:@"Content-Type"] != nil)
  124. [mheaders setValue:ncType forKey:@"Content-Type"];
  125. blobData = [[NSData alloc] initWithBase64EncodedString:body options:0];
  126. [request setHTTPBody:blobData];
  127. size = [blobData length];
  128. }
  129. // otherwise use the body as-is
  130. else
  131. {
  132. size = [body length];
  133. [request setHTTPBody: [body dataUsingEncoding:NSUTF8StringEncoding]];
  134. }
  135. }
  136. }
  137. }
  138. [request setHTTPMethod: method];
  139. [request setAllHTTPHeaderFields:mheaders];
  140. onComplete(request, size);
  141. });
  142. }
  143. +(void) buildFormBody:(NSArray *)form boundary:(NSString *)boundary onComplete:(void(^)(NSData * formData))onComplete
  144. {
  145. __block NSMutableData * formData = [[NSMutableData alloc] init];
  146. if(form == nil)
  147. onComplete(nil);
  148. else
  149. {
  150. __block int i = 0;
  151. __block int count = [form count];
  152. // a recursive block that builds multipart body asynchornously
  153. void __block (^getFieldData)(id field) = ^(id field)
  154. {
  155. NSString * name = [field valueForKey:@"name"];
  156. __block NSString * content = [field valueForKey:@"data"];
  157. NSString * contentType = [field valueForKey:@"type"];
  158. // skip when the form field `name` or `data` is empty
  159. if(content == nil || name == nil)
  160. {
  161. i++;
  162. getFieldData([form objectAtIndex:i]);
  163. RCTLogWarn(@"RNFetchBlob multipart request builder has found a field without `data` or `name` property, the field will be removed implicitly.", field);
  164. return;
  165. }
  166. contentType = contentType == nil ? @"application/octet-stream" : contentType;
  167. // field is a text field
  168. if([field valueForKey:@"filename"] == nil || content == [NSNull null]) {
  169. [formData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
  170. [formData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n", name] dataUsingEncoding:NSUTF8StringEncoding]];
  171. [formData appendData:[[NSString stringWithFormat:@"Content-Type: text/plain\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
  172. [formData appendData:[[NSString stringWithFormat:@"%@\r\n", content] dataUsingEncoding:NSUTF8StringEncoding]];
  173. }
  174. // field contains a file
  175. else {
  176. NSMutableData * blobData;
  177. if(content != nil)
  178. {
  179. // append data from file asynchronously
  180. if([content hasPrefix:FILE_PREFIX])
  181. {
  182. NSString * orgPath = [content substringFromIndex:[FILE_PREFIX length]];
  183. orgPath = [RNFetchBlobFS getPathOfAsset:orgPath];
  184. [RNFetchBlobFS readFile:orgPath encoding:@"utf8" resolver:nil rejecter:nil onComplete:^(NSData *content) {
  185. NSString * filename = [field valueForKey:@"filename"];
  186. [formData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
  187. [formData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", name, filename] dataUsingEncoding:NSUTF8StringEncoding]];
  188. [formData appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", contentType] dataUsingEncoding:NSUTF8StringEncoding]];
  189. [formData appendData:content];
  190. [formData appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
  191. i++;
  192. if(i < count)
  193. {
  194. __block NSDictionary * nextField = [form objectAtIndex:i];
  195. getFieldData(nextField);
  196. }
  197. else
  198. {
  199. onComplete(formData);
  200. getFieldData = nil;
  201. }
  202. }];
  203. return ;
  204. }
  205. else
  206. blobData = [[NSData alloc] initWithBase64EncodedString:content options:0];
  207. }
  208. NSString * filename = [field valueForKey:@"filename"];
  209. [formData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
  210. [formData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", name, filename] dataUsingEncoding:NSUTF8StringEncoding]];
  211. [formData appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", contentType] dataUsingEncoding:NSUTF8StringEncoding]];
  212. [formData appendData:blobData];
  213. [formData appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
  214. blobData = nil;
  215. }
  216. i++;
  217. if(i < count)
  218. {
  219. __block NSDictionary * nextField = [form objectAtIndex:i];
  220. getFieldData(nextField);
  221. }
  222. else
  223. {
  224. onComplete(formData);
  225. getFieldData = nil;
  226. }
  227. };
  228. __block NSDictionary * nextField = [form objectAtIndex:i];
  229. getFieldData(nextField);
  230. }
  231. }
  232. +(NSString *) getHeaderIgnoreCases:(NSString *)field fromHeaders:(NSMutableArray *) headers {
  233. NSString * normalCase = [headers valueForKey:field];
  234. NSString * ignoredCase = [headers valueForKey:[field lowercaseString]];
  235. if( normalCase != nil)
  236. return normalCase;
  237. else
  238. return ignoredCase;
  239. }
  240. @end