MKNetworkOperation.m 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300
  1. //
  2. // MKNetworkOperation.m
  3. // MKNetworkKit
  4. //
  5. // Created by Mugunth Kumar (@mugunthkumar) on 11/11/11.
  6. // Copyright (C) 2011-2020 by Steinlogic Consulting and Training Pte Ltd
  7. // Permission is hereby granted, free of charge, to any person obtaining a copy
  8. // of this software and associated documentation files (the "Software"), to deal
  9. // in the Software without restriction, including without limitation the rights
  10. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. // copies of the Software, and to permit persons to whom the Software is
  12. // furnished to do so, subject to the following conditions:
  13. //
  14. // The above copyright notice and this permission notice shall be included in
  15. // all copies or substantial portions of the Software.
  16. //
  17. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23. // THE SOFTWARE.
  24. #import "MKNetworkKit.h"
  25. #ifdef __OBJC_GC__
  26. #error MKNetworkKit does not support Objective-C Garbage Collection
  27. #endif
  28. #if ! __has_feature(objc_arc)
  29. #error MKNetworkKit is ARC only. Either turn on ARC for the project or use -fobjc-arc flag
  30. #endif
  31. @interface MKNetworkOperation (/*Private Methods*/)
  32. @property (strong, nonatomic) NSURLConnection *connection;
  33. @property (strong, nonatomic) NSString *uniqueId;
  34. @property (strong, nonatomic) NSMutableURLRequest *request;
  35. @property (strong, nonatomic) NSHTTPURLResponse *response;
  36. @property (strong, nonatomic) NSMutableDictionary *fieldsToBePosted;
  37. @property (strong, nonatomic) NSMutableArray *filesToBePosted;
  38. @property (strong, nonatomic) NSMutableArray *dataToBePosted;
  39. @property (strong, nonatomic) NSString *username;
  40. @property (strong, nonatomic) NSString *password;
  41. @property (nonatomic, strong) NSMutableArray *responseBlocks;
  42. @property (nonatomic, strong) NSMutableArray *errorBlocks;
  43. @property (nonatomic, assign) MKNetworkOperationState state;
  44. @property (nonatomic, assign) BOOL isCancelled;
  45. @property (strong, nonatomic) NSMutableData *mutableData;
  46. @property (assign, nonatomic) NSUInteger downloadedDataSize;
  47. @property (nonatomic, strong) NSMutableArray *uploadProgressChangedHandlers;
  48. @property (nonatomic, strong) NSMutableArray *downloadProgressChangedHandlers;
  49. @property (nonatomic, copy) MKNKEncodingBlock postDataEncodingHandler;
  50. @property (nonatomic, assign) NSInteger startPosition;
  51. @property (nonatomic, strong) NSMutableArray *downloadStreams;
  52. @property (nonatomic, strong) NSData *cachedResponse;
  53. @property (nonatomic, copy) MKNKResponseBlock cacheHandlingBlock;
  54. #if TARGET_OS_IPHONE
  55. @property (nonatomic, assign) UIBackgroundTaskIdentifier backgroundTaskId;
  56. #endif
  57. @property (strong, nonatomic) NSError *error;
  58. - (id)initWithURLString:(NSString *)aURLString
  59. params:(NSMutableDictionary *)body
  60. httpMethod:(NSString *)method;
  61. -(NSData*) bodyData;
  62. -(NSString*) encodedPostDataString;
  63. - (void) showLocalNotification;
  64. - (void) endBackgroundTask;
  65. @end
  66. @implementation MKNetworkOperation
  67. @synthesize postDataEncodingHandler = _postDataEncodingHandler;
  68. @synthesize stringEncoding = _stringEncoding;
  69. @dynamic freezable;
  70. @synthesize uniqueId = _uniqueId; // freezable operations have a unique id
  71. @synthesize connection = _connection;
  72. @synthesize request = _request;
  73. @synthesize response = _response;
  74. @synthesize fieldsToBePosted = _fieldsToBePosted;
  75. @synthesize filesToBePosted = _filesToBePosted;
  76. @synthesize dataToBePosted = _dataToBePosted;
  77. @synthesize username = _username;
  78. @synthesize password = _password;
  79. @synthesize clientCertificate = _clientCertificate;
  80. @synthesize authHandler = _authHandler;
  81. @synthesize operationStateChangedHandler = _operationStateChangedHandler;
  82. @synthesize responseBlocks = _responseBlocks;
  83. @synthesize errorBlocks = _errorBlocks;
  84. @synthesize isCancelled = _isCancelled;
  85. @synthesize mutableData = _mutableData;
  86. @synthesize downloadedDataSize = _downloadedDataSize;
  87. @synthesize uploadProgressChangedHandlers = _uploadProgressChangedHandlers;
  88. @synthesize downloadProgressChangedHandlers = _downloadProgressChangedHandlers;
  89. @synthesize downloadStreams = _downloadStreams;
  90. @synthesize cachedResponse = _cachedResponse;
  91. @synthesize cacheHandlingBlock = _cacheHandlingBlock;
  92. @synthesize credentialPersistence = _credentialPersistence;
  93. @synthesize startPosition = _startPosition;
  94. #if TARGET_OS_IPHONE
  95. @synthesize backgroundTaskId = _backgroundTaskId;
  96. @synthesize localNotification = localNotification_;
  97. @synthesize shouldShowLocalNotificationOnError = shouldShowLocalNotificationOnError_;
  98. #endif
  99. @synthesize cacheHeaders = _cacheHeaders;
  100. @synthesize error = _error;
  101. // A RESTful service should always return the same response for a given URL and it's parameters.
  102. // this means if these values are correct, you can cache the responses
  103. // This is another reason why we check only GET methods.
  104. // even if URL and others are same, POST, DELETE, PUT methods should not be cached and should not be treated equal.
  105. -(BOOL) isCacheable {
  106. return [self.request.HTTPMethod isEqualToString:@"GET"];
  107. }
  108. //===========================================================
  109. // + (BOOL)automaticallyNotifiesObserversForKey:
  110. //
  111. //===========================================================
  112. + (BOOL)automaticallyNotifiesObserversForKey: (NSString *)theKey
  113. {
  114. BOOL automatic;
  115. if ([theKey isEqualToString:@"postDataEncoding"]) {
  116. automatic = NO;
  117. } else {
  118. automatic = [super automaticallyNotifiesObserversForKey:theKey];
  119. }
  120. return automatic;
  121. }
  122. //===========================================================
  123. // postDataEncoding
  124. //===========================================================
  125. - (MKNKPostDataEncodingType)postDataEncoding
  126. {
  127. return _postDataEncoding;
  128. }
  129. - (void)setPostDataEncoding:(MKNKPostDataEncodingType)aPostDataEncoding
  130. {
  131. if (_postDataEncoding != aPostDataEncoding) {
  132. [self willChangeValueForKey:@"postDataEncoding"];
  133. _postDataEncoding = aPostDataEncoding;
  134. NSString *charset = (__bridge NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding(self.stringEncoding));
  135. switch (self.postDataEncoding) {
  136. case MKNKPostDataEncodingTypeURL: {
  137. [self.request setValue:
  138. [NSString stringWithFormat:@"application/x-www-form-urlencoded; charset=%@", charset]
  139. forHTTPHeaderField:@"Content-Type"];
  140. }
  141. break;
  142. case MKNKPostDataEncodingTypeJSON: {
  143. if(NSClassFromString(@"NSJSONSerialization")) {
  144. [self.request setValue:
  145. [NSString stringWithFormat:@"application/json; charset=%@", charset]
  146. forHTTPHeaderField:@"Content-Type"];
  147. }
  148. else {
  149. [self.request setValue:
  150. [NSString stringWithFormat:@"application/x-www-form-urlencoded; charset=%@", charset]
  151. forHTTPHeaderField:@"Content-Type"];
  152. }
  153. }
  154. break;
  155. case MKNKPostDataEncodingTypePlist: {
  156. [self.request setValue:
  157. [NSString stringWithFormat:@"application/x-plist; charset=%@", charset]
  158. forHTTPHeaderField:@"Content-Type"];
  159. }
  160. default:
  161. break;
  162. }
  163. [self didChangeValueForKey:@"postDataEncoding"];
  164. }
  165. }
  166. -(NSString*) encodedPostDataString {
  167. NSString *returnValue = @"";
  168. if(self.postDataEncodingHandler)
  169. returnValue = self.postDataEncodingHandler(self.fieldsToBePosted);
  170. else if(self.postDataEncoding == MKNKPostDataEncodingTypeURL)
  171. returnValue = [self.fieldsToBePosted urlEncodedKeyValueString];
  172. else if(self.postDataEncoding == MKNKPostDataEncodingTypeJSON)
  173. returnValue = [self.fieldsToBePosted jsonEncodedKeyValueString];
  174. else if(self.postDataEncoding == MKNKPostDataEncodingTypePlist)
  175. returnValue = [self.fieldsToBePosted plistEncodedKeyValueString];
  176. return returnValue;
  177. }
  178. -(void) setCustomPostDataEncodingHandler:(MKNKEncodingBlock) postDataEncodingHandler forType:(NSString*) contentType {
  179. NSString *charset = (__bridge NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding(self.stringEncoding));
  180. self.postDataEncoding = MKNKPostDataEncodingTypeCustom;
  181. self.postDataEncodingHandler = postDataEncodingHandler;
  182. [self.request setValue:
  183. [NSString stringWithFormat:@"%@; charset=%@", contentType, charset]
  184. forHTTPHeaderField:@"Content-Type"];
  185. }
  186. //===========================================================
  187. // freezable
  188. //===========================================================
  189. - (BOOL)freezable
  190. {
  191. return _freezable;
  192. }
  193. -(NSString*) url {
  194. return [[self.request URL] absoluteString];
  195. }
  196. -(NSURLRequest*) readonlyRequest {
  197. return [self.request copy];
  198. }
  199. -(NSHTTPURLResponse*) readonlyResponse {
  200. return [self.response copy];
  201. }
  202. - (NSDictionary *) readonlyPostDictionary {
  203. return [self.fieldsToBePosted copy];
  204. }
  205. -(NSString*) HTTPMethod {
  206. return self.request.HTTPMethod;
  207. }
  208. -(NSInteger) HTTPStatusCode {
  209. if(self.response)
  210. return self.response.statusCode;
  211. else
  212. return 0;
  213. }
  214. - (void)setFreezable:(BOOL)flag
  215. {
  216. // get method cannot be frozen.
  217. // No point in freezing a method that doesn't change server state.
  218. if([self.request.HTTPMethod isEqualToString:@"GET"] && flag) return;
  219. _freezable = flag;
  220. if(_freezable && self.uniqueId == nil)
  221. self.uniqueId = [NSString uniqueString];
  222. }
  223. -(BOOL) isEqual:(id)object {
  224. if([self.request.HTTPMethod isEqualToString:@"GET"] || [self.request.HTTPMethod isEqualToString:@"HEAD"]) {
  225. MKNetworkOperation *anotherObject = (MKNetworkOperation*) object;
  226. return ([[self uniqueIdentifier] isEqualToString:[anotherObject uniqueIdentifier]]);
  227. }
  228. return NO;
  229. }
  230. -(NSString*) uniqueIdentifier {
  231. NSMutableString *str = [NSMutableString stringWithFormat:@"%@ %@", self.request.HTTPMethod, self.url];
  232. if(self.username || self.password) {
  233. [str appendFormat:@" [%@:%@]",
  234. self.username ? self.username : @"",
  235. self.password ? self.password : @""];
  236. }
  237. if(self.freezable) {
  238. [str appendString:self.uniqueId];
  239. }
  240. return [str md5];
  241. }
  242. -(BOOL) isCachedResponse {
  243. return self.cachedResponse != nil;
  244. }
  245. -(void) notifyCache {
  246. if(![self isCacheable]) return;
  247. if(!([self.response statusCode] >= 200 && [self.response statusCode] < 300)) return;
  248. if(![self isCancelled])
  249. self.cacheHandlingBlock(self);
  250. }
  251. -(MKNetworkOperationState) state {
  252. return _state;
  253. }
  254. -(void) setState:(MKNetworkOperationState)newState {
  255. switch (newState) {
  256. case MKNetworkOperationStateReady:
  257. [self willChangeValueForKey:@"isReady"];
  258. break;
  259. case MKNetworkOperationStateExecuting:
  260. [self willChangeValueForKey:@"isReady"];
  261. [self willChangeValueForKey:@"isExecuting"];
  262. break;
  263. case MKNetworkOperationStateFinished:
  264. [self willChangeValueForKey:@"isExecuting"];
  265. [self willChangeValueForKey:@"isFinished"];
  266. break;
  267. }
  268. _state = newState;
  269. switch (newState) {
  270. case MKNetworkOperationStateReady:
  271. [self didChangeValueForKey:@"isReady"];
  272. break;
  273. case MKNetworkOperationStateExecuting:
  274. [self didChangeValueForKey:@"isReady"];
  275. [self didChangeValueForKey:@"isExecuting"];
  276. break;
  277. case MKNetworkOperationStateFinished:
  278. [self didChangeValueForKey:@"isExecuting"];
  279. [self didChangeValueForKey:@"isFinished"];
  280. break;
  281. }
  282. if(self.operationStateChangedHandler) {
  283. self.operationStateChangedHandler(newState);
  284. }
  285. }
  286. - (void)encodeWithCoder:(NSCoder *)encoder
  287. {
  288. [encoder encodeInteger:self.stringEncoding forKey:@"stringEncoding"];
  289. [encoder encodeInteger:_postDataEncoding forKey:@"postDataEncoding"];
  290. [encoder encodeObject:self.uniqueId forKey:@"uniqueId"];
  291. [encoder encodeObject:self.request forKey:@"request"];
  292. [encoder encodeObject:self.response forKey:@"response"];
  293. [encoder encodeObject:self.fieldsToBePosted forKey:@"fieldsToBePosted"];
  294. [encoder encodeObject:self.filesToBePosted forKey:@"filesToBePosted"];
  295. [encoder encodeObject:self.dataToBePosted forKey:@"dataToBePosted"];
  296. [encoder encodeObject:self.username forKey:@"username"];
  297. [encoder encodeObject:self.password forKey:@"password"];
  298. [encoder encodeObject:self.clientCertificate forKey:@"clientCertificate"];
  299. self.state = MKNetworkOperationStateReady;
  300. [encoder encodeInt32:_state forKey:@"state"];
  301. [encoder encodeBool:self.isCancelled forKey:@"isCancelled"];
  302. [encoder encodeObject:self.mutableData forKey:@"mutableData"];
  303. [encoder encodeInteger:self.downloadedDataSize forKey:@"downloadedDataSize"];
  304. [encoder encodeObject:self.downloadStreams forKey:@"downloadStreams"];
  305. [encoder encodeInteger:self.startPosition forKey:@"startPosition"];
  306. [encoder encodeInteger:self.credentialPersistence forKey:@"credentialPersistence"];
  307. }
  308. - (id)initWithCoder:(NSCoder *)decoder
  309. {
  310. self = [super init];
  311. if (self) {
  312. [self setStringEncoding:[decoder decodeIntegerForKey:@"stringEncoding"]];
  313. _postDataEncoding = [decoder decodeIntegerForKey:@"postDataEncoding"];
  314. self.request = [decoder decodeObjectForKey:@"request"];
  315. self.uniqueId = [decoder decodeObjectForKey:@"uniqueId"];
  316. self.response = [decoder decodeObjectForKey:@"response"];
  317. self.fieldsToBePosted = [decoder decodeObjectForKey:@"fieldsToBePosted"];
  318. self.filesToBePosted = [decoder decodeObjectForKey:@"filesToBePosted"];
  319. self.dataToBePosted = [decoder decodeObjectForKey:@"dataToBePosted"];
  320. self.username = [decoder decodeObjectForKey:@"username"];
  321. self.password = [decoder decodeObjectForKey:@"password"];
  322. self.clientCertificate = [decoder decodeObjectForKey:@"clientCertificate"];
  323. [self setState:[decoder decodeInt32ForKey:@"state"]];
  324. self.isCancelled = [decoder decodeBoolForKey:@"isCancelled"];
  325. self.mutableData = [decoder decodeObjectForKey:@"mutableData"];
  326. self.downloadedDataSize = [decoder decodeIntegerForKey:@"downloadedDataSize"];
  327. self.downloadStreams = [decoder decodeObjectForKey:@"downloadStreams"];
  328. self.startPosition = [decoder decodeIntegerForKey:@"startPosition"];
  329. self.credentialPersistence = [decoder decodeIntegerForKey:@"credentialPersistence"];
  330. }
  331. return self;
  332. }
  333. - (id)copyWithZone:(NSZone *)zone
  334. {
  335. MKNetworkOperation *theCopy = [[[self class] allocWithZone:zone] init]; // use designated initializer
  336. theCopy.postDataEncoding = _postDataEncoding;
  337. [theCopy setStringEncoding:self.stringEncoding];
  338. [theCopy setUniqueId:[self.uniqueId copy]];
  339. [theCopy setConnection:[self.connection copy]];
  340. [theCopy setRequest:[self.request copy]];
  341. [theCopy setResponse:[self.response copy]];
  342. [theCopy setFieldsToBePosted:[self.fieldsToBePosted copy]];
  343. [theCopy setFilesToBePosted:[self.filesToBePosted copy]];
  344. [theCopy setDataToBePosted:[self.dataToBePosted copy]];
  345. [theCopy setUsername:[self.username copy]];
  346. [theCopy setPassword:[self.password copy]];
  347. [theCopy setClientCertificate:[self.clientCertificate copy]];
  348. [theCopy setResponseBlocks:[self.responseBlocks copy]];
  349. [theCopy setErrorBlocks:[self.errorBlocks copy]];
  350. [theCopy setState:self.state];
  351. [theCopy setIsCancelled:self.isCancelled];
  352. [theCopy setMutableData:[self.mutableData copy]];
  353. [theCopy setDownloadedDataSize:self.downloadedDataSize];
  354. [theCopy setUploadProgressChangedHandlers:[self.uploadProgressChangedHandlers copy]];
  355. [theCopy setDownloadProgressChangedHandlers:[self.downloadProgressChangedHandlers copy]];
  356. [theCopy setDownloadStreams:[self.downloadStreams copy]];
  357. [theCopy setCachedResponse:[self.cachedResponse copy]];
  358. [theCopy setCacheHandlingBlock:self.cacheHandlingBlock];
  359. [theCopy setStartPosition:self.startPosition];
  360. [theCopy setCredentialPersistence:self.credentialPersistence];
  361. return theCopy;
  362. }
  363. -(void) dealloc {
  364. [_connection cancel];
  365. _connection = nil;
  366. }
  367. -(void) updateHandlersFromOperation:(MKNetworkOperation*) operation {
  368. [self.responseBlocks addObjectsFromArray:operation.responseBlocks];
  369. [self.errorBlocks addObjectsFromArray:operation.errorBlocks];
  370. [self.uploadProgressChangedHandlers addObjectsFromArray:operation.uploadProgressChangedHandlers];
  371. [self.downloadProgressChangedHandlers addObjectsFromArray:operation.downloadProgressChangedHandlers];
  372. [self.downloadStreams addObjectsFromArray:operation.downloadStreams];
  373. }
  374. -(void) setCachedData:(NSData*) cachedData {
  375. self.cachedResponse = cachedData;
  376. [self operationSucceeded];
  377. }
  378. -(void) updateOperationBasedOnPreviousHeaders:(NSMutableDictionary*) headers {
  379. NSString *lastModified = [headers objectForKey:@"Last-Modified"];
  380. NSString *eTag = [headers objectForKey:@"ETag"];
  381. if(lastModified) {
  382. [self.request setValue:lastModified forHTTPHeaderField:@"IF-MODIFIED-SINCE"];
  383. }
  384. if(eTag) {
  385. [self.request setValue:eTag forHTTPHeaderField:@"IF-NONE-MATCH"];
  386. }
  387. }
  388. -(void) setUsername:(NSString*) username password:(NSString*) password {
  389. self.username = username;
  390. self.password = password;
  391. }
  392. -(void) setUsername:(NSString*) username password:(NSString*) password basicAuth:(BOOL) bYesOrNo {
  393. [self setUsername:username password:password];
  394. NSString *base64EncodedString = [[[NSString stringWithFormat:@"%@:%@", self.username, self.password] dataUsingEncoding:NSUTF8StringEncoding] base64EncodedString];
  395. [self setAuthorizationHeaderValue:base64EncodedString forAuthType:@"Basic"];
  396. }
  397. -(void) onCompletion:(MKNKResponseBlock) response onError:(MKNKErrorBlock) error {
  398. [self.responseBlocks addObject:[response copy]];
  399. [self.errorBlocks addObject:[error copy]];
  400. }
  401. -(void) onUploadProgressChanged:(MKNKProgressBlock) uploadProgressBlock {
  402. [self.uploadProgressChangedHandlers addObject:[uploadProgressBlock copy]];
  403. }
  404. -(void) onDownloadProgressChanged:(MKNKProgressBlock) downloadProgressBlock {
  405. [self.downloadProgressChangedHandlers addObject:[downloadProgressBlock copy]];
  406. }
  407. -(void) setUploadStream:(NSInputStream*) inputStream {
  408. #warning Method not tested yet.
  409. self.request.HTTPBodyStream = inputStream;
  410. }
  411. -(void) addDownloadStream:(NSOutputStream*) outputStream {
  412. [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
  413. [self.downloadStreams addObject:outputStream];
  414. }
  415. - (id)initWithURLString:(NSString *)aURLString
  416. params:(NSMutableDictionary *)params
  417. httpMethod:(NSString *)method
  418. {
  419. if((self = [super init])) {
  420. self.responseBlocks = [NSMutableArray array];
  421. self.errorBlocks = [NSMutableArray array];
  422. self.filesToBePosted = [NSMutableArray array];
  423. self.dataToBePosted = [NSMutableArray array];
  424. self.fieldsToBePosted = [NSMutableDictionary dictionary];
  425. self.uploadProgressChangedHandlers = [NSMutableArray array];
  426. self.downloadProgressChangedHandlers = [NSMutableArray array];
  427. self.downloadStreams = [NSMutableArray array];
  428. self.credentialPersistence = NSURLCredentialPersistenceForSession;
  429. NSURL *finalURL = nil;
  430. if(params)
  431. self.fieldsToBePosted = params;
  432. self.stringEncoding = NSUTF8StringEncoding; // use a delegate to get these values later
  433. if ([method isEqualToString:@"GET"])
  434. self.cacheHeaders = [NSMutableDictionary dictionary];
  435. if (([method isEqualToString:@"GET"] ||
  436. [method isEqualToString:@"DELETE"]) && (params && [params count] > 0)) {
  437. finalURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@?%@", aURLString,
  438. [self encodedPostDataString]]];
  439. } else {
  440. finalURL = [NSURL URLWithString:aURLString];
  441. }
  442. self.request = [NSMutableURLRequest requestWithURL:finalURL
  443. cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
  444. timeoutInterval:kMKNetworkKitRequestTimeOutInSeconds];
  445. [self.request setHTTPMethod:method];
  446. [self.request setValue:[NSString stringWithFormat:@"%@, en-us",
  447. [[NSLocale preferredLanguages] componentsJoinedByString:@", "]
  448. ] forHTTPHeaderField:@"Accept-Language"];
  449. if (([method isEqualToString:@"POST"] ||
  450. [method isEqualToString:@"PUT"]) && (params && [params count] > 0)) {
  451. self.postDataEncoding = MKNKPostDataEncodingTypeURL;
  452. }
  453. self.state = MKNetworkOperationStateReady;
  454. }
  455. return self;
  456. }
  457. -(void) addHeaders:(NSDictionary*) headersDictionary {
  458. [headersDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
  459. [self.request addValue:obj forHTTPHeaderField:key];
  460. }];
  461. }
  462. -(void) setAuthorizationHeaderValue:(NSString*) token forAuthType:(NSString*) authType {
  463. [self.request setValue:[NSString stringWithFormat:@"%@ %@", authType, token]
  464. forHTTPHeaderField:@"Authorization"];
  465. }
  466. /*
  467. Printing a MKNetworkOperation object is printed in curl syntax
  468. */
  469. -(NSString*) description {
  470. NSMutableString *displayString = [NSMutableString stringWithFormat:@"%@\nRequest\n-------\n%@",
  471. [[NSDate date] descriptionWithLocale:[NSLocale currentLocale]],
  472. [self curlCommandLineString]];
  473. NSString *responseString = [self responseString];
  474. if([responseString length] > 0) {
  475. [displayString appendFormat:@"\n--------\nResponse\n--------\n%@\n", responseString];
  476. }
  477. return displayString;
  478. }
  479. -(NSString*) curlCommandLineString
  480. {
  481. __block NSMutableString *displayString = [NSMutableString stringWithFormat:@"curl -X %@", self.request.HTTPMethod];
  482. if([self.filesToBePosted count] == 0 && [self.dataToBePosted count] == 0) {
  483. [[self.request allHTTPHeaderFields] enumerateKeysAndObjectsUsingBlock:^(id key, id val, BOOL *stop)
  484. {
  485. [displayString appendFormat:@" -H \"%@: %@\"", key, val];
  486. }];
  487. }
  488. [displayString appendFormat:@" \"%@\"", self.url];
  489. if ([self.request.HTTPMethod isEqualToString:@"POST"] || [self.request.HTTPMethod isEqualToString:@"PUT"]) {
  490. NSString *option = [self.filesToBePosted count] == 0 ? @"-d" : @"-F";
  491. if(self.postDataEncoding == MKNKPostDataEncodingTypeURL) {
  492. [self.fieldsToBePosted enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
  493. [displayString appendFormat:@" %@ \"%@=%@\"", option, key, obj];
  494. }];
  495. } else {
  496. [displayString appendFormat:@" -d \"%@\"", [self encodedPostDataString]];
  497. }
  498. [self.filesToBePosted enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  499. NSDictionary *thisFile = (NSDictionary*) obj;
  500. [displayString appendFormat:@" -F \"%@=@%@;type=%@\"", [thisFile objectForKey:@"name"],
  501. [thisFile objectForKey:@"filepath"], [thisFile objectForKey:@"mimetype"]];
  502. }];
  503. /* Not sure how to do this via curl
  504. [self.dataToBePosted enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  505. NSDictionary *thisData = (NSDictionary*) obj;
  506. [displayString appendFormat:@" --data-binary \"%@\"", [thisData objectForKey:@"data"]];
  507. }];*/
  508. }
  509. return displayString;
  510. }
  511. -(void) addData:(NSData*) data forKey:(NSString*) key {
  512. [self addData:data forKey:key mimeType:@"application/octet-stream" fileName:@"file"];
  513. }
  514. -(void) addData:(NSData*) data forKey:(NSString*) key mimeType:(NSString*) mimeType fileName:(NSString*) fileName {
  515. if ([self.request.HTTPMethod isEqualToString:@"GET"]) {
  516. [self.request setHTTPMethod:@"POST"];
  517. }
  518. NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
  519. data, @"data",
  520. key, @"name",
  521. mimeType, @"mimetype",
  522. fileName, @"filename",
  523. nil];
  524. [self.dataToBePosted addObject:dict];
  525. }
  526. -(void) addFile:(NSString*) filePath forKey:(NSString*) key {
  527. [self addFile:filePath forKey:key mimeType:@"application/octet-stream"];
  528. }
  529. -(void) addFile:(NSString*) filePath forKey:(NSString*) key mimeType:(NSString*) mimeType {
  530. if ([self.request.HTTPMethod isEqualToString:@"GET"]) {
  531. [self.request setHTTPMethod:@"POST"];
  532. }
  533. NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
  534. filePath, @"filepath",
  535. key, @"name",
  536. mimeType, @"mimetype",
  537. nil];
  538. [self.filesToBePosted addObject:dict];
  539. }
  540. -(NSData*) bodyData {
  541. if([self.filesToBePosted count] == 0 && [self.dataToBePosted count] == 0) {
  542. return [[self encodedPostDataString] dataUsingEncoding:self.stringEncoding];
  543. }
  544. NSString *boundary = @"0xKhTmLbOuNdArY";
  545. NSMutableData *body = [NSMutableData data];
  546. __block NSUInteger postLength = 0;
  547. [self.fieldsToBePosted enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
  548. NSString *thisFieldString = [NSString stringWithFormat:
  549. @"--%@\r\nContent-Disposition: form-data; name=\"%@\"\r\n\r\n%@",
  550. boundary, key, obj];
  551. [body appendData:[thisFieldString dataUsingEncoding:[self stringEncoding]]];
  552. [body appendData:[@"\r\n" dataUsingEncoding:[self stringEncoding]]];
  553. }];
  554. [self.filesToBePosted enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  555. NSDictionary *thisFile = (NSDictionary*) obj;
  556. NSString *thisFieldString = [NSString stringWithFormat:
  557. @"--%@\r\nContent-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\nContent-Type: %@\r\nContent-Transfer-Encoding: binary\r\n\r\n",
  558. boundary,
  559. [thisFile objectForKey:@"name"],
  560. [[thisFile objectForKey:@"filepath"] lastPathComponent],
  561. [thisFile objectForKey:@"mimetype"]];
  562. [body appendData:[thisFieldString dataUsingEncoding:[self stringEncoding]]];
  563. [body appendData: [NSData dataWithContentsOfFile:[thisFile objectForKey:@"filepath"]]];
  564. [body appendData:[@"\r\n" dataUsingEncoding:[self stringEncoding]]];
  565. }];
  566. [self.dataToBePosted enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  567. NSDictionary *thisDataObject = (NSDictionary*) obj;
  568. NSString *thisFieldString = [NSString stringWithFormat:
  569. @"--%@\r\nContent-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\nContent-Type: %@\r\nContent-Transfer-Encoding: binary\r\n\r\n",
  570. boundary,
  571. [thisDataObject objectForKey:@"name"],
  572. [thisDataObject objectForKey:@"filename"],
  573. [thisDataObject objectForKey:@"mimetype"]];
  574. [body appendData:[thisFieldString dataUsingEncoding:[self stringEncoding]]];
  575. [body appendData:[thisDataObject objectForKey:@"data"]];
  576. [body appendData:[@"\r\n" dataUsingEncoding:[self stringEncoding]]];
  577. }];
  578. if (postLength >= 1)
  579. [self.request setValue:[NSString stringWithFormat:@"%lu", postLength] forHTTPHeaderField:@"content-length"];
  580. [body appendData: [[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:self.stringEncoding]];
  581. NSString *charset = (__bridge NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding(self.stringEncoding));
  582. if(([self.filesToBePosted count] > 0) || ([self.dataToBePosted count] > 0)) {
  583. [self.request setValue:[NSString stringWithFormat:@"multipart/form-data; charset=%@; boundary=%@", charset, boundary]
  584. forHTTPHeaderField:@"Content-Type"];
  585. [self.request setValue:[NSString stringWithFormat:@"%d", [body length]] forHTTPHeaderField:@"Content-Length"];
  586. }
  587. return body;
  588. }
  589. -(void) setCacheHandler:(MKNKResponseBlock) cacheHandler {
  590. self.cacheHandlingBlock = cacheHandler;
  591. }
  592. #pragma mark -
  593. #pragma Main method
  594. -(void) main {
  595. @autoreleasepool {
  596. [self start];
  597. }
  598. }
  599. -(void) endBackgroundTask {
  600. #if TARGET_OS_IPHONE
  601. dispatch_async(dispatch_get_main_queue(), ^{
  602. if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
  603. [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskId];
  604. self.backgroundTaskId = UIBackgroundTaskInvalid;
  605. }
  606. });
  607. #endif
  608. }
  609. - (void) start
  610. {
  611. #if TARGET_OS_IPHONE
  612. self.backgroundTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
  613. dispatch_async(dispatch_get_main_queue(), ^{
  614. if (self.backgroundTaskId != UIBackgroundTaskInvalid)
  615. {
  616. [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskId];
  617. self.backgroundTaskId = UIBackgroundTaskInvalid;
  618. [self cancel];
  619. }
  620. });
  621. }];
  622. #endif
  623. if(!self.isCancelled) {
  624. if (([self.request.HTTPMethod isEqualToString:@"POST"] || [self.request.HTTPMethod isEqualToString:@"PUT"]) && !self.request.HTTPBodyStream) {
  625. [self.request setHTTPBody:[self bodyData]];
  626. }
  627. dispatch_async(dispatch_get_main_queue(), ^{
  628. self.connection = [[NSURLConnection alloc] initWithRequest:self.request
  629. delegate:self
  630. startImmediately:NO];
  631. [self.connection scheduleInRunLoop:[NSRunLoop currentRunLoop]
  632. forMode:NSRunLoopCommonModes];
  633. [self.connection start];
  634. });
  635. self.state = MKNetworkOperationStateExecuting;
  636. }
  637. else {
  638. self.state = MKNetworkOperationStateFinished;
  639. [self endBackgroundTask];
  640. }
  641. }
  642. #pragma -
  643. #pragma mark NSOperation stuff
  644. - (BOOL)isConcurrent
  645. {
  646. return YES;
  647. }
  648. - (BOOL)isReady {
  649. return (self.state == MKNetworkOperationStateReady);
  650. }
  651. - (BOOL)isFinished
  652. {
  653. return (self.state == MKNetworkOperationStateFinished);
  654. }
  655. - (BOOL)isExecuting {
  656. return (self.state == MKNetworkOperationStateExecuting);
  657. }
  658. -(void) cancel {
  659. if([self isFinished])
  660. return;
  661. @synchronized(self) {
  662. self.isCancelled = YES;
  663. [self.connection cancel];
  664. [self.responseBlocks removeAllObjects];
  665. self.responseBlocks = nil;
  666. [self.errorBlocks removeAllObjects];
  667. self.errorBlocks = nil;
  668. [self.uploadProgressChangedHandlers removeAllObjects];
  669. self.uploadProgressChangedHandlers = nil;
  670. [self.downloadProgressChangedHandlers removeAllObjects];
  671. self.downloadProgressChangedHandlers = nil;
  672. for(NSOutputStream *stream in self.downloadStreams)
  673. [stream close];
  674. [self.downloadStreams removeAllObjects];
  675. self.downloadStreams = nil;
  676. self.authHandler = nil;
  677. self.mutableData = nil;
  678. self.downloadedDataSize = 0;
  679. self.cacheHandlingBlock = nil;
  680. if(self.state == MKNetworkOperationStateExecuting)
  681. self.state = MKNetworkOperationStateFinished; // This notifies the queue and removes the operation.
  682. // if the operation is not removed, the spinner continues to spin, not a good UX
  683. [self endBackgroundTask];
  684. }
  685. [super cancel];
  686. }
  687. #pragma mark -
  688. #pragma mark NSURLConnection delegates
  689. - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
  690. self.state = MKNetworkOperationStateFinished;
  691. self.mutableData = nil;
  692. self.downloadedDataSize = 0;
  693. for(NSOutputStream *stream in self.downloadStreams)
  694. [stream close];
  695. [self operationFailedWithError:error];
  696. [self endBackgroundTask];
  697. }
  698. - (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
  699. if ([challenge previousFailureCount] == 0) {
  700. if (((challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodDefault) ||
  701. (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic) ||
  702. (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPDigest) ||
  703. (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodNTLM)) &&
  704. (self.username && self.password))
  705. {
  706. // for NTLM, we will assume user name to be of the form "domain\\username"
  707. NSURLCredential *credential = [NSURLCredential credentialWithUser:self.username
  708. password:self.password
  709. persistence:self.credentialPersistence];
  710. [challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
  711. }
  712. else if ((challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate) && self.clientCertificate) {
  713. NSData *certData = [[NSData alloc] initWithContentsOfFile:self.clientCertificate];
  714. #warning method not implemented. Don't use client certicate authentication for now.
  715. SecIdentityRef myIdentity = nil; // ???
  716. SecCertificateRef myCert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);
  717. SecCertificateRef certArray[1] = { myCert };
  718. CFArrayRef myCerts = CFArrayCreate(NULL, (void *)certArray, 1, NULL);
  719. CFRelease(myCert);
  720. NSURLCredential *credential = [NSURLCredential credentialWithIdentity:myIdentity
  721. certificates:(__bridge NSArray *)myCerts
  722. persistence:NSURLCredentialPersistencePermanent];
  723. CFRelease(myCerts);
  724. [challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
  725. }
  726. else if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
  727. #warning method not tested. proceed at your own risk
  728. SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
  729. SecTrustResultType result;
  730. SecTrustEvaluate(serverTrust, &result);
  731. if(result == kSecTrustResultProceed) {
  732. [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
  733. }
  734. else if(result == kSecTrustResultConfirm) {
  735. // ask user
  736. BOOL userOkWithWrongCert = NO; // (ACTUALLY CHEAT., DON'T BE A F***ING BROWSER, USERS ALWAYS TAP YES WHICH IS RISKY)
  737. if(userOkWithWrongCert) {
  738. // Cert not trusted, but user is OK with that
  739. [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
  740. } else {
  741. // Cert not trusted, and user is not OK with that. Don't proceed
  742. [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
  743. }
  744. }
  745. else {
  746. // invalid or revoked certificate
  747. [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
  748. //[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
  749. }
  750. }
  751. else if (self.authHandler) {
  752. // forward the authentication to the view controller that created this operation
  753. // If this happens for NSURLAuthenticationMethodHTMLForm, you have to
  754. // do some shit work like showing a modal webview controller and close it after authentication.
  755. // I HATE THIS.
  756. self.authHandler(challenge);
  757. }
  758. else {
  759. [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
  760. }
  761. } else {
  762. // apple proposes to cancel authentication, which results in NSURLErrorDomain error -1012, but we prefer to trigger a 401
  763. // [[challenge sender] cancelAuthenticationChallenge:challenge];
  764. [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
  765. }
  766. }
  767. - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
  768. NSUInteger size = [self.response expectedContentLength] < 0 ? 0 : [self.response expectedContentLength];
  769. self.response = (NSHTTPURLResponse*) response;
  770. // dont' save data if the operation was created to download directly to a stream.
  771. if([self.downloadStreams count] == 0)
  772. self.mutableData = [NSMutableData dataWithCapacity:size];
  773. else
  774. self.mutableData = nil;
  775. for(NSOutputStream *stream in self.downloadStreams)
  776. [stream open];
  777. NSDictionary *httpHeaders = [self.response allHeaderFields];
  778. // if you attach a stream to the operation, MKNetworkKit will not cache the response.
  779. // Streams are usually "big data chunks" that doesn't need caching anyways.
  780. if([self.request.HTTPMethod isEqualToString:@"GET"] && [self.downloadStreams count] == 0) {
  781. // We have all this complicated cache handling since NSURLRequestReloadRevalidatingCacheData is not implemented
  782. // do cache processing only if the request is a "GET" method
  783. NSString *lastModified = [httpHeaders objectForKey:@"Last-Modified"];
  784. NSString *eTag = [httpHeaders objectForKey:@"ETag"];
  785. NSString *expiresOn = [httpHeaders objectForKey:@"Expires"];
  786. NSString *contentType = [httpHeaders objectForKey:@"Content-Type"];
  787. // if contentType is image,
  788. NSDate *expiresOnDate = nil;
  789. if([contentType rangeOfString:@"image"].location != NSNotFound) {
  790. // For images let's assume a expiry date of 7 days if there is no eTag or Last Modified.
  791. if(!eTag && !lastModified)
  792. expiresOnDate = [[NSDate date] dateByAddingTimeInterval:kMKNetworkKitDefaultImageCacheDuration];
  793. else
  794. expiresOnDate = [[NSDate date] dateByAddingTimeInterval:kMKNetworkKitDefaultImageHeadRequestDuration];
  795. }
  796. NSString *cacheControl = [httpHeaders objectForKey:@"Cache-Control"]; // max-age, must-revalidate, no-cache
  797. NSArray *cacheControlEntities = [cacheControl componentsSeparatedByString:@","];
  798. for(NSString *substring in cacheControlEntities) {
  799. if([substring rangeOfString:@"max-age"].location != NSNotFound) {
  800. // do some processing to calculate expiresOn
  801. NSString *maxAge = nil;
  802. NSArray *array = [substring componentsSeparatedByString:@"="];
  803. if([array count] > 1)
  804. maxAge = [array objectAtIndex:1];
  805. expiresOnDate = [[NSDate date] dateByAddingTimeInterval:[maxAge intValue]];
  806. }
  807. if([substring rangeOfString:@"no-cache"].location != NSNotFound) {
  808. // Don't cache this request
  809. expiresOnDate = [[NSDate date] dateByAddingTimeInterval:kMKNetworkKitDefaultCacheDuration];
  810. }
  811. }
  812. // if there was a cacheControl entity, we would have a expiresOnDate that is not nil.
  813. // "Cache-Control" headers take precedence over "Expires" headers
  814. expiresOn = [expiresOnDate rfc1123String];
  815. // now remember lastModified, eTag and expires for this request in cache
  816. if(expiresOn)
  817. [self.cacheHeaders setObject:expiresOn forKey:@"Expires"];
  818. if(lastModified)
  819. [self.cacheHeaders setObject:lastModified forKey:@"Last-Modified"];
  820. if(eTag)
  821. [self.cacheHeaders setObject:eTag forKey:@"ETag"];
  822. }
  823. }
  824. - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
  825. if (self.downloadedDataSize == 0) {
  826. // This is the first batch of data
  827. // Check for a range header and make changes as neccesary
  828. NSString *rangeString = [[self request] valueForHTTPHeaderField:@"Range"];
  829. if ([rangeString hasPrefix:@"bytes="] && [rangeString hasSuffix:@"-"]) {
  830. NSString *bytesText = [rangeString substringWithRange:NSMakeRange(6, [rangeString length] - 7)];
  831. self.startPosition = [bytesText integerValue];
  832. self.downloadedDataSize = self.startPosition;
  833. DLog(@"Resuming at %d bytes", self.startPosition);
  834. }
  835. }
  836. if([self.downloadStreams count] == 0)
  837. [self.mutableData appendData:data];
  838. for(NSOutputStream *stream in self.downloadStreams) {
  839. if ([stream hasSpaceAvailable]) {
  840. const uint8_t *dataBuffer = [data bytes];
  841. [stream write:&dataBuffer[0] maxLength:[data length]];
  842. }
  843. }
  844. self.downloadedDataSize += [data length];
  845. for(MKNKProgressBlock downloadProgressBlock in self.downloadProgressChangedHandlers) {
  846. if([self.response expectedContentLength] > 0) {
  847. double progress = (double)(self.downloadedDataSize) / (double)(self.startPosition + [self.response expectedContentLength]);
  848. downloadProgressBlock(progress);
  849. }
  850. }
  851. }
  852. - (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten
  853. totalBytesWritten:(NSInteger)totalBytesWritten
  854. totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite {
  855. for(MKNKProgressBlock uploadProgressBlock in self.uploadProgressChangedHandlers) {
  856. if(totalBytesExpectedToWrite > 0) {
  857. uploadProgressBlock(((double)totalBytesWritten/(double)totalBytesExpectedToWrite));
  858. }
  859. }
  860. }
  861. // http://stackoverflow.com/questions/1446509/handling-redirects-correctly-with-nsurlconnection
  862. - (NSURLRequest *)connection: (NSURLConnection *)inConnection
  863. willSendRequest: (NSURLRequest *)inRequest
  864. redirectResponse: (NSURLResponse *)inRedirectResponse;
  865. {
  866. if (inRedirectResponse) {
  867. NSMutableURLRequest *r = [self.request mutableCopy];
  868. [r setURL: [inRequest URL]];
  869. DLog(@"Redirected to %@", [[inRequest URL] absoluteString]);
  870. return r;
  871. } else {
  872. return inRequest;
  873. }
  874. }
  875. - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
  876. if([self isCancelled])
  877. return;
  878. self.state = MKNetworkOperationStateFinished;
  879. for(NSOutputStream *stream in self.downloadStreams)
  880. [stream close];
  881. if (self.response.statusCode >= 200 && self.response.statusCode < 300 && ![self isCancelled]) {
  882. self.cachedResponse = nil; // remove cached data
  883. [self notifyCache];
  884. [self operationSucceeded];
  885. }
  886. if (self.response.statusCode >= 300 && self.response.statusCode < 400) {
  887. if(self.response.statusCode == 301) {
  888. DLog(@"%@ has moved to %@", self.url, [self.response.URL absoluteString]);
  889. }
  890. else if(self.response.statusCode == 304) {
  891. DLog(@"%@ not modified", self.url);
  892. }
  893. else if(self.response.statusCode == 307) {
  894. DLog(@"%@ temporarily redirected", self.url);
  895. }
  896. else {
  897. DLog(@"%@ returned status %d", self.url, (int) self.response.statusCode);
  898. }
  899. } else if (self.response.statusCode >= 400 && self.response.statusCode < 600 && ![self isCancelled]) {
  900. [self operationFailedWithError:[NSError errorWithDomain:NSURLErrorDomain
  901. code:self.response.statusCode
  902. userInfo:self.response.allHeaderFields]];
  903. }
  904. [self endBackgroundTask];
  905. }
  906. #pragma mark -
  907. #pragma mark Our methods to get data
  908. -(NSData*) responseData {
  909. if([self isFinished])
  910. return self.mutableData;
  911. else if(self.cachedResponse)
  912. return self.cachedResponse;
  913. else
  914. return nil;
  915. }
  916. -(NSString*)responseString {
  917. return [self responseStringWithEncoding:self.stringEncoding];
  918. }
  919. -(NSString*) responseStringWithEncoding:(NSStringEncoding) encoding {
  920. return [[NSString alloc] initWithData:[self responseData] encoding:encoding];
  921. }
  922. #if TARGET_OS_IPHONE
  923. -(UIImage*) responseImage {
  924. return [UIImage imageWithData:[self responseData]];
  925. }
  926. #elif TARGET_OS_MAC
  927. -(NSImage*) responseImage {
  928. return [[NSImage alloc] initWithData:[self responseData]];
  929. }
  930. -(NSXMLDocument*) responseXML {
  931. return [[NSXMLDocument alloc] initWithData:[self responseData] options:0 error:nil];
  932. }
  933. #endif
  934. -(id) responseJSON {
  935. if(NSClassFromString(@"NSJSONSerialization")) {
  936. if([self responseData] == nil) return nil;
  937. NSError *error = nil;
  938. id returnValue = [NSClassFromString(@"NSJSONSerialization") JSONObjectWithData:[self responseData] options:0 error:&error];
  939. if(error) DLog(@"JSON Parsing Error: %@", error);
  940. return returnValue;
  941. }
  942. else {
  943. DLog("You are running on iOS 4. Subclass MKNO and override responseJSON to support custom JSON parsing");
  944. return [self responseString];
  945. }
  946. }
  947. #pragma mark -
  948. #pragma mark Overridable methods
  949. -(void) operationSucceeded {
  950. for(MKNKResponseBlock responseBlock in self.responseBlocks)
  951. responseBlock(self);
  952. }
  953. -(void) showLocalNotification {
  954. #if TARGET_OS_IPHONE
  955. if(self.localNotification) {
  956. [[UIApplication sharedApplication] presentLocalNotificationNow:self.localNotification];
  957. } else if(self.shouldShowLocalNotificationOnError) {
  958. UILocalNotification *localNotification = [[UILocalNotification alloc] init];
  959. localNotification.alertBody = [self.error localizedDescription];
  960. localNotification.alertAction = NSLocalizedString(@"Dismiss", @"");
  961. [[UIApplication sharedApplication] presentLocalNotificationNow:localNotification];
  962. }
  963. #endif
  964. }
  965. -(void) operationFailedWithError:(NSError*) error {
  966. self.error = error;
  967. DLog(@"%@, [%@]", self, [self.error localizedDescription]);
  968. for(MKNKErrorBlock errorBlock in self.errorBlocks)
  969. errorBlock(error);
  970. #if TARGET_OS_IPHONE
  971. DLog(@"State: %d", [[UIApplication sharedApplication] applicationState]);
  972. if([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground)
  973. [self showLocalNotification];
  974. #endif
  975. }
  976. @end