From 9eea1a3a830a4a1e9c04545e97c9b14d69dcd5fe Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 21 Oct 2019 09:32:28 +1100 Subject: [PATCH] Parse group attachments --- .../API/Public Chat/LokiPublicChatAPI.swift | 13 +++++++--- .../Public Chat/LokiPublicChatMessage.swift | 24 ++++++++++++------- .../Public Chat/LokiPublicChatPoller.swift | 15 ++++++++++++ .../Attachments/OWSAttachmentDownloads.m | 15 ++++++++---- .../src/Messages/Attachments/TSAttachment.h | 2 +- .../src/Messages/Attachments/TSAttachment.m | 6 ++--- .../Attachments/TSAttachmentPointer.h | 2 +- .../Attachments/TSAttachmentPointer.m | 4 +++- .../src/Messages/OWSMessageSender.m | 4 ++-- 9 files changed, 60 insertions(+), 25 deletions(-) diff --git a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift index b95bbeb2a..8739b4c3b 100644 --- a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift +++ b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift @@ -9,7 +9,8 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { private static let maxRetryCount: UInt = 8 // MARK: Public Chat - @objc private static let channelInfoType = "net.patter-app.settings" + private static let channelInfoType = "net.patter-app.settings" + private static let attachmentType = "net.app.core.oembed" @objc public static let publicChatMessageType = "network.loki.messenger.publicChat" @objc public static let defaultChats: [LokiPublicChat] = { @@ -90,7 +91,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { return rawMessages.flatMap { message in let isDeleted = (message["is_deleted"] as? Int == 1) guard !isDeleted else { return nil } - guard let annotations = message["annotations"] as? [JSON], let annotation = annotations.first, let value = annotation["value"] as? JSON, + guard let annotations = message["annotations"] as? [JSON], let annotation = annotations.first(where: { $0["type"] as? String == publicChatMessageType }), let value = annotation["value"] as? JSON, let serverID = message["id"] as? UInt64, let hexEncodedSignatureData = value["sig"] as? String, let signatureVersion = value["sigver"] as? UInt64, let body = message["text"] as? String, let user = message["user"] as? JSON, let hexEncodedPublicKey = user["username"] as? String, let timestamp = value["timestamp"] as? UInt64 else { @@ -108,7 +109,13 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { quote = nil } let signature = LokiPublicChatMessage.Signature(data: Data(hex: hexEncodedSignatureData), version: signatureVersion) - let result = LokiPublicChatMessage(serverID: serverID, hexEncodedPublicKey: hexEncodedPublicKey, displayName: displayName, body: body, type: publicChatMessageType, timestamp: timestamp, quote: quote, attachments: [], signature: signature) + let attachmentsAsJSON = annotations.filter { $0["type"] as? String == attachmentType } + let attachments: [LokiPublicChatMessage.Attachment] = attachmentsAsJSON.compactMap { attachmentAsJSON in + guard let value = attachmentAsJSON["value"] as? JSON, let server = value["server"] as? String, let serverID = value["id"] as? UInt64, let contentType = value["contentType"] as? String, let size = value["size"] as? UInt, let fileName = value["fileName"] as? String, let flags = value["flags"] as? UInt, let width = value["width"] as? UInt, let height = value["height"] as? UInt, let url = value["url"] as? String else { return nil } + let caption = value["caption"] as? String + return LokiPublicChatMessage.Attachment(server: server, serverID: serverID, contentType: contentType, size: size, fileName: fileName, flags: flags, width: width, height: height, caption: caption, url: url) + } + let result = LokiPublicChatMessage(serverID: serverID, hexEncodedPublicKey: hexEncodedPublicKey, displayName: displayName, body: body, type: publicChatMessageType, timestamp: timestamp, quote: quote, attachments: attachments, signature: signature) guard result.hasValidSignature() else { print("[Loki] Ignoring public chat message with invalid signature.") return nil diff --git a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatMessage.swift b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatMessage.swift index 9a75cc5fe..c7c51ce58 100644 --- a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatMessage.swift +++ b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatMessage.swift @@ -29,14 +29,16 @@ public final class LokiPublicChatMessage : NSObject { } public struct Attachment { + public let server: String public let serverID: UInt64 - public let kind: Kind + public let contentType: String + public let size: UInt + public let fileName: String + public let flags: UInt public let width: UInt public let height: UInt public let caption: String? public let url: String - public let server: String - public let serverDisplayName: String public enum Kind : String { case photo, video } } @@ -111,10 +113,15 @@ public final class LokiPublicChatMessage : NSObject { } let annotation: JSON = [ "type" : type, "value" : value ] let attachmentAnnotations: [JSON] = attachments.map { attachment in - var attachmentValue: JSON = [ "version" : 1, "type" : attachment.kind.rawValue, "width" : attachment.width, "height" : attachment.height, - "url" : attachment.url, "provider_name" : attachment.serverDisplayName, "provider_url" : attachment.server ] + let type = attachment.contentType.hasPrefix("image") ? "photo" : "video" // TODO: We should do better than this + var attachmentValue: JSON = [ + // Field required by the .NET API + "version" : 1, "type" : type, + // Custom fields + "server" : attachment.server, "id" : attachment.serverID, "contentType" : attachment.contentType, "size" : attachment.size, "fileName" : attachment.fileName, "flags" : attachment.flags, "width" : attachment.width, "height" : attachment.height, "url" : attachment.url + ] if let caption = attachment.caption { - attachmentValue["title"] = attachment.caption + attachmentValue["caption"] = attachment.caption } return [ "type" : attachmentType, "value" : attachmentValue ] } @@ -126,9 +133,8 @@ public final class LokiPublicChatMessage : NSObject { } // MARK: Convenience - @objc public func addAttachment(serverID: UInt64, kind: String, width: UInt, height: UInt, caption: String?, url: String, server: String, serverDisplayName: String) { - guard let kind = Attachment.Kind(rawValue: kind) else { preconditionFailure() } - let attachment = Attachment(serverID: serverID, kind: kind, width: width, height: height, caption: caption, url: url, server: server, serverDisplayName: serverDisplayName) + @objc public func addAttachment(server: String, serverID: UInt64, contentType: String, size: UInt, fileName: String, flags: UInt, width: UInt, height: UInt, caption: String?, url: String) { + let attachment = Attachment(server: server, serverID: serverID, contentType: contentType, size: size, fileName: fileName, flags: flags, width: width, height: height, caption: caption, url: url) attachments.append(attachment) } diff --git a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatPoller.swift b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatPoller.swift index 47159fbf8..9d4ff42dc 100644 --- a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatPoller.swift +++ b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatPoller.swift @@ -54,6 +54,21 @@ public final class LokiPublicChatPoller : NSObject { let groupContext = SSKProtoGroupContext.builder(id: id, type: .deliver) groupContext.setName(publicChat.displayName) let dataMessage = SSKProtoDataMessage.builder() + let attachments: [SSKProtoAttachmentPointer] = message.attachments.map { attachment in + let result = SSKProtoAttachmentPointer.builder(id: attachment.serverID) + result.setContentType(attachment.contentType) + result.setSize(UInt32(attachment.size)) + result.setFileName(attachment.fileName) + result.setFlags(UInt32(attachment.flags)) + result.setWidth(UInt32(attachment.width)) + result.setHeight(UInt32(attachment.height)) + if let caption = attachment.caption { + result.setCaption(caption) + } + result.setUrl(attachment.url) + return try! result.build() + } + dataMessage.setAttachments(attachments) dataMessage.setTimestamp(message.timestamp) dataMessage.setGroup(try! groupContext.build()) if let quote = message.quote { diff --git a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.m b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.m index 52339c018..ceaa53bd8 100644 --- a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.m +++ b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.m @@ -425,11 +425,16 @@ typedef void (^AttachmentDownloadFailure)(NSError *error); OWSAssertDebug(attachmentPointer); NSError *decryptError; - NSData *_Nullable plaintext = [Cryptography decryptAttachment:cipherText - withKey:attachmentPointer.encryptionKey - digest:attachmentPointer.digest - unpaddedSize:attachmentPointer.byteCount - error:&decryptError]; + NSData *_Nullable plaintext; + if (attachmentPointer.encryptionKey != nil) { + plaintext = [Cryptography decryptAttachment:cipherText + withKey:attachmentPointer.encryptionKey + digest:attachmentPointer.digest + unpaddedSize:attachmentPointer.byteCount + error:&decryptError]; + } else { + plaintext = cipherText; + } if (decryptError) { OWSLogError(@"failed to decrypt with error: %@", decryptError); diff --git a/SignalServiceKit/src/Messages/Attachments/TSAttachment.h b/SignalServiceKit/src/Messages/Attachments/TSAttachment.h index 8efa7124b..e4b15002c 100644 --- a/SignalServiceKit/src/Messages/Attachments/TSAttachment.h +++ b/SignalServiceKit/src/Messages/Attachments/TSAttachment.h @@ -57,7 +57,7 @@ typedef NS_ENUM(NSUInteger, TSAttachmentType) { // This constructor is used for new instances of TSAttachmentPointer, // i.e. undownloaded incoming attachments. - (instancetype)initWithServerId:(UInt64)serverId - encryptionKey:(NSData *)encryptionKey + encryptionKey:(nullable NSData *)encryptionKey byteCount:(UInt32)byteCount contentType:(NSString *)contentType sourceFilename:(nullable NSString *)sourceFilename diff --git a/SignalServiceKit/src/Messages/Attachments/TSAttachment.m b/SignalServiceKit/src/Messages/Attachments/TSAttachment.m index 692f1ac52..75e82b0bd 100644 --- a/SignalServiceKit/src/Messages/Attachments/TSAttachment.m +++ b/SignalServiceKit/src/Messages/Attachments/TSAttachment.m @@ -28,7 +28,7 @@ NSUInteger const TSAttachmentSchemaVersion = 4; // This constructor is used for new instances of TSAttachmentPointer, // i.e. undownloaded incoming attachments. - (instancetype)initWithServerId:(UInt64)serverId - encryptionKey:(NSData *)encryptionKey + encryptionKey:(nullable NSData *)encryptionKey byteCount:(UInt32)byteCount contentType:(NSString *)contentType sourceFilename:(nullable NSString *)sourceFilename @@ -36,7 +36,7 @@ NSUInteger const TSAttachmentSchemaVersion = 4; albumMessageId:(nullable NSString *)albumMessageId { OWSAssertDebug(serverId > 0); - OWSAssertDebug(encryptionKey.length > 0); +// OWSAssertDebug(encryptionKey.length > 0); if (byteCount <= 0) { // This will fail with legacy iOS clients which don't upload attachment size. OWSLogWarn(@"Missing byteCount for attachment with serverId: %lld", serverId); @@ -138,7 +138,7 @@ NSUInteger const TSAttachmentSchemaVersion = 4; { if (!pointer.lazyRestoreFragment) { OWSAssertDebug(pointer.serverId > 0); - OWSAssertDebug(pointer.encryptionKey.length > 0); + // OWSAssertDebug(pointer.encryptionKey.length > 0); if (pointer.byteCount <= 0) { // This will fail with legacy iOS clients which don't upload attachment size. OWSLogWarn(@"Missing pointer.byteCount for attachment with serverId: %lld", pointer.serverId); diff --git a/SignalServiceKit/src/Messages/Attachments/TSAttachmentPointer.h b/SignalServiceKit/src/Messages/Attachments/TSAttachmentPointer.h index 9090c9cf8..02af7b9a7 100644 --- a/SignalServiceKit/src/Messages/Attachments/TSAttachmentPointer.h +++ b/SignalServiceKit/src/Messages/Attachments/TSAttachmentPointer.h @@ -44,7 +44,7 @@ typedef NS_ENUM(NSUInteger, TSAttachmentPointerState) { - (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; - (instancetype)initWithServerId:(UInt64)serverId - key:(NSData *)key + key:(nullable NSData *)key digest:(nullable NSData *)digest byteCount:(UInt32)byteCount contentType:(NSString *)contentType diff --git a/SignalServiceKit/src/Messages/Attachments/TSAttachmentPointer.m b/SignalServiceKit/src/Messages/Attachments/TSAttachmentPointer.m index 58186c8d7..196d73810 100644 --- a/SignalServiceKit/src/Messages/Attachments/TSAttachmentPointer.m +++ b/SignalServiceKit/src/Messages/Attachments/TSAttachmentPointer.m @@ -54,7 +54,7 @@ NS_ASSUME_NONNULL_BEGIN } - (instancetype)initWithServerId:(UInt64)serverId - key:(NSData *)key + key:(nullable NSData *)key digest:(nullable NSData *)digest byteCount:(UInt32)byteCount contentType:(NSString *)contentType @@ -112,10 +112,12 @@ NS_ASSUME_NONNULL_BEGIN OWSFailDebug(@"Invalid attachment id."); return nil; } + /* if (attachmentProto.key.length < 1) { OWSFailDebug(@"Invalid attachment key."); return nil; } + */ NSString *_Nullable fileName = attachmentProto.fileName; NSString *_Nullable contentType = attachmentProto.contentType; if (contentType.length < 1) { diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index 6bd4b006f..ab3f38223 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -1208,13 +1208,13 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; quotedMessageServerID = [LKDatabaseUtilities getServerIDForQuoteWithID:quoteID quoteeHexEncodedPublicKey:quoteeHexEncodedPublicKey threadID:messageSend.thread.uniqueId transaction:transaction]; }]; } - LKGroupMessage *groupMessage = [[LKGroupMessage alloc] initWithHexEncodedPublicKey:userHexEncodedPublicKey displayName:displayName body:message.body type:LKPublicChatAPI.publicChatMessageType + LKGroupMessage *groupMessage = [[LKGroupMessage alloc] initWithHexEncodedPublicKey:userHexEncodedPublicKey displayName:displayName body:@"Test" type:LKPublicChatAPI.publicChatMessageType timestamp:message.timestamp quotedMessageTimestamp:quoteID quoteeHexEncodedPublicKey:quoteeHexEncodedPublicKey quotedMessageBody:quote.body quotedMessageServerID:quotedMessageServerID signatureData:nil signatureVersion:0]; for (NSString *attachmentID in message.attachmentIds) { TSAttachmentStream *attachment = [TSAttachmentStream fetchObjectWithUniqueID:attachmentID]; if (attachment == nil) { continue; } // TODO: Videos - [groupMessage addAttachmentWithServerID:attachment.serverId kind:@"photo" width:@(attachment.imageSize.width).unsignedIntegerValue height:@(attachment.imageSize.height).unsignedIntegerValue caption:attachment.caption url:attachment.downloadURL server:publicChat.server serverDisplayName:publicChat.displayName]; + [groupMessage addAttachmentWithServer:publicChat.server serverID:attachment.serverId contentType:attachment.contentType size:attachment.byteCount fileName:attachment.sourceFilename flags:0 width:@(attachment.imageSize.width).unsignedIntegerValue height:@(attachment.imageSize.height).unsignedIntegerValue caption:attachment.caption url:attachment.downloadURL]; } [[LKPublicChatAPI sendMessage:groupMessage toGroup:publicChat.channel onServer:publicChat.server] .thenOn(OWSDispatch.sendingQueue, ^(LKGroupMessage *groupMessage) {