From e262013147bdd9ecc1a288f35a99669427d04f57 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Thu, 8 Oct 2020 16:20:42 +1100 Subject: [PATCH 01/10] use onion routing for individual avatar downloads --- SignalMessaging/profiles/OWSProfileManager.m | 48 ++----------------- .../src/Loki/API/FileServerAPI.swift | 26 ++++++++++ 2 files changed, 31 insertions(+), 43 deletions(-) diff --git a/SignalMessaging/profiles/OWSProfileManager.m b/SignalMessaging/profiles/OWSProfileManager.m index 58017d509..29636658d 100644 --- a/SignalMessaging/profiles/OWSProfileManager.m +++ b/SignalMessaging/profiles/OWSProfileManager.m @@ -1157,43 +1157,14 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); OWSLogVerbose(@"downloading profile avatar: %@", userProfile.uniqueId); - NSString *tempDirectory = OWSTemporaryDirectory(); - NSString *tempFilePath = [tempDirectory stringByAppendingPathComponent:fileName]; - NSString *profilePictureURL = userProfile.avatarUrlPath; - NSError *serializationError; - NSMutableURLRequest *request = - [self.avatarHTTPManager.requestSerializer requestWithMethod:@"GET" - URLString:profilePictureURL - parameters:nil - error:&serializationError]; - if (serializationError) { - OWSFailDebug(@"serializationError: %@", serializationError); - return; - } - - NSURLSession* session = [NSURLSession sharedSession]; - NSURLSessionTask* downloadTask = [session downloadTaskWithRequest:request completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) { - + AnyPromise *promise = [LKFileServerAPI downloadProfilePicture:profilePictureURL]; + [promise.then(^(NSData *data) { @synchronized(self.currentAvatarDownloads) { [self.currentAvatarDownloads removeObject:userProfile.recipientId]; } - - if (error) { - OWSLogError(@"Dowload failed: %@", error); - return; - } - - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSURL *tempFileUrl = [NSURL fileURLWithPath:tempFilePath]; - NSError *moveError; - if (![fileManager moveItemAtURL:location toURL:tempFileUrl error:&moveError]) { - OWSLogError(@"MoveItemAtURL for avatar failed: %@", moveError); - return; - } - - NSData *_Nullable encryptedData = (error ? nil : [NSData dataWithContentsOfFile:tempFilePath]); + NSData *_Nullable encryptedData = data; NSData *_Nullable decryptedData = [self decryptProfileData:encryptedData profileKey:profileKeyAtStart]; UIImage *_Nullable image = nil; if (decryptedData) { @@ -1213,19 +1184,12 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); if (latestUserProfile.avatarUrlPath.length > 0) { [self downloadAvatarForUserProfile:latestUserProfile]; } - } else if (error) { - if ([response isKindOfClass:NSHTTPURLResponse.class] - && ((NSHTTPURLResponse *)response).statusCode == 403) { - OWSLogInfo(@"no avatar for: %@", userProfile.recipientId); - } else { - OWSLogError(@"avatar download for %@ failed with error: %@", userProfile.recipientId, error); - } } else if (!encryptedData) { OWSLogError(@"avatar encrypted data for %@ could not be read.", userProfile.recipientId); } else if (!decryptedData) { OWSLogError(@"avatar data for %@ could not be decrypted.", userProfile.recipientId); } else if (!image) { - OWSLogError(@"avatar image for %@ could not be loaded with error: %@", userProfile.recipientId, error); + OWSLogError(@"avatar image for %@ could not be loaded.", userProfile.recipientId); } else { [self updateProfileAvatarCache:image filename:fileName]; @@ -1248,9 +1212,7 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); OWSAssertDebug(backgroundTask); backgroundTask = nil; - }]; - - [downloadTask resume]; + }) retainUntilComplete]; }); } diff --git a/SignalServiceKit/src/Loki/API/FileServerAPI.swift b/SignalServiceKit/src/Loki/API/FileServerAPI.swift index fc2f69535..c8cc947c9 100644 --- a/SignalServiceKit/src/Loki/API/FileServerAPI.swift +++ b/SignalServiceKit/src/Loki/API/FileServerAPI.swift @@ -18,6 +18,7 @@ public final class FileServerAPI : DotNetAPI { public static let fileSizeORMultiplier: Double = 6 @objc public static let server = "https://file.getsession.org" + @objc public static let fileStaticServer = "https://file-static.lokinet.org" // MARK: Storage override internal class var authTokenCollection: String { return "LokiStorageAuthTokenCollection" } @@ -52,6 +53,31 @@ public final class FileServerAPI : DotNetAPI { } } + @objc(downloadProfilePicture:) + public static func objc_downloadProfilePicture(_ downloadURL: String) -> AnyPromise { + return AnyPromise.from(downloadProfilePicture(downloadURL)) + } + + public static func downloadProfilePicture(_ downloadURL: String) -> Promise { + var error: NSError? + var url = downloadURL + if downloadURL.contains(fileStaticServer) { + url = downloadURL.replacingOccurrences(of: fileStaticServer, with: "\(server)/loki/v1") + } + let request = AFHTTPRequestSerializer().request(withMethod: "GET", urlString: url, parameters: nil, error: &error) + if let error = error { + print("[Loki] Couldn't download profile picture due to error: \(error).") + return Promise(error: error) + } + return OnionRequestAPI.sendOnionRequest(request, to: server, using: fileServerPublicKey, isJSONRequired: false).map2 { json in + guard let body = json["body"] as? JSON, let dataArray = body["data"] as? [UInt8] else { + print("[Loki] Couldn't download profile picture.") + return Data() + } + return Data(dataArray) + } + } + // MARK: Open Group Server Public Key public static func getPublicKey(for openGroupServer: String) -> Promise { let url = URL(string: "\(server)/loki/v1/getOpenGroupKey/\(URL(string: openGroupServer)!.host!)")! From b30cfd960c42acfeeb6e47d3ab0e7027dd49e200 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Fri, 9 Oct 2020 11:18:49 +1100 Subject: [PATCH 02/10] using onion routing for open group avatar downloads --- .../Loki/API/Open Groups/PublicChatAPI.swift | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/SignalServiceKit/src/Loki/API/Open Groups/PublicChatAPI.swift b/SignalServiceKit/src/Loki/API/Open Groups/PublicChatAPI.swift index e18cd245a..58cb1dd4b 100644 --- a/SignalServiceKit/src/Loki/API/Open Groups/PublicChatAPI.swift +++ b/SignalServiceKit/src/Loki/API/Open Groups/PublicChatAPI.swift @@ -369,7 +369,7 @@ public final class PublicChatAPI : DotNetAPI { } } - static func updateProfileIfNeeded(for channel: UInt64, on server: String, from info: PublicChatInfo) { + static func updateProfileIfNeeded(for channel: UInt64, on server: String, from info: PublicChatInfo, token: String, serverPublicKey: String) { let storage = OWSPrimaryStorage.shared() let publicChatID = "\(server).\(channel)" try! Storage.writeSync { transaction in @@ -388,27 +388,26 @@ public final class PublicChatAPI : DotNetAPI { if oldProfilePictureURL != info.profilePictureURL || groupModel.groupImage == nil { storage.setProfilePictureURL(info.profilePictureURL, forPublicChatWithID: publicChatID, in: transaction) if let profilePictureURL = info.profilePictureURL { - let configuration = URLSessionConfiguration.default - let manager = AFURLSessionManager.init(sessionConfiguration: configuration) - let url = URL(string: "\(server)\(profilePictureURL)")! - let request = URLRequest(url: url) - let task = manager.downloadTask(with: request, progress: nil, - destination: { (targetPath: URL, response: URLResponse) -> URL in - let tempFilePath = URL(fileURLWithPath: OWSTemporaryDirectoryAccessibleAfterFirstAuth()).appendingPathComponent(UUID().uuidString) - return tempFilePath - }, - completionHandler: { (response: URLResponse, filePath: URL?, error: Error?) in - if let error = error { - print("[Loki] Couldn't download profile picture for public chat channel with ID: \(channel) on server: \(server).") - return - } - if let filePath = filePath, let avatarData = try? Data.init(contentsOf: filePath) { - let attachmentStream = TSAttachmentStream(contentType: OWSMimeTypeImageJpeg, byteCount: UInt32(avatarData.count), sourceFilename: nil, caption: nil, albumMessageId: nil) - try! attachmentStream.write(avatarData) - groupThread.updateAvatar(with: attachmentStream) - } - }) - task.resume() + var error: NSError? + let url = "\(server)/loki/v1\(profilePictureURL)" + let request = AFHTTPRequestSerializer().request(withMethod: "GET", urlString: url, parameters: nil, error: &error) + request.allHTTPHeaderFields = [ "Content-Type" : "application/json", + "Authorization" : "Bearer \(token)", + "Accept-Ranges" : "bytes"] + if let error = error { + print("[Loki] Couldn't download open group avatar due to error: \(error).") + return + } + OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey, isJSONRequired: false).map{ json in + guard let body = json["body"] as? JSON, let dataArray = body["data"] as? [UInt8] else { + print("[Loki] Couldn't download open group avatar.") + return + } + let avatarData = Data(dataArray) + let attachmentStream = TSAttachmentStream(contentType: OWSMimeTypeImageJpeg, byteCount: UInt32(avatarData.count), sourceFilename: nil, caption: nil, albumMessageId: nil) + try! attachmentStream.write(avatarData) + groupThread.updateAvatar(with: attachmentStream) + } } } } @@ -444,7 +443,7 @@ public final class PublicChatAPI : DotNetAPI { storage.setUserCount(memberCount, forPublicChatWithID: "\(server).\(channel)", in: transaction) } let publicChatInfo = PublicChatInfo(displayName: displayName, profilePictureURL: profilePictureURL, memberCount: memberCount) - updateProfileIfNeeded(for: channel, on: server, from: publicChatInfo) + updateProfileIfNeeded(for: channel, on: server, from: publicChatInfo, token: token, serverPublicKey: serverPublicKey) return publicChatInfo } } From 4e5a1f9400f399ec05c8928681e55e4d777a2e2c Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Fri, 9 Oct 2020 11:26:12 +1100 Subject: [PATCH 03/10] remove accept-ranges headers --- SignalServiceKit/src/Loki/API/Open Groups/PublicChatAPI.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/SignalServiceKit/src/Loki/API/Open Groups/PublicChatAPI.swift b/SignalServiceKit/src/Loki/API/Open Groups/PublicChatAPI.swift index 58cb1dd4b..bc1cad0f6 100644 --- a/SignalServiceKit/src/Loki/API/Open Groups/PublicChatAPI.swift +++ b/SignalServiceKit/src/Loki/API/Open Groups/PublicChatAPI.swift @@ -391,9 +391,7 @@ public final class PublicChatAPI : DotNetAPI { var error: NSError? let url = "\(server)/loki/v1\(profilePictureURL)" let request = AFHTTPRequestSerializer().request(withMethod: "GET", urlString: url, parameters: nil, error: &error) - request.allHTTPHeaderFields = [ "Content-Type" : "application/json", - "Authorization" : "Bearer \(token)", - "Accept-Ranges" : "bytes"] + request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)"] if let error = error { print("[Loki] Couldn't download open group avatar due to error: \(error).") return From 6739bfc41ad3eb03e05f5658a78c369f6e1ae5a3 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Tue, 13 Oct 2020 13:55:26 +1100 Subject: [PATCH 04/10] download attachments with onion routing --- .../src/Loki/API/FileServerAPI.swift | 14 +- .../Attachments/OWSAttachmentDownloads.m | 136 +++--------------- 2 files changed, 29 insertions(+), 121 deletions(-) diff --git a/SignalServiceKit/src/Loki/API/FileServerAPI.swift b/SignalServiceKit/src/Loki/API/FileServerAPI.swift index c8cc947c9..4ea2414dc 100644 --- a/SignalServiceKit/src/Loki/API/FileServerAPI.swift +++ b/SignalServiceKit/src/Loki/API/FileServerAPI.swift @@ -55,10 +55,16 @@ public final class FileServerAPI : DotNetAPI { @objc(downloadProfilePicture:) public static func objc_downloadProfilePicture(_ downloadURL: String) -> AnyPromise { - return AnyPromise.from(downloadProfilePicture(downloadURL)) + return AnyPromise.from(downloadAttachment(downloadURL)) } - public static func downloadProfilePicture(_ downloadURL: String) -> Promise { + // MARK: Attachment Download + @objc(downloadAttachment:) + public static func objc_downloadAttachment(_ downloadURL: String) -> AnyPromise { + return AnyPromise.from(downloadAttachment(downloadURL)) + } + + public static func downloadAttachment(_ downloadURL: String) -> Promise { var error: NSError? var url = downloadURL if downloadURL.contains(fileStaticServer) { @@ -66,12 +72,12 @@ public final class FileServerAPI : DotNetAPI { } let request = AFHTTPRequestSerializer().request(withMethod: "GET", urlString: url, parameters: nil, error: &error) if let error = error { - print("[Loki] Couldn't download profile picture due to error: \(error).") + print("[Loki] Couldn't download attachment due to error: \(error).") return Promise(error: error) } return OnionRequestAPI.sendOnionRequest(request, to: server, using: fileServerPublicKey, isJSONRequired: false).map2 { json in guard let body = json["body"] as? JSON, let dataArray = body["data"] as? [UInt8] else { - print("[Loki] Couldn't download profile picture.") + print("[Loki] Couldn't download attachment.") return Data() } return Data(dataArray) diff --git a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.m b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.m index fb850789b..de487cac6 100644 --- a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.m +++ b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.m @@ -513,7 +513,6 @@ typedef void (^AttachmentDownloadFailure)(NSError *error); NSString *tempFilePath = [OWSTemporaryDirectoryAccessibleAfterFirstAuth() stringByAppendingPathComponent:[NSUUID UUID].UUIDString]; NSURL *tempFileURL = [NSURL fileURLWithPath:tempFilePath]; - __block NSURLSessionDownloadTask *task; void (^failureHandler)(NSError *) = ^(NSError *error) { OWSLogError(@"Failed to download attachment with error: %@", error.description); @@ -524,125 +523,28 @@ typedef void (^AttachmentDownloadFailure)(NSError *error); failureHandlerParam(task, error); }; + + AnyPromise *promise = [LKFileServerAPI downloadAttachment:location]; + [promise.then(^(NSData *data) { + BOOL success = [data writeToFile:tempFilePath atomically:YES]; + if (success) { + successHandler(tempFilePath); + } - NSString *method = @"GET"; - NSError *serializationError = nil; - NSMutableURLRequest *request = [manager.requestSerializer requestWithMethod:method - URLString:location - parameters:nil - error:&serializationError]; - if (serializationError) { - return failureHandler(serializationError); - } - - task = [manager downloadTaskWithRequest:request - progress:^(NSProgress *progress) { - OWSAssertDebug(progress != nil); - - // Don't do anything until we've received at least one byte of data. - if (progress.completedUnitCount < 1) { - return; - } - - void (^abortDownload)(void) = ^{ - OWSFailDebug(@"Download aborted."); - [task cancel]; - }; - - if (progress.totalUnitCount > kMaxDownloadSize || progress.completedUnitCount > kMaxDownloadSize) { - // A malicious service might send a misleading content length header, - // so.... - // - // If the current downloaded bytes or the expected total byes - // exceed the max download size, abort the download. - OWSLogError(@"Attachment download exceed expected content length: %lld, %lld.", - (long long)progress.totalUnitCount, - (long long)progress.completedUnitCount); - abortDownload(); - return; - } - - job.progress = progress.fractionCompleted; - - [self fireProgressNotification:MAX(kAttachmentDownloadProgressTheta, progress.fractionCompleted) - attachmentId:attachmentPointer.uniqueId]; - - // We only need to check the content length header once. - if (hasCheckedContentLength) { - return; - } - - // Once we've received some bytes of the download, check the content length - // header for the download. - // - // If the task doesn't exist, or doesn't have a response, or is missing - // the expected headers, or has an invalid or oversize content length, etc., - // abort the download. - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)task.response; - if (![httpResponse isKindOfClass:[NSHTTPURLResponse class]]) { - OWSLogError(@"Attachment download has missing or invalid response."); - abortDownload(); - return; - } - - NSDictionary *headers = [httpResponse allHeaderFields]; - if (![headers isKindOfClass:[NSDictionary class]]) { - OWSLogError(@"Attachment download invalid headers."); - abortDownload(); - return; - } - - - NSString *contentLength = headers[@"Content-Length"]; - if (![contentLength isKindOfClass:[NSString class]]) { - OWSLogError(@"Attachment download missing or invalid content length."); - abortDownload(); - return; - } - - - if (contentLength.longLongValue > kMaxDownloadSize) { - OWSLogError(@"Attachment download content length exceeds max download size."); - abortDownload(); - return; - } - - // This response has a valid content length that is less - // than our max download size. Proceed with the download. - hasCheckedContentLength = YES; + NSNumber *_Nullable fileSize = [OWSFileSystem fileSizeOfPath:tempFilePath]; + if (!fileSize) { + OWSLogError(@"Could not determine attachment file size."); + NSError *error = OWSErrorWithCodeDescription( + OWSErrorCodeInvalidMessage, NSLocalizedString(@"ERROR_MESSAGE_INVALID_MESSAGE", @"")); + return failureHandler(error); } - destination:^(NSURL *targetPath, NSURLResponse *response) { - return tempFileURL; + if (fileSize.unsignedIntegerValue > kMaxDownloadSize) { + OWSLogError(@"Attachment download length exceeds max size."); + NSError *error = OWSErrorWithCodeDescription( + OWSErrorCodeInvalidMessage, NSLocalizedString(@"ERROR_MESSAGE_INVALID_MESSAGE", @"")); + return failureHandler(error); } - completionHandler:^(NSURLResponse *response, NSURL *_Nullable filePath, NSError *_Nullable error) { - if (error) { - failureHandler(error); - return; - } - if (![tempFileURL isEqual:filePath]) { - OWSLogError(@"Unexpected temp file path."); - NSError *error = OWSErrorWithCodeDescription( - OWSErrorCodeInvalidMessage, NSLocalizedString(@"ERROR_MESSAGE_INVALID_MESSAGE", @"")); - return failureHandler(error); - } - - NSNumber *_Nullable fileSize = [OWSFileSystem fileSizeOfPath:tempFilePath]; - if (!fileSize) { - OWSLogError(@"Could not determine attachment file size."); - NSError *error = OWSErrorWithCodeDescription( - OWSErrorCodeInvalidMessage, NSLocalizedString(@"ERROR_MESSAGE_INVALID_MESSAGE", @"")); - return failureHandler(error); - } - if (fileSize.unsignedIntegerValue > kMaxDownloadSize) { - OWSLogError(@"Attachment download length exceeds max size."); - NSError *error = OWSErrorWithCodeDescription( - OWSErrorCodeInvalidMessage, NSLocalizedString(@"ERROR_MESSAGE_INVALID_MESSAGE", @"")); - return failureHandler(error); - } - successHandler(tempFilePath); - }]; - - [task resume]; + }) retainUntilComplete]; } - (void)fireProgressNotification:(CGFloat)progress attachmentId:(NSString *)attachmentId From 9cd52342e8a717286a429a7bf4d5b7920a033565 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Tue, 13 Oct 2020 14:25:12 +1100 Subject: [PATCH 05/10] clean --- .../src/Messages/Attachments/OWSAttachmentDownloads.m | 6 ------ 1 file changed, 6 deletions(-) diff --git a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.m b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.m index de487cac6..8a118d070 100644 --- a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.m +++ b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.m @@ -500,12 +500,6 @@ typedef void (^AttachmentDownloadFailure)(NSError *error); OWSAssertDebug(job); TSAttachmentPointer *attachmentPointer = job.attachmentPointer; - AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; - manager.requestSerializer = [AFHTTPRequestSerializer serializer]; - [manager.requestSerializer setValue:OWSMimeTypeApplicationOctetStream forHTTPHeaderField:@"Content-Type"]; - manager.responseSerializer = [AFHTTPResponseSerializer serializer]; - manager.completionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - // We want to avoid large downloads from a compromised or buggy service. const long kMaxDownloadSize = 10 * 1024 * 1024; __block BOOL hasCheckedContentLength = NO; From 76b6afe0e30ab8e2cb830295fbc02386f387c46b Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Tue, 13 Oct 2020 14:59:10 +1100 Subject: [PATCH 06/10] show an indefinite loader for onion routing download --- .../Cells/ConversationMediaView.swift | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/Cells/ConversationMediaView.swift b/Signal/src/ViewControllers/ConversationView/Cells/ConversationMediaView.swift index 304ede4d0..7e11383f0 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/ConversationMediaView.swift +++ b/Signal/src/ViewControllers/ConversationView/Cells/ConversationMediaView.swift @@ -169,10 +169,18 @@ public class ConversationMediaView: UIView { return } + let view: UIView backgroundColor = (Theme.isDarkThemeEnabled ? .ows_gray90 : .ows_gray05) - let progressView = MediaDownloadView(attachmentId: attachmentId, radius: maxMessageWidth * 0.1) - self.addSubview(progressView) - progressView.autoPinEdgesToSuperviewEdges() + if isOnionRouted { // Loki: Due to the way onion routing works we can't get upload progress for those attachments + let activityIndicatorView = UIActivityIndicatorView(style: .white) + activityIndicatorView.isHidden = false + activityIndicatorView.startAnimating() + view = activityIndicatorView + } else { + view = MediaDownloadView(attachmentId: attachmentId, radius: maxMessageWidth * 0.1) + } + addSubview(view) + view.autoPinEdgesToSuperviewEdges() } private func addUploadProgressIfNecessary(_ subview: UIView) -> Bool { From 0ae8da838a1f8100348243b7746d2d10b188e807 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 14 Oct 2020 10:51:07 +1100 Subject: [PATCH 07/10] Minor refactoring --- .../Cells/ConversationMediaView.swift | 2 +- .../MediaGalleryViewController.swift | 2 +- SignalMessaging/profiles/OWSProfileManager.m | 3 +- .../src/Loki/API/FileServerAPI.swift | 30 +++++++------------ .../Loki/API/Open Groups/PublicChatAPI.swift | 15 +++++----- .../Attachments/OWSAttachmentDownloads.m | 3 +- 6 files changed, 23 insertions(+), 32 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/Cells/ConversationMediaView.swift b/Signal/src/ViewControllers/ConversationView/Cells/ConversationMediaView.swift index 7e11383f0..5fa530c01 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/ConversationMediaView.swift +++ b/Signal/src/ViewControllers/ConversationView/Cells/ConversationMediaView.swift @@ -169,8 +169,8 @@ public class ConversationMediaView: UIView { return } - let view: UIView backgroundColor = (Theme.isDarkThemeEnabled ? .ows_gray90 : .ows_gray05) + let view: UIView if isOnionRouted { // Loki: Due to the way onion routing works we can't get upload progress for those attachments let activityIndicatorView = UIActivityIndicatorView(style: .white) activityIndicatorView.isHidden = false diff --git a/Signal/src/ViewControllers/MediaGalleryViewController.swift b/Signal/src/ViewControllers/MediaGalleryViewController.swift index ccc9c253e..e0e35dbca 100644 --- a/Signal/src/ViewControllers/MediaGalleryViewController.swift +++ b/Signal/src/ViewControllers/MediaGalleryViewController.swift @@ -377,7 +377,7 @@ class MediaGallery: NSObject, MediaGalleryDataSource, MediaTileViewControllerDel } guard let initialDetailItem = galleryItem else { - owsFailDebug("unexpectedly failed to build initialDetailItem.") +// owsFailDebug("unexpectedly failed to build initialDetailItem.") return } diff --git a/SignalMessaging/profiles/OWSProfileManager.m b/SignalMessaging/profiles/OWSProfileManager.m index 29636658d..006126dea 100644 --- a/SignalMessaging/profiles/OWSProfileManager.m +++ b/SignalMessaging/profiles/OWSProfileManager.m @@ -1158,8 +1158,7 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); OWSLogVerbose(@"downloading profile avatar: %@", userProfile.uniqueId); NSString *profilePictureURL = userProfile.avatarUrlPath; - AnyPromise *promise = [LKFileServerAPI downloadProfilePicture:profilePictureURL]; - [promise.then(^(NSData *data) { + [[LKFileServerAPI downloadAttachmentFrom:profilePictureURL].then(^(NSData *data) { @synchronized(self.currentAvatarDownloads) { [self.currentAvatarDownloads removeObject:userProfile.recipientId]; diff --git a/SignalServiceKit/src/Loki/API/FileServerAPI.swift b/SignalServiceKit/src/Loki/API/FileServerAPI.swift index 4ea2414dc..69ae71d67 100644 --- a/SignalServiceKit/src/Loki/API/FileServerAPI.swift +++ b/SignalServiceKit/src/Loki/API/FileServerAPI.swift @@ -18,7 +18,7 @@ public final class FileServerAPI : DotNetAPI { public static let fileSizeORMultiplier: Double = 6 @objc public static let server = "https://file.getsession.org" - @objc public static let fileStaticServer = "https://file-static.lokinet.org" + @objc public static let fileStorageBucketURL = "https://file-static.lokinet.org" // MARK: Storage override internal class var authTokenCollection: String { return "LokiStorageAuthTokenCollection" } @@ -53,34 +53,26 @@ public final class FileServerAPI : DotNetAPI { } } - @objc(downloadProfilePicture:) - public static func objc_downloadProfilePicture(_ downloadURL: String) -> AnyPromise { - return AnyPromise.from(downloadAttachment(downloadURL)) + // MARK: Attachments + @objc(downloadAttachmentFrom:) + public static func objc_downloadAttachment(from url: String) -> AnyPromise { + return AnyPromise.from(downloadAttachment(from: url)) } - // MARK: Attachment Download - @objc(downloadAttachment:) - public static func objc_downloadAttachment(_ downloadURL: String) -> AnyPromise { - return AnyPromise.from(downloadAttachment(downloadURL)) - } - - public static func downloadAttachment(_ downloadURL: String) -> Promise { + public static func downloadAttachment(from url: String) -> Promise { var error: NSError? - var url = downloadURL - if downloadURL.contains(fileStaticServer) { - url = downloadURL.replacingOccurrences(of: fileStaticServer, with: "\(server)/loki/v1") - } + let url = url.replacingOccurrences(of: fileStorageBucketURL, with: "\(server)/loki/v1") let request = AFHTTPRequestSerializer().request(withMethod: "GET", urlString: url, parameters: nil, error: &error) if let error = error { print("[Loki] Couldn't download attachment due to error: \(error).") return Promise(error: error) } return OnionRequestAPI.sendOnionRequest(request, to: server, using: fileServerPublicKey, isJSONRequired: false).map2 { json in - guard let body = json["body"] as? JSON, let dataArray = body["data"] as? [UInt8] else { - print("[Loki] Couldn't download attachment.") - return Data() + guard let body = json["body"] as? JSON, let data = body["data"] as? [UInt8] else { + print("[Loki] Couldn't parse attachment from: \(json).") + throw DotNetAPIError.parsingFailed } - return Data(dataArray) + return Data(data) } } diff --git a/SignalServiceKit/src/Loki/API/Open Groups/PublicChatAPI.swift b/SignalServiceKit/src/Loki/API/Open Groups/PublicChatAPI.swift index bc1cad0f6..d5d1b3be6 100644 --- a/SignalServiceKit/src/Loki/API/Open Groups/PublicChatAPI.swift +++ b/SignalServiceKit/src/Loki/API/Open Groups/PublicChatAPI.swift @@ -391,19 +391,20 @@ public final class PublicChatAPI : DotNetAPI { var error: NSError? let url = "\(server)/loki/v1\(profilePictureURL)" let request = AFHTTPRequestSerializer().request(withMethod: "GET", urlString: url, parameters: nil, error: &error) - request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)"] + request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ] if let error = error { print("[Loki] Couldn't download open group avatar due to error: \(error).") return } - OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey, isJSONRequired: false).map{ json in - guard let body = json["body"] as? JSON, let dataArray = body["data"] as? [UInt8] else { - print("[Loki] Couldn't download open group avatar.") + OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey, isJSONRequired: false).map(on: DispatchQueue.global(qos: .default)) { json in + guard let body = json["body"] as? JSON, let data = body["data"] as? [UInt8] else { + print("[Loki] Couldn't parse open group profile picture from: \(json).") return } - let avatarData = Data(dataArray) - let attachmentStream = TSAttachmentStream(contentType: OWSMimeTypeImageJpeg, byteCount: UInt32(avatarData.count), sourceFilename: nil, caption: nil, albumMessageId: nil) - try! attachmentStream.write(avatarData) + let profilePicture = Data(data) + let attachmentStream = TSAttachmentStream(contentType: OWSMimeTypeImageJpeg, byteCount: UInt32(profilePicture.count), + sourceFilename: nil, caption: nil, albumMessageId: nil) + try! attachmentStream.write(profilePicture) groupThread.updateAvatar(with: attachmentStream) } } diff --git a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.m b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.m index 8a118d070..f55716b41 100644 --- a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.m +++ b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.m @@ -518,8 +518,7 @@ typedef void (^AttachmentDownloadFailure)(NSError *error); failureHandlerParam(task, error); }; - AnyPromise *promise = [LKFileServerAPI downloadAttachment:location]; - [promise.then(^(NSData *data) { + [[LKFileServerAPI downloadAttachmentFrom:location].then(^(NSData *data) { BOOL success = [data writeToFile:tempFilePath atomically:YES]; if (success) { successHandler(tempFilePath); From 863b0944319e586e1726cf157521149873ed8fc0 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 14 Oct 2020 10:58:36 +1100 Subject: [PATCH 08/10] Re-enable debug assertion --- Signal/src/ViewControllers/MediaGalleryViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Signal/src/ViewControllers/MediaGalleryViewController.swift b/Signal/src/ViewControllers/MediaGalleryViewController.swift index e0e35dbca..ccc9c253e 100644 --- a/Signal/src/ViewControllers/MediaGalleryViewController.swift +++ b/Signal/src/ViewControllers/MediaGalleryViewController.swift @@ -377,7 +377,7 @@ class MediaGallery: NSObject, MediaGalleryDataSource, MediaTileViewControllerDel } guard let initialDetailItem = galleryItem else { -// owsFailDebug("unexpectedly failed to build initialDetailItem.") + owsFailDebug("unexpectedly failed to build initialDetailItem.") return } From bdd178b227968618ef2104f426ce4632d594caab Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Wed, 14 Oct 2020 14:07:18 +1100 Subject: [PATCH 09/10] onion routing attachment download for open groups --- .../src/Loki/API/FileServerAPI.swift | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/SignalServiceKit/src/Loki/API/FileServerAPI.swift b/SignalServiceKit/src/Loki/API/FileServerAPI.swift index 4ea2414dc..f1c7b1959 100644 --- a/SignalServiceKit/src/Loki/API/FileServerAPI.swift +++ b/SignalServiceKit/src/Loki/API/FileServerAPI.swift @@ -66,24 +66,38 @@ public final class FileServerAPI : DotNetAPI { public static func downloadAttachment(_ downloadURL: String) -> Promise { var error: NSError? - var url = downloadURL - if downloadURL.contains(fileStaticServer) { - url = downloadURL.replacingOccurrences(of: fileStaticServer, with: "\(server)/loki/v1") + let url = URL(string: downloadURL)! + var host = "https://\(url.host!)" + var actualDownloadURL = downloadURL + if fileStaticServer.contains(host) { + actualDownloadURL = downloadURL.replacingOccurrences(of: fileStaticServer, with: "\(server)/loki/v1") + host = server + } else { + actualDownloadURL = downloadURL.replacingOccurrences(of: host, with: "\(host)/loki/v1") } - let request = AFHTTPRequestSerializer().request(withMethod: "GET", urlString: url, parameters: nil, error: &error) + let request = AFHTTPRequestSerializer().request(withMethod: "GET", urlString: actualDownloadURL, parameters: nil, error: &error) if let error = error { print("[Loki] Couldn't download attachment due to error: \(error).") return Promise(error: error) } - return OnionRequestAPI.sendOnionRequest(request, to: server, using: fileServerPublicKey, isJSONRequired: false).map2 { json in - guard let body = json["body"] as? JSON, let dataArray = body["data"] as? [UInt8] else { - print("[Loki] Couldn't download attachment.") - return Data() + return getServerPublicKey(for: host).then2 { serverPublicKey in + OnionRequestAPI.sendOnionRequest(request, to: host, using: serverPublicKey, isJSONRequired: false).map2 { json in + guard let body = json["body"] as? JSON, let dataArray = body["data"] as? [UInt8] else { + print("[Loki] Couldn't download attachment.") + return Data() + } + return Data(dataArray) } - return Data(dataArray) } } + public static func getServerPublicKey(for host: String) -> Promise { + if server.contains(host) { + return Promise.value(fileServerPublicKey) + } + return PublicChatAPI.getOpenGroupServerPublicKey(for: host) + } + // MARK: Open Group Server Public Key public static func getPublicKey(for openGroupServer: String) -> Promise { let url = URL(string: "\(server)/loki/v1/getOpenGroupKey/\(URL(string: openGroupServer)!.host!)")! From 24825a2eea613b3c8c09527f1ba6e160190b093c Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Wed, 14 Oct 2020 14:58:49 +1100 Subject: [PATCH 10/10] minor refactor --- .../Loki/API/Open Groups/PublicChatAPI.swift | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/SignalServiceKit/src/Loki/API/Open Groups/PublicChatAPI.swift b/SignalServiceKit/src/Loki/API/Open Groups/PublicChatAPI.swift index bc1cad0f6..ccd3a9ce3 100644 --- a/SignalServiceKit/src/Loki/API/Open Groups/PublicChatAPI.swift +++ b/SignalServiceKit/src/Loki/API/Open Groups/PublicChatAPI.swift @@ -369,7 +369,7 @@ public final class PublicChatAPI : DotNetAPI { } } - static func updateProfileIfNeeded(for channel: UInt64, on server: String, from info: PublicChatInfo, token: String, serverPublicKey: String) { + static func updateProfileIfNeeded(for channel: UInt64, on server: String, from info: PublicChatInfo) { let storage = OWSPrimaryStorage.shared() let publicChatID = "\(server).\(channel)" try! Storage.writeSync { transaction in @@ -388,20 +388,7 @@ public final class PublicChatAPI : DotNetAPI { if oldProfilePictureURL != info.profilePictureURL || groupModel.groupImage == nil { storage.setProfilePictureURL(info.profilePictureURL, forPublicChatWithID: publicChatID, in: transaction) if let profilePictureURL = info.profilePictureURL { - var error: NSError? - let url = "\(server)/loki/v1\(profilePictureURL)" - let request = AFHTTPRequestSerializer().request(withMethod: "GET", urlString: url, parameters: nil, error: &error) - request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)"] - if let error = error { - print("[Loki] Couldn't download open group avatar due to error: \(error).") - return - } - OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey, isJSONRequired: false).map{ json in - guard let body = json["body"] as? JSON, let dataArray = body["data"] as? [UInt8] else { - print("[Loki] Couldn't download open group avatar.") - return - } - let avatarData = Data(dataArray) + FileServerAPI.downloadAttachment("\(server)\(profilePictureURL)").map { avatarData in let attachmentStream = TSAttachmentStream(contentType: OWSMimeTypeImageJpeg, byteCount: UInt32(avatarData.count), sourceFilename: nil, caption: nil, albumMessageId: nil) try! attachmentStream.write(avatarData) groupThread.updateAvatar(with: attachmentStream) @@ -441,7 +428,7 @@ public final class PublicChatAPI : DotNetAPI { storage.setUserCount(memberCount, forPublicChatWithID: "\(server).\(channel)", in: transaction) } let publicChatInfo = PublicChatInfo(displayName: displayName, profilePictureURL: profilePictureURL, memberCount: memberCount) - updateProfileIfNeeded(for: channel, on: server, from: publicChatInfo, token: token, serverPublicKey: serverPublicKey) + updateProfileIfNeeded(for: channel, on: server, from: publicChatInfo) return publicChatInfo } }