diff --git a/Pods b/Pods index db7ec4663..a1c073c81 160000 --- a/Pods +++ b/Pods @@ -1 +1 @@ -Subproject commit db7ec4663cdd5d9615f182dcc51731f82df4b297 +Subproject commit a1c073c81d62ae5b6e7dcd54f0d41b0cd0c254cd diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 0b624001c..aad3871f8 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -60,7 +60,6 @@ 343A65981FC4CFE7000477A1 /* ConversationScrollButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 343A65961FC4CFE6000477A1 /* ConversationScrollButton.m */; }; 3441FD9F21A3604F00BB9542 /* BackupRestoreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3441FD9E21A3604F00BB9542 /* BackupRestoreViewController.swift */; }; 34480B361FD0929200BC14EF /* ShareAppExtensionContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 34480B351FD0929200BC14EF /* ShareAppExtensionContext.m */; }; - 34480B491FD0A60200BC14EF /* OWSMath.h in Headers */ = {isa = PBXBuildFile; fileRef = 34480B481FD0A60200BC14EF /* OWSMath.h */; settings = {ATTRIBUTES = (Public, ); }; }; 34480B551FD0A7A400BC14EF /* DebugLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 34480B4D1FD0A7A300BC14EF /* DebugLogger.h */; settings = {ATTRIBUTES = (Public, ); }; }; 34480B561FD0A7A400BC14EF /* DebugLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 34480B4E1FD0A7A300BC14EF /* DebugLogger.m */; }; 34480B571FD0A7A400BC14EF /* OWSScrubbingLogFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 34480B4F1FD0A7A300BC14EF /* OWSScrubbingLogFormatter.h */; }; @@ -710,7 +709,6 @@ 34480B351FD0929200BC14EF /* ShareAppExtensionContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShareAppExtensionContext.m; sourceTree = ""; }; 34480B371FD092A900BC14EF /* SignalShareExtension-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SignalShareExtension-Bridging-Header.h"; sourceTree = ""; }; 34480B381FD092E300BC14EF /* SignalShareExtension-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SignalShareExtension-Prefix.pch"; sourceTree = ""; }; - 34480B481FD0A60200BC14EF /* OWSMath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMath.h; sourceTree = ""; }; 34480B4D1FD0A7A300BC14EF /* DebugLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugLogger.h; sourceTree = ""; }; 34480B4E1FD0A7A300BC14EF /* DebugLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugLogger.m; sourceTree = ""; }; 34480B4F1FD0A7A300BC14EF /* OWSScrubbingLogFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSScrubbingLogFormatter.h; sourceTree = ""; }; @@ -1564,7 +1562,6 @@ 346129AA1FD1F0EE00532771 /* OWSFormat.m */, 45666EC71D994C0D008FE134 /* OWSGroupAvatarBuilder.h */, 45666EC81D994C0D008FE134 /* OWSGroupAvatarBuilder.m */, - 34480B481FD0A60200BC14EF /* OWSMath.h */, 346129371FD1B47200532771 /* OWSPreferences.h */, 346129381FD1B47200532771 /* OWSPreferences.m */, 34641E172088D7E900E2EDE5 /* OWSScreenLock.swift */, @@ -2606,7 +2603,6 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 34480B491FD0A60200BC14EF /* OWSMath.h in Headers */, 346129E71FD5C0C600532771 /* OWSDatabaseMigrationRunner.h in Headers */, 34AC09F9211B39B100997B47 /* CountryCodeViewController.h in Headers */, 34ABB2C52090C59700C727A6 /* OWSResaveCollectionDBMigration.h in Headers */, diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index e4a613128..00e35e94d 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -24,7 +24,6 @@ #import #import #import -#import #import #import #import @@ -38,6 +37,7 @@ #import #import #import +#import #import #import #import diff --git a/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m b/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m index 4fac6d6e7..cf684f8ec 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m @@ -11,6 +11,7 @@ #import "UIColor+OWS.h" #import "UIFont+OWS.h" #import "ViewControllerUtils.h" +#import #import #import #import @@ -739,27 +740,24 @@ const CGFloat kMaxTextViewHeight = 98; [self ensureLinkPreviewViewWithState:[LinkPreviewLoading new]]; __weak ConversationInputToolbar *weakSelf = self; - [OWSLinkPreview tryToBuildPreviewInfoWithPreviewUrl:previewUrl - callbackQueue:dispatch_get_main_queue() - completion:^(OWSLinkPreviewDraft *_Nullable linkPreviewDraft) { - ConversationInputToolbar *_Nullable strongSelf = weakSelf; - if (!strongSelf) { - return; - } - if (strongSelf.inputLinkPreview != inputLinkPreview) { - // Obsolete callback. - return; - } - if (!linkPreviewDraft) { - // The link preview could not be loaded. - [strongSelf clearLinkPreviewView]; - return; - } - inputLinkPreview.linkPreviewDraft = linkPreviewDraft; - LinkPreviewDraft *viewState = [[LinkPreviewDraft alloc] - initWithLinkPreviewDraft:linkPreviewDraft]; - [strongSelf ensureLinkPreviewViewWithState:viewState]; - }]; + [[OWSLinkPreview tryToBuildPreviewInfoObjcWithPreviewUrl:previewUrl] + .then(^(OWSLinkPreviewDraft *linkPreviewDraft) { + ConversationInputToolbar *_Nullable strongSelf = weakSelf; + if (!strongSelf) { + return; + } + if (strongSelf.inputLinkPreview != inputLinkPreview) { + // Obsolete callback. + return; + } + inputLinkPreview.linkPreviewDraft = linkPreviewDraft; + LinkPreviewDraft *viewState = [[LinkPreviewDraft alloc] initWithLinkPreviewDraft:linkPreviewDraft]; + [strongSelf ensureLinkPreviewViewWithState:viewState]; + }) + .catch(^(id error) { + // The link preview could not be loaded. + [weakSelf clearLinkPreviewView]; + }) retainUntilComplete]; } - (void)ensureLinkPreviewViewWithState:(id)state diff --git a/Signal/src/ViewControllers/HomeView/HomeViewCell.m b/Signal/src/ViewControllers/HomeView/HomeViewCell.m index 820598db2..8b6b58093 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewCell.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewCell.m @@ -6,9 +6,9 @@ #import "OWSAvatarBuilder.h" #import "Signal-Swift.h" #import -#import #import #import +#import #import #import #import diff --git a/Signal/src/views/OWSProgressView.m b/Signal/src/views/OWSProgressView.m index 881638399..7e8f1985a 100644 --- a/Signal/src/views/OWSProgressView.m +++ b/Signal/src/views/OWSProgressView.m @@ -1,10 +1,10 @@ // -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. // #import "OWSProgressView.h" -#import #import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/SignalMessaging.h b/SignalMessaging/SignalMessaging.h index bd2a2b93c..5416e1aae 100644 --- a/SignalMessaging/SignalMessaging.h +++ b/SignalMessaging/SignalMessaging.h @@ -31,7 +31,6 @@ FOUNDATION_EXPORT const unsigned char SignalMessagingVersionString[]; #import #import #import -#import #import #import #import diff --git a/SignalMessaging/categories/UIView+OWS.h b/SignalMessaging/categories/UIView+OWS.h index bfb091e79..2a7f63224 100644 --- a/SignalMessaging/categories/UIView+OWS.h +++ b/SignalMessaging/categories/UIView+OWS.h @@ -2,8 +2,8 @@ // Copyright (c) 2019 Open Whisper Systems. All rights reserved. // -#import "OWSMath.h" #import +#import #import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalServiceKit/src/Messages/Interactions/OWSLinkPreview.swift b/SignalServiceKit/src/Messages/Interactions/OWSLinkPreview.swift index c3cea1fdd..6fb9b96fc 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSLinkPreview.swift +++ b/SignalServiceKit/src/Messages/Interactions/OWSLinkPreview.swift @@ -3,11 +3,16 @@ // import Foundation +import PromiseKit @objc public enum LinkPreviewError: Int, Error { case invalidInput case noPreview + case assertionFailure + case couldNotDownload + case featureDisabled + case invalidContent } // MARK: - OWSLinkPreviewDraft @@ -424,70 +429,57 @@ public class OWSLinkPreview: MTLModel { // This cache should only be accessed on serialQueue. private static var linkPreviewDraftCache: NSCache = NSCache() - // Completion will always be invoked exactly once. - // - // The completion is called with a link preview if one can be built for - // the message body. It building the preview fails, completion will be - // called with nil to avoid failing the message send. - @objc - public class func tryToBuildPreviewInfo(previewUrl: String?, - callbackQueue: DispatchQueue, - completion completionParam: @escaping (OWSLinkPreviewDraft?) -> Void) { - - // Ensure we invoke completion on the callback queue. - let completion = { (linkPreviewDraft) in - callbackQueue.async { - completionParam(linkPreviewDraft) - } + private class func cachedLinkPreview(forPreviewUrl previewUrl: String) -> OWSLinkPreviewDraft? { + var result: OWSLinkPreviewDraft? + serialQueue.sync { + result = linkPreviewDraftCache.object(forKey: previewUrl as AnyObject) } + return result + } + + private class func setCachedLinkPreview(_ linkPreviewDraft: OWSLinkPreviewDraft, + forPreviewUrl previewUrl: String) { + serialQueue.sync { + previewUrlCache.setObject(linkPreviewDraft, forKey: previewUrl as AnyObject) + } + } + + @objc + public class func tryToBuildPreviewInfoObjc(previewUrl: String?) -> AnyPromise { + return AnyPromise(tryToBuildPreviewInfo(previewUrl: previewUrl)) + } + + public class func tryToBuildPreviewInfo(previewUrl: String?) -> Promise { guard OWSLinkPreview.featureEnabled else { - completion(nil) - return + return Promise(error: LinkPreviewError.featureDisabled) } guard SSKPreferences.areLinkPreviewsEnabled() else { - completion(nil) - return + return Promise(error: LinkPreviewError.featureDisabled) } guard let previewUrl = previewUrl else { - completion(nil) - return + return Promise(error: LinkPreviewError.invalidInput) } - serialQueue.async { - if let cachedInfo = linkPreviewDraftCache.object(forKey: previewUrl as AnyObject) { - Logger.verbose("Link preview info cache hit.") - completion(cachedInfo) - return - } - downloadLink(url: previewUrl, completion: { (data) in - DispatchQueue.global().async { - guard let data = data else { - completion(nil) - return - } - parse(linkData: data, linkUrlString: previewUrl) { (linkPreviewDraft) in - guard let linkPreviewDraft = linkPreviewDraft else { - completion(nil) - return - } + if let cachedInfo = cachedLinkPreview(forPreviewUrl: previewUrl) { + Logger.verbose("Link preview info cache hit.") + return Promise.value(cachedInfo) + } + return downloadLink(url: previewUrl) + .then(on: DispatchQueue.global()) { (data) -> Promise in + return parse(linkData: data, linkUrlString: previewUrl) + .then(on: DispatchQueue.global()) { (linkPreviewDraft) -> Promise in guard linkPreviewDraft.isValid() else { - completion(nil) - return + return Promise(error: LinkPreviewError.noPreview) } - serialQueue.async { - previewUrlCache.setObject(linkPreviewDraft, forKey: previewUrl as AnyObject) + setCachedLinkPreview(linkPreviewDraft, forPreviewUrl: previewUrl) - completion(linkPreviewDraft) - } - } + return Promise.value(linkPreviewDraft) } - }) } } private class func downloadLink(url: String, - completion: @escaping (Data?) -> Void, - remainingRetries: UInt = 3) { + remainingRetries: UInt = 3) -> Promise { Logger.verbose("url: \(url)") @@ -507,6 +499,7 @@ public class OWSLinkPreview: MTLModel { sessionManager.requestSerializer.setValue(nil, forHTTPHeaderField: headerField) } + let (promise, resolver) = Promise.pending() sessionManager.get(url, parameters: [String: AnyObject](), progress: nil, @@ -514,65 +507,92 @@ public class OWSLinkPreview: MTLModel { guard let data = value as? Data else { Logger.warn("Result is not data: \(type(of: value)).") - completion(nil) + resolver.reject( LinkPreviewError.assertionFailure) return } - completion(data) + resolver.fulfill(data) }, failure: { _, error in Logger.verbose("Error: \(error)") guard isRetryable(error: error) else { Logger.warn("Error is not retryable.") - completion(nil) + resolver.reject( LinkPreviewError.couldNotDownload) return } guard remainingRetries > 0 else { Logger.warn("No more retries.") - completion(nil) + resolver.reject( LinkPreviewError.couldNotDownload) return } - OWSLinkPreview.downloadLink(url: url, completion: completion, remainingRetries: remainingRetries - 1) + OWSLinkPreview.downloadLink(url: url, remainingRetries: remainingRetries - 1) + .done(on: DispatchQueue.global()) { (data) in + resolver.fulfill(data) + }.catch(on: DispatchQueue.global()) { (error) in + resolver.reject( error) + }.retainUntilComplete() }) + return promise } - private class func downloadImage(url urlString: String, - completion: @escaping (Data?) -> Void) { + private class func downloadImage(url urlString: String, imageMimeType: String) -> Promise { Logger.verbose("url: \(urlString)") guard let url = URL(string: urlString) else { Logger.error("Could not parse URL.") - return completion(nil) + return Promise(error: LinkPreviewError.invalidInput) } guard let assetDescription = ProxiedContentAssetDescription(url: url as NSURL) else { Logger.error("Could not create asset description.") - return completion(nil) + return Promise(error: LinkPreviewError.invalidInput) } + let (promise, resolver) = Promise.pending() DispatchQueue.main.async { _ = ProxiedContentDownloader.defaultDownloader.requestAsset(assetDescription: assetDescription, priority: .high, success: { (_, asset) in - DispatchQueue.global().async { - do { - let data = try Data(contentsOf: URL(fileURLWithPath: asset.filePath)) - completion(data) - } catch { - owsFailDebug("Could not load asset data: \(type(of: asset.filePath)).") - completion(nil) - } - } - + resolver.fulfill(asset) }, failure: { (_) in - DispatchQueue.global().async { - Logger.verbose("Error downloading asset") - - completion(nil) - } + Logger.warn("Error downloading asset") + resolver.reject(LinkPreviewError.couldNotDownload) }) } + return promise.then(on: DispatchQueue.global()) { (asset: ProxiedContentAsset) -> Promise in + do { + let imageSize = NSData.imageSize(forFilePath: asset.filePath, mimeType: imageMimeType) + guard imageSize.width > 0, imageSize.height > 0 else { + Logger.error("Link preview is invalid or has invalid size.") + return Promise(error: LinkPreviewError.invalidContent) + } + let data = try Data(contentsOf: URL(fileURLWithPath: asset.filePath)) + + let maxImageSize: CGFloat = 1024 + let shouldResize = imageSize.width > maxImageSize || imageSize.height > maxImageSize + guard shouldResize else { + return Promise.value(data) + } + + guard let srcImage = UIImage(data: data) else { + Logger.error("Could not parse image.") + return Promise(error: LinkPreviewError.invalidContent) + } + guard let dstImage = srcImage.resized(withMaxDimensionPoints: maxImageSize) else { + Logger.error("Could not resize image.") + return Promise(error: LinkPreviewError.invalidContent) + } + guard let dstData = UIImageJPEGRepresentation(dstImage, 0.8) else { + Logger.error("Could not write resized image.") + return Promise(error: LinkPreviewError.invalidContent) + } + return Promise.value(dstData) + } catch { + owsFailDebug("Could not load asset data: \(type(of: asset.filePath)).") + return Promise(error: LinkPreviewError.assertionFailure) + } + } } private class func isRetryable(error: Error) -> Bool { @@ -589,12 +609,10 @@ public class OWSLinkPreview: MTLModel { // // private class func parse(linkData: Data, - linkUrlString: String, - completion: @escaping (OWSLinkPreviewDraft?) -> Void) { + linkUrlString: String) -> Promise { guard let linkText = String(bytes: linkData, encoding: .utf8) else { owsFailDebug("Could not parse link text.") - completion(nil) - return + return Promise(error: LinkPreviewError.invalidInput) } var title: String? @@ -610,25 +628,66 @@ public class OWSLinkPreview: MTLModel { Logger.verbose("title: \(String(describing: title))") guard let rawImageUrlString = NSRegularExpression.parseFirstMatch(pattern: "", text: linkText) else { - return completion(OWSLinkPreviewDraft(urlString: linkUrlString, title: title)) + return Promise.value(OWSLinkPreviewDraft(urlString: linkUrlString, title: title)) } guard let imageUrlString = decodeHTMLEntities(inString: rawImageUrlString)?.ows_stripped() else { - return completion(OWSLinkPreviewDraft(urlString: linkUrlString, title: title)) + return Promise.value(OWSLinkPreviewDraft(urlString: linkUrlString, title: title)) } guard isValidMediaUrl(imageUrlString) else { Logger.error("Invalid image URL.") - return completion(OWSLinkPreviewDraft(urlString: linkUrlString, title: title)) + return Promise.value(OWSLinkPreviewDraft(urlString: linkUrlString, title: title)) } - Logger.verbose("imageUrlString: \(imageUrlString)") - guard let imageUrl = URL(string: imageUrlString) else { + guard let imageFileExtension = fileExtension(forImageUrl: imageUrlString) else { + Logger.error("Image URL has unknown or invalid file extension: \(imageUrlString).") + return Promise.value(OWSLinkPreviewDraft(urlString: linkUrlString, title: title)) + } + guard let imageMimeType = mimetype(forImageFileExtension: imageFileExtension) else { + Logger.error("Image URL has unknown or invalid content type: \(imageUrlString).") + return Promise.value(OWSLinkPreviewDraft(urlString: linkUrlString, title: title)) + } + + return downloadImage(url: imageUrlString, imageMimeType: imageMimeType) + .then(on: DispatchQueue.global()) { (imageData: Data) -> Promise in + let imageFilePath = OWSFileSystem.temporaryFilePath(withFileExtension: imageFileExtension) + do { + try imageData.write(to: NSURL.fileURL(withPath: imageFilePath), options: .atomicWrite) + } catch let error as NSError { + owsFailDebug("file write failed: \(imageFilePath), \(error)") + return Promise(error: LinkPreviewError.assertionFailure) + } + // NOTE: imageSize(forFilePath:...) will call ows_isValidImage(...). + let imageSize = NSData.imageSize(forFilePath: imageFilePath, mimeType: imageMimeType) + let kMaxImageSize: CGFloat = 2048 + guard imageSize.width > 0, + imageSize.height > 0, + imageSize.width < kMaxImageSize, + imageSize.height < kMaxImageSize else { + Logger.error("Image has invalid size: \(imageSize).") + return Promise(error: LinkPreviewError.assertionFailure) + } + + let linkPreviewDraft = OWSLinkPreviewDraft(urlString: linkUrlString, title: title, imageFilePath: imageFilePath) + return Promise.value(linkPreviewDraft) + } + .recover(on: DispatchQueue.global()) { (_) -> Promise in + return Promise.value(OWSLinkPreviewDraft(urlString: linkUrlString, title: title)) + } + } + + private class func fileExtension(forImageUrl urlString: String) -> String? { + guard let imageUrl = URL(string: urlString) else { Logger.error("Could not parse image URL.") - return completion(OWSLinkPreviewDraft(urlString: linkUrlString, title: title)) + return nil } let imageFilename = imageUrl.lastPathComponent let imageFileExtension = (imageFilename as NSString).pathExtension.lowercased() + return imageFileExtension + } + + private class func mimetype(forImageFileExtension imageFileExtension: String) -> String? { guard let imageMimeType = MIMETypeUtil.mimeType(forFileExtension: imageFileExtension) else { Logger.error("Image URL has unknown content type: \(imageFileExtension).") - return completion(OWSLinkPreviewDraft(urlString: linkUrlString, title: title)) + return nil } let kValidMimeTypes = [ OWSMimeTypeImagePng, @@ -636,36 +695,9 @@ public class OWSLinkPreview: MTLModel { ] guard kValidMimeTypes.contains(imageMimeType) else { Logger.error("Image URL has invalid content type: \(imageMimeType).") - return completion(OWSLinkPreviewDraft(urlString: linkUrlString, title: title)) + return nil } - - downloadImage(url: imageUrlString, - completion: { (imageData) in - guard let imageData = imageData else { - Logger.error("Could not download image.") - return completion(OWSLinkPreviewDraft(urlString: linkUrlString, title: title)) - } - let imageFilePath = OWSFileSystem.temporaryFilePath(withFileExtension: imageFileExtension) - do { - try imageData.write(to: NSURL.fileURL(withPath: imageFilePath), options: .atomicWrite) - } catch let error as NSError { - owsFailDebug("file write failed: \(imageFilePath), \(error)") - return completion(OWSLinkPreviewDraft(urlString: linkUrlString, title: title)) - } - // NOTE: imageSize(forFilePath:...) will call ows_isValidImage(...). - let imageSize = NSData.imageSize(forFilePath: imageFilePath, mimeType: imageMimeType) - let kMaxImageSize: CGFloat = 2048 - guard imageSize.width > 0, - imageSize.height > 0, - imageSize.width < kMaxImageSize, - imageSize.height < kMaxImageSize else { - Logger.error("Image has invalid size: \(imageSize).") - return completion(OWSLinkPreviewDraft(urlString: linkUrlString, title: title)) - } - - let linkPreviewDraft = OWSLinkPreviewDraft(urlString: linkUrlString, title: title, imageFilePath: imageFilePath) - completion(linkPreviewDraft) - }) + return imageMimeType } private class func decodeHTMLEntities(inString value: String) -> String? { diff --git a/SignalMessaging/utils/OWSMath.h b/SignalServiceKit/src/Util/OWSMath.h similarity index 97% rename from SignalMessaging/utils/OWSMath.h rename to SignalServiceKit/src/Util/OWSMath.h index d159c9f36..1a9ec40cb 100644 --- a/SignalMessaging/utils/OWSMath.h +++ b/SignalServiceKit/src/Util/OWSMath.h @@ -1,5 +1,5 @@ // -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. // NS_ASSUME_NONNULL_BEGIN diff --git a/SignalShareExtension/SignalShareExtension-Bridging-Header.h b/SignalShareExtension/SignalShareExtension-Bridging-Header.h index c7a9dd1b8..bc241fe7c 100644 --- a/SignalShareExtension/SignalShareExtension-Bridging-Header.h +++ b/SignalShareExtension/SignalShareExtension-Bridging-Header.h @@ -1,5 +1,5 @@ // -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. // #import @@ -14,7 +14,6 @@ #import #import #import -#import #import #import #import @@ -23,5 +22,6 @@ #import #import #import +#import #import #import