// _____ _
// |_ _| |_ _ _ ___ ___ _ __ __ _
// | | | ' \| '_/ -_) -_) ' \/ _` |_
// |_| |_||_|_| \___\___|_|_|_\__,_(_)
//
// Threema iOS Client
// Copyright (c) 2015-2020 Threema GmbH
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License, version 3,
// as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
#import "BlobMessageLoader.h"
#import "ProtocolDefines.h"
#import "NaClCrypto.h"
#import "MessageSender.h"
#import "EntityManager.h"
#import "ThreemaError.h"
#import "PinnedHTTPSURLLoader.h"
#import "Threema-Swift.h"
#import "UserSettings.h"
#import "MediaConverter.h"
#ifdef DEBUG
static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
#else
static const DDLogLevel ddLogLevel = DDLogLevelWarning;
#endif
@interface BlobMessageLoader ()
@end
@implementation BlobMessageLoader
- (void)startWithMessage:(BaseMessage *)message onCompletion:(void (^)(BaseMessage *loadedMessage))onCompletion onError:(void (^)(NSError *error))onError {
_message = message;
NSData *blobData = [_message blobGetData];
if (blobData != nil) {
onCompletion(message);
return;
}
NSData *blobId = [_message blobGetId];
if (blobId == nil) {
DDLogWarn(@"Missing blob ID or encryption key!");
onError([ThreemaError threemaError:[BundleUtil localizedStringForKey:@"media_file_not_found"]]);
return;
}
NSData *encryptionKey = [_message blobGetEncryptionKey];
if (encryptionKey == nil) {
// handle image message backward compatibility
if ([message isKindOfClass:[ImageMessage class]]) {
if (((ImageMessage *)message).imageNonce == nil) {
DDLogWarn(@"Missing image encryption key or nonce!");
return;
}
} else {
DDLogWarn(@"Missing encryption key!");
return;
}
}
NSNumber *progress = [_message blobGetProgress];
if (progress != nil) {
DDLogWarn(@"Blob download already in progress");
return;
}
_message = message;
[_message blobUpdateProgress:[NSNumber numberWithFloat:0]];
PinnedHTTPSURLLoader *loader = [[PinnedHTTPSURLLoader alloc] init];
loader.delegate = self;
[loader startWithBlobId:blobId onCompletion:^(NSData *data) {
if ([_message wasDeleted]) {
return;
}
NSData *decryptedData = [self decryptData:data];
if (decryptedData == nil) {
onError([ThreemaError threemaError:@"Blob data decryption failed"]);
return;
}
[self updateDBObjectWithData:decryptedData onCompletion:^{
if (_message.conversation.groupId == nil) {
[MessageSender markBlobAsDone:blobId];
}
DDLogInfo(@"Blob successfully downloaded (%lu bytes)", (unsigned long)data.length);
onCompletion(_message);
}];
} onError:^(NSError *error) {
[[ValidationLogger sharedValidationLogger] logString:error.description];
dispatch_async(dispatch_get_main_queue(), ^{
if ([_message wasDeleted] == NO) {
EntityManager *entityManager = [[EntityManager alloc] init];
[entityManager performSyncBlockAndSafe:^{
_message.sendFailed = [NSNumber numberWithBool:YES];
[_message blobUpdateProgress:nil];
}];
}
});
onError([ThreemaError threemaError:[BundleUtil localizedStringForKey:@"media_file_not_found"]]);
}];
}
- (NSData *)decryptData:(NSData *)data {
NSData *decryptedData = nil;
@try {
NSData *encryptionKey = [_message blobGetEncryptionKey];
decryptedData = [[NaClCrypto sharedCrypto] symmetricDecryptData:data withKey:encryptionKey nonce:[NSData dataWithBytesNoCopy:kNonce_1 length:sizeof(kNonce_1) freeWhenDone:NO]];
} @catch (NSException *exception) {
DDLogError(@"Blob decryption failed: %@", [exception description]);
}
return decryptedData;
}
- (void)updateDBObjectWithData:(NSData *)data onCompletion:(void(^)(void))onCompletion {
dispatch_async(dispatch_get_main_queue(), ^{
EntityManager *entityManager = [[EntityManager alloc] init];
[entityManager performSyncBlockAndSafe:^{
[_message blobSetData:data];
_message.sendFailed = [NSNumber numberWithBool:NO];
[_message blobUpdateProgress:nil];
}];
/* Add to photo library */
if ([UserSettings sharedUserSettings].autoSaveMedia && [_message isKindOfClass:[FileMessage class]]) {
FileMessage *fileMessage = (FileMessage *)_message;
NSString *filename = [FileUtility getTemporaryFileName];
__block NSURL *tmpFileUrl = [fileMessage tmpURL:filename];
[fileMessage exportDataToURL:tmpFileUrl];
BOOL isVideo = [UTIConverter isVideoMimeType:fileMessage.mimeType] || [UTIConverter isMovieMimeType:fileMessage.mimeType];
BOOL isImage = [UTIConverter isImageMimeType:fileMessage.mimeType];
BOOL isSticker = [fileMessage.type isEqual: @2];
if (isVideo == true || (isImage == true && isSticker == false)) {
[[AlbumManager shared] saveWithUrl:tmpFileUrl isVideo:isVideo completionHandler:^(BOOL success) {
if (tmpFileUrl) {
[[NSFileManager defaultManager] removeItemAtURL:tmpFileUrl error:nil];
tmpFileUrl = nil;
}
}];
}
}
onCompletion();
});
}
#pragma mark HTTPSURLLoaderDelegate
- (BOOL)httpsLoaderShouldCancel {
if ([_message wasDeleted]) {
return YES;
}
return NO;
}
- (void)httpsLoaderReceivedData:(NSData *)totalData {
if ([_message wasDeleted]) {
return;
}
[_message blobUpdateProgress:[NSNumber numberWithFloat:totalData.length / [_message blobGetSize].floatValue]];
}
@end