BlobUploader.m 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2015-2020 Threema GmbH
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU Affero General Public License, version 3,
  11. // as published by the Free Software Foundation.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU Affero General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Affero General Public License
  19. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. #import "BlobUploader.h"
  21. #import "ActivityIndicatorProxy.h"
  22. #import "Contact.h"
  23. #import "BlobUtil.h"
  24. #import "BaseMessage.h"
  25. #import "ThreemaError.h"
  26. #import "SSLCAHelper.h"
  27. #import "NSString+Hex.h"
  28. #import "BlobUploadDelegate.h"
  29. #ifdef DEBUG
  30. static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
  31. #else
  32. static const DDLogLevel ddLogLevel = DDLogLevelWarning;
  33. #endif
  34. @interface BlobUploader ()
  35. @property NSMutableData *receivedData;
  36. @property NSURLConnection *uploadConnection;
  37. @property id<BlobUploadDelegate> blobUploadDelegate;
  38. @end
  39. @implementation BlobUploader
  40. - (void)startUploadFor:(id<BlobUploadDelegate>)messageProxy {
  41. _blobUploadDelegate = messageProxy;
  42. [ActivityIndicatorProxy startActivity];
  43. [_blobUploadDelegate uploadProgress: [NSNumber numberWithFloat:0]];
  44. NSURL *blobUploadUrl = [BlobUtil urlForBlobUpload];
  45. NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:blobUploadUrl cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:kBlobUploadTimeout];
  46. request.HTTPMethod = @"POST";
  47. NSString *boundary = @"---------------------------Boundary_Line";
  48. NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary];
  49. [request addValue:contentType forHTTPHeaderField: @"Content-Type"];
  50. NSUInteger dataLength = [self totalDataLength];
  51. NSMutableData *body = [NSMutableData dataWithCapacity: dataLength];
  52. [body appendData:[[NSString stringWithFormat:@"--%@\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
  53. [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"blob\"; filename=\"blob.bin\"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
  54. [body appendData:[@"Content-Type: application/octet-stream\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
  55. [body appendData:_data];
  56. _data = nil; // release memory now
  57. if (_thumbnailData) {
  58. [body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
  59. [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"blob2\"; filename=\"blob2.bin\"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
  60. [body appendData:[@"Content-Type: application/octet-stream\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
  61. [body appendData:_thumbnailData];
  62. _thumbnailData = nil; // release memory now
  63. [body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
  64. } else {
  65. [body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
  66. }
  67. [request setHTTPBody:body];
  68. [request addValue:[NSString stringWithFormat:@"%lu", (unsigned long)[body length]] forHTTPHeaderField:@"Content-Length"];
  69. _receivedData = [NSMutableData data];
  70. _uploadConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
  71. [_uploadConnection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
  72. [_uploadConnection start];
  73. }
  74. - (NSUInteger)totalDataLength {
  75. NSUInteger length = _data.length + 1024;
  76. if (_thumbnailData) {
  77. length += _thumbnailData.length;
  78. }
  79. return length;
  80. }
  81. #pragma mark - URL connection delegate
  82. - (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite {
  83. DDLogVerbose(@"totalBytesWritten: %ld, totalBytesExpectedToWrite: %ld", (long)totalBytesWritten, (long)totalBytesExpectedToWrite);
  84. if ([_blobUploadDelegate uploadShouldCancel]) {
  85. DDLogWarn(@"Upload cancelled");
  86. [connection cancel];
  87. [_blobUploadDelegate uploadDidCancel];
  88. return;
  89. }
  90. [_blobUploadDelegate uploadProgress: [NSNumber numberWithFloat:((float)totalBytesWritten / (float)totalBytesExpectedToWrite)]];
  91. }
  92. - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
  93. DDLogVerbose(@"Request succeeded - received %lu bytes", (unsigned long)[_receivedData length]);
  94. [ActivityIndicatorProxy stopActivity];
  95. NSString *blobIdHex = [[NSString alloc] initWithData:_receivedData encoding:NSASCIIStringEncoding];
  96. NSData *blobId = [blobIdHex decodeHex];
  97. if (blobId.length == kBlobIdLen) {
  98. DDLogVerbose(@"Blob ID: %@", blobId);
  99. [_blobUploadDelegate uploadSucceededWithBlobIds:@[blobId]];
  100. } else if (blobId.length == (2*kBlobIdLen)) {
  101. NSData *blobId1 = [NSData dataWithBytes:blobId.bytes length:kBlobIdLen];
  102. NSData *blobId2 = [NSData dataWithBytes:(blobId.bytes + kBlobIdLen) length:kBlobIdLen];
  103. DDLogVerbose(@"Blob ID: %@ / %@", blobId1, blobId2);
  104. [_blobUploadDelegate uploadSucceededWithBlobIds:@[blobId1, blobId2]];
  105. } else {
  106. DDLogError(@"Got invalid blob ID from server: %@", blobId);
  107. [self connection:connection didFailWithError:[ThreemaError threemaError:@"Got invalid blob ID from server"]];
  108. }
  109. _uploadConnection = nil; // release memory for body etc. now
  110. }
  111. - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
  112. DDLogError(@"Connection failed - error %@ %@",
  113. [error localizedDescription],
  114. [[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
  115. [ActivityIndicatorProxy stopActivity];
  116. if ([_blobUploadDelegate uploadShouldCancel]) {
  117. DDLogWarn(@"Upload cancelled");
  118. [_blobUploadDelegate uploadDidCancel];
  119. return;
  120. }
  121. [_blobUploadDelegate uploadFailed];
  122. _uploadConnection = nil; // release memory for body etc. now
  123. }
  124. /* this method ensures that HTTP errors get treated like connection failures etc. as well */
  125. - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
  126. [_receivedData setLength:0];
  127. NSInteger statusCode = [((NSHTTPURLResponse *)response) statusCode];
  128. if (statusCode >= 400)
  129. {
  130. [connection cancel]; // stop connecting; no more delegate messages
  131. NSDictionary *errorInfo = [NSDictionary dictionaryWithObject:[NSString stringWithFormat:
  132. NSLocalizedString(@"Server returned status code %d",@""),
  133. statusCode]
  134. forKey:NSLocalizedDescriptionKey];
  135. NSError *statusError = [NSError errorWithDomain:NSURLErrorDomain
  136. code:statusCode
  137. userInfo:errorInfo];
  138. [self connection:connection didFailWithError:statusError];
  139. }
  140. }
  141. - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
  142. [_receivedData appendData:data];
  143. }
  144. - (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
  145. return [SSLCAHelper connection:connection canAuthenticateAgainstProtectionSpace:protectionSpace];
  146. }
  147. - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
  148. [SSLCAHelper connection:connection didReceiveAuthenticationChallenge:challenge];
  149. }
  150. - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse {
  151. return nil;
  152. }
  153. @end