// // Copyright 2019 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import Foundation class DownloadStickerPackOperation: CDNDownloadOperation { private let stickerPackInfo: StickerPackInfo private let success: (StickerPack) -> Void private let failure: (Error) -> Void @objc public init( stickerPackInfo: StickerPackInfo, success: @escaping (StickerPack) -> Void, failure: @escaping (Error) -> Void ) { owsAssertDebug(stickerPackInfo.packId.count > 0) owsAssertDebug(stickerPackInfo.packKey.count > 0) self.stickerPackInfo = stickerPackInfo self.success = success self.failure = failure super.init() } override public func run() { if let stickerPack = StickerManager.fetchStickerPack(stickerPackInfo: stickerPackInfo) { success(stickerPack) self.reportSuccess() return } // https://cdn.signal.org/stickers//manifest.proto let urlPath = "stickers/\(stickerPackInfo.packId.hexadecimalString)/manifest.proto" firstly { try tryToDownload(urlPath: urlPath, maxDownloadSize: kMaxStickerPackDownloadSize) }.done(on: DispatchQueue.global()) { [weak self] (url: URL) in guard let self = self else { return } do { let url = try StickerManager.decrypt(at: url, packKey: self.stickerPackInfo.packKey) let plaintext = try Data(contentsOf: url) let stickerPack = try self.parseStickerPackManifest(stickerPackInfo: self.stickerPackInfo, manifestData: plaintext) self.success(stickerPack) self.reportSuccess() } catch { owsFailDebug("Decryption failed: \(error)") self.markUrlPathAsCorrupt(urlPath) // Fail immediately; do not retry. return self.reportError(SSKUnretryableError.stickerDecryptionFailure) } }.catch(on: DispatchQueue.global()) { [weak self] error in guard let self = self else { return } if error.hasFatalHttpStatusCode() { StickerManager.markStickerPackAsMissing(stickerPackInfo: self.stickerPackInfo) } return self.reportError(withUndefinedRetry: error) } } private func parseStickerPackManifest(stickerPackInfo: StickerPackInfo, manifestData: Data) throws -> StickerPack { owsAssertDebug(manifestData.count > 0) let manifestProto: SSKProtoPack do { manifestProto = try SSKProtoPack(serializedData: manifestData) } catch let error as NSError { owsFailDebug("Couldn't parse protos: \(error)") throw StickerError.invalidInput } let title = parseOptionalString(manifestProto.title) let author = parseOptionalString(manifestProto.author) let manifestCover = parsePackItem(manifestProto.cover) var items = [StickerPackItem]() for stickerProto in manifestProto.stickers { if let item = parsePackItem(stickerProto) { items.append(item) } } guard let firstItem = items.first else { owsFailDebug("Invalid manifest, no stickers") throw StickerError.invalidInput } let cover = manifestCover ?? firstItem let stickerPack = StickerPack(info: stickerPackInfo, title: title, author: author, cover: cover, stickers: items) return stickerPack } private func parseOptionalString(_ value: String?) -> String? { value?.ows_stripped().nilIfEmpty } private func parsePackItem(_ proto: SSKProtoPackSticker?) -> StickerPackItem? { guard let proto = proto else { return nil } let stickerId = proto.id let emojiString = parseOptionalString(proto.emoji) ?? "" let contentType = parseOptionalString(proto.contentType) ?? "" return StickerPackItem(stickerId: stickerId, emojiString: emojiString, contentType: contentType) } override public func didFail(error: Error) { Logger.error("Download exhausted retries: \(error)") failure(error) } }