FileMessageSender.m 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  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 "FileMessageSender.h"
  21. #import "EntityManager.h"
  22. #import "NaClCrypto.h"
  23. #import "BoxFileMessage.h"
  24. #import "GroupFileMessage.h"
  25. #import "FileMessageEncoder.h"
  26. #import "MessageQueue.h"
  27. #import "MyIdentityStore.h"
  28. #import "Contact.h"
  29. #import "ContactPhotoSender.h"
  30. #import "UTIConverter.h"
  31. #import "BundleUtil.h"
  32. #ifdef DEBUG
  33. static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
  34. #else
  35. static const DDLogLevel ddLogLevel = DDLogLevelWarning;
  36. #endif
  37. @interface FileMessageSender ()
  38. @property URLSenderItem *item;
  39. @property NSString *webRequestId;
  40. @property NSString *correlationId;
  41. @end
  42. @implementation FileMessageSender
  43. - (void)sendItem:(URLSenderItem *)item inConversation:(Conversation *)conversation {
  44. [self sendItem:item inConversation:conversation requestId:nil];
  45. }
  46. - (void)sendItem:(URLSenderItem *)item inConversation:(Conversation *)conversation requestId:(NSString *)requestId {
  47. _item = item;
  48. self.conversation = conversation;
  49. _webRequestId = requestId;
  50. [self scheduleUpload];
  51. }
  52. - (void)sendItem:(URLSenderItem *)item inConversation:(Conversation *)conversation requestId:(NSString *)requestId correlationId:(NSString *)correlationId {
  53. _item = item;
  54. self.conversation = conversation;
  55. _webRequestId = requestId;
  56. self.correlationId = correlationId;
  57. [self scheduleUpload];
  58. }
  59. - (NSData *)prepareDataFor:(URLSenderItem *)item {
  60. NSData *data = [item getData];
  61. if (data == nil) {
  62. DDLogError(@"Cannot read data from %@", item);
  63. [self.uploadProgressDelegate blobMessageSender:self uploadFailedForMessage:nil error:UploadErrorInvalidFile];
  64. return nil;
  65. }
  66. if ([data length] > kMaxFileSize) {
  67. DDLogError(@"File to big %@, size: %lul", item, (unsigned long)[data length]);
  68. [self.uploadProgressDelegate blobMessageSender:self uploadFailedForMessage:nil error:UploadErrorFileTooBig];
  69. return nil;
  70. }
  71. return data;
  72. }
  73. - (BOOL)supportsCaption {
  74. return YES;
  75. }
  76. - (void)createDBMessage {
  77. NSData *data = [self prepareDataFor:_item];
  78. if (data == nil) {
  79. return;
  80. }
  81. UIImage *thumbnailImage = [_item getThumbnail];
  82. NSData *encryptionKey = [[NaClCrypto sharedCrypto] randomBytes:kBlobKeyLen];
  83. EntityManager *entityManager = [[EntityManager alloc] init];
  84. [entityManager performSyncBlockAndSafe:^{
  85. FileData *fileData = [entityManager.entityCreator fileData];
  86. fileData.data = data;
  87. Conversation *conversationOwnContext = (Conversation *)[entityManager.entityFetcher getManagedObjectById:self.conversation.objectID];
  88. FileMessage *message = [entityManager.entityCreator fileMessageForConversation:conversationOwnContext];
  89. message.fileSize = [NSNumber numberWithInteger:data.length];
  90. if (self.fileNameFromWeb != nil) {
  91. message.fileName = self.fileNameFromWeb;
  92. } else {
  93. message.fileName = [_item getName];
  94. }
  95. message.mimeType = [_item getMimeType];
  96. if (_item.renderType == nil) {
  97. message.type = @0;
  98. } else {
  99. message.type = _item.renderType;
  100. }
  101. if (_item.sendAsFile == false) {
  102. if ([_item.renderType isEqual:@0] && ([message sendAsFileImageMessage] == true || [message sendAsFileVideoMessage] == true || [message sendAsFileAudioMessage] == true)) {
  103. message.type = [NSNumber numberWithInt:1];
  104. }
  105. else if ([_item.renderType isEqual:@0] && [message sendAsFileGifMessage] == true) {
  106. message.type = [NSNumber numberWithInt:2];
  107. }
  108. }
  109. if ([message sendAsFileVideoMessage] || [message sendAsFileAudioMessage]) {
  110. // add duration
  111. message.duration = [[NSNumber alloc] initWithFloat:[_item getDuration]];
  112. }
  113. if ([message renderFileImageMessage]) {
  114. // add height and width
  115. message.height = [[NSNumber alloc] initWithFloat:[_item getHeight]];
  116. message.width = [[NSNumber alloc] initWithFloat:[_item getWidth]];
  117. }
  118. message.data = fileData;
  119. message.encryptionKey = encryptionKey;
  120. message.progress = nil;
  121. message.sendFailed = [NSNumber numberWithBool:NO];
  122. message.webRequestId = _webRequestId;
  123. message.correlationId = _correlationId;
  124. if (thumbnailImage) {
  125. NSData *thumbnailData = nil;
  126. if ([UTIConverter isPNGImageMimeType:message.mimeType]) {
  127. thumbnailData = UIImagePNGRepresentation(thumbnailImage);
  128. message.mimeTypeThumbnail = message.mimeType;
  129. } else {
  130. thumbnailData = UIImageJPEGRepresentation(thumbnailImage, kJPEGCompressionQuality);
  131. }
  132. ImageData *dbThumbnail = [entityManager.entityCreator imageData];
  133. dbThumbnail.data = thumbnailData;
  134. dbThumbnail.height = [NSNumber numberWithInt:thumbnailImage.size.height];
  135. dbThumbnail.width = [NSNumber numberWithInt:thumbnailImage.size.width];
  136. message.thumbnail = dbThumbnail;
  137. }
  138. message.caption = _item.caption;
  139. message.json = [FileMessageEncoder jsonStringForMessage:message];
  140. self.message = message;
  141. }];
  142. }
  143. - (void)retryMessage:(FileMessage *)message {
  144. self.message = message;
  145. self.conversation = message.conversation;
  146. [self scheduleUpload];
  147. }
  148. -(NSData *)encryptedData {
  149. FileMessage *fileMessage = (FileMessage *)self.message;
  150. NSData *data = fileMessage.data.data;
  151. NSData *encryptionKey = fileMessage.encryptionKey;
  152. NSData *boxFileData = [[NaClCrypto sharedCrypto] symmetricEncryptData:data withKey:encryptionKey nonce:[NSData dataWithBytesNoCopy:kNonce_1 length:sizeof(kNonce_1) freeWhenDone:NO]];
  153. if (boxFileData == nil) {
  154. DDLogWarn(@"File encryption failed");
  155. }
  156. return boxFileData;
  157. }
  158. - (NSData *)encryptedThumbnailData {
  159. FileMessage *fileMessage = (FileMessage *)self.message;
  160. if (fileMessage.thumbnail) {
  161. NSData *boxThumbnailData = [[NaClCrypto sharedCrypto] symmetricEncryptData:fileMessage.thumbnail.data withKey:fileMessage.encryptionKey nonce:[NSData dataWithBytesNoCopy:kNonce_2 length:sizeof(kNonce_2) freeWhenDone:NO]];
  162. if (boxThumbnailData == nil) {
  163. DDLogWarn(@"Thumbnail encryption failed");
  164. }
  165. return boxThumbnailData;
  166. }
  167. return nil;
  168. }
  169. #pragma mark - BlobMessageSender
  170. - (void)sendMessageTo:(Contact *)contact blobIds:(NSArray *)blobIds {
  171. FileMessage *fileMessage = (FileMessage *)self.message;
  172. fileMessage.blobId = blobIds[0];
  173. if ([blobIds count] > 1) {
  174. fileMessage.blobThumbnailId = blobIds[1];
  175. }
  176. BoxFileMessage *boxMsg = [FileMessageEncoder encodeFileMessage:fileMessage];
  177. boxMsg.messageId = self.message.id;
  178. boxMsg.toIdentity = contact.identity;
  179. [[MessageQueue sharedMessageQueue] enqueue:boxMsg];
  180. [ContactPhotoSender sendProfilePicture:boxMsg];
  181. }
  182. - (void)sendGroupMessageTo:(Contact *)contact blobIds:(NSArray *)blobIds {
  183. FileMessage *fileMessage = (FileMessage *)self.message;
  184. fileMessage.blobId = blobIds[0];
  185. if ([blobIds count] > 1) {
  186. fileMessage.blobThumbnailId = blobIds[1];
  187. }
  188. GroupFileMessage *boxMsg = [FileMessageEncoder encodeGroupFileMessage:fileMessage];
  189. boxMsg.messageId = self.message.id;
  190. boxMsg.date = self.message.date;
  191. boxMsg.groupId = self.conversation.groupId;
  192. if (self.conversation.contact == nil) {
  193. boxMsg.groupCreator = [MyIdentityStore sharedMyIdentityStore].identity;
  194. } else {
  195. boxMsg.groupCreator = self.conversation.contact.identity;
  196. }
  197. boxMsg.toIdentity = contact.identity;
  198. [[MessageQueue sharedMessageQueue] enqueue:boxMsg];
  199. [ContactPhotoSender sendProfilePicture:boxMsg];
  200. }
  201. #pragma mark - Error translation
  202. + (NSString *)messageForError:(UploadError)error {
  203. NSString *errorMessage;
  204. switch (error) {
  205. case UploadErrorFileTooBig:
  206. errorMessage = [BundleUtil localizedStringForKey:@"error_message_file_too_big"];
  207. break;
  208. case UploadErrorInvalidFile:
  209. errorMessage = [BundleUtil localizedStringForKey:@"error_message_invalid_file"];
  210. break;
  211. default:
  212. errorMessage = [BundleUtil localizedStringForKey:@"error_message_generic"];
  213. break;
  214. }
  215. return errorMessage;
  216. }
  217. @end