Merge branch 'charlesmchen/linkPreviewImageMaxSize'

pull/1/head
Matthew Chen 6 years ago
commit 9ae817d94d

@ -1 +1 @@
Subproject commit db7ec4663cdd5d9615f182dcc51731f82df4b297 Subproject commit a1c073c81d62ae5b6e7dcd54f0d41b0cd0c254cd

@ -60,7 +60,6 @@
343A65981FC4CFE7000477A1 /* ConversationScrollButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 343A65961FC4CFE6000477A1 /* ConversationScrollButton.m */; }; 343A65981FC4CFE7000477A1 /* ConversationScrollButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 343A65961FC4CFE6000477A1 /* ConversationScrollButton.m */; };
3441FD9F21A3604F00BB9542 /* BackupRestoreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3441FD9E21A3604F00BB9542 /* BackupRestoreViewController.swift */; }; 3441FD9F21A3604F00BB9542 /* BackupRestoreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3441FD9E21A3604F00BB9542 /* BackupRestoreViewController.swift */; };
34480B361FD0929200BC14EF /* ShareAppExtensionContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 34480B351FD0929200BC14EF /* ShareAppExtensionContext.m */; }; 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, ); }; }; 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 */; }; 34480B561FD0A7A400BC14EF /* DebugLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 34480B4E1FD0A7A300BC14EF /* DebugLogger.m */; };
34480B571FD0A7A400BC14EF /* OWSScrubbingLogFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 34480B4F1FD0A7A300BC14EF /* OWSScrubbingLogFormatter.h */; }; 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 = "<group>"; }; 34480B351FD0929200BC14EF /* ShareAppExtensionContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShareAppExtensionContext.m; sourceTree = "<group>"; };
34480B371FD092A900BC14EF /* SignalShareExtension-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SignalShareExtension-Bridging-Header.h"; sourceTree = "<group>"; }; 34480B371FD092A900BC14EF /* SignalShareExtension-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SignalShareExtension-Bridging-Header.h"; sourceTree = "<group>"; };
34480B381FD092E300BC14EF /* SignalShareExtension-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SignalShareExtension-Prefix.pch"; sourceTree = "<group>"; }; 34480B381FD092E300BC14EF /* SignalShareExtension-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SignalShareExtension-Prefix.pch"; sourceTree = "<group>"; };
34480B481FD0A60200BC14EF /* OWSMath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMath.h; sourceTree = "<group>"; };
34480B4D1FD0A7A300BC14EF /* DebugLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugLogger.h; sourceTree = "<group>"; }; 34480B4D1FD0A7A300BC14EF /* DebugLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugLogger.h; sourceTree = "<group>"; };
34480B4E1FD0A7A300BC14EF /* DebugLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugLogger.m; sourceTree = "<group>"; }; 34480B4E1FD0A7A300BC14EF /* DebugLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugLogger.m; sourceTree = "<group>"; };
34480B4F1FD0A7A300BC14EF /* OWSScrubbingLogFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSScrubbingLogFormatter.h; sourceTree = "<group>"; }; 34480B4F1FD0A7A300BC14EF /* OWSScrubbingLogFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSScrubbingLogFormatter.h; sourceTree = "<group>"; };
@ -1564,7 +1562,6 @@
346129AA1FD1F0EE00532771 /* OWSFormat.m */, 346129AA1FD1F0EE00532771 /* OWSFormat.m */,
45666EC71D994C0D008FE134 /* OWSGroupAvatarBuilder.h */, 45666EC71D994C0D008FE134 /* OWSGroupAvatarBuilder.h */,
45666EC81D994C0D008FE134 /* OWSGroupAvatarBuilder.m */, 45666EC81D994C0D008FE134 /* OWSGroupAvatarBuilder.m */,
34480B481FD0A60200BC14EF /* OWSMath.h */,
346129371FD1B47200532771 /* OWSPreferences.h */, 346129371FD1B47200532771 /* OWSPreferences.h */,
346129381FD1B47200532771 /* OWSPreferences.m */, 346129381FD1B47200532771 /* OWSPreferences.m */,
34641E172088D7E900E2EDE5 /* OWSScreenLock.swift */, 34641E172088D7E900E2EDE5 /* OWSScreenLock.swift */,
@ -2606,7 +2603,6 @@
isa = PBXHeadersBuildPhase; isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
34480B491FD0A60200BC14EF /* OWSMath.h in Headers */,
346129E71FD5C0C600532771 /* OWSDatabaseMigrationRunner.h in Headers */, 346129E71FD5C0C600532771 /* OWSDatabaseMigrationRunner.h in Headers */,
34AC09F9211B39B100997B47 /* CountryCodeViewController.h in Headers */, 34AC09F9211B39B100997B47 /* CountryCodeViewController.h in Headers */,
34ABB2C52090C59700C727A6 /* OWSResaveCollectionDBMigration.h in Headers */, 34ABB2C52090C59700C727A6 /* OWSResaveCollectionDBMigration.h in Headers */,

@ -24,7 +24,6 @@
#import <SignalMessaging/AppSetup.h> #import <SignalMessaging/AppSetup.h>
#import <SignalMessaging/Environment.h> #import <SignalMessaging/Environment.h>
#import <SignalMessaging/OWSContactsManager.h> #import <SignalMessaging/OWSContactsManager.h>
#import <SignalMessaging/OWSMath.h>
#import <SignalMessaging/OWSNavigationController.h> #import <SignalMessaging/OWSNavigationController.h>
#import <SignalMessaging/OWSPreferences.h> #import <SignalMessaging/OWSPreferences.h>
#import <SignalMessaging/OWSProfileManager.h> #import <SignalMessaging/OWSProfileManager.h>
@ -38,6 +37,7 @@
#import <SignalServiceKit/OWSFailedAttachmentDownloadsJob.h> #import <SignalServiceKit/OWSFailedAttachmentDownloadsJob.h>
#import <SignalServiceKit/OWSFailedMessagesJob.h> #import <SignalServiceKit/OWSFailedMessagesJob.h>
#import <SignalServiceKit/OWSIncompleteCallsJob.h> #import <SignalServiceKit/OWSIncompleteCallsJob.h>
#import <SignalServiceKit/OWSMath.h>
#import <SignalServiceKit/OWSMessageManager.h> #import <SignalServiceKit/OWSMessageManager.h>
#import <SignalServiceKit/OWSMessageSender.h> #import <SignalServiceKit/OWSMessageSender.h>
#import <SignalServiceKit/OWSPrimaryStorage+Calling.h> #import <SignalServiceKit/OWSPrimaryStorage+Calling.h>

@ -11,6 +11,7 @@
#import "UIColor+OWS.h" #import "UIColor+OWS.h"
#import "UIFont+OWS.h" #import "UIFont+OWS.h"
#import "ViewControllerUtils.h" #import "ViewControllerUtils.h"
#import <PromiseKit/AnyPromise.h>
#import <SignalMessaging/OWSFormat.h> #import <SignalMessaging/OWSFormat.h>
#import <SignalMessaging/SignalMessaging-Swift.h> #import <SignalMessaging/SignalMessaging-Swift.h>
#import <SignalMessaging/UIView+OWS.h> #import <SignalMessaging/UIView+OWS.h>
@ -739,27 +740,24 @@ const CGFloat kMaxTextViewHeight = 98;
[self ensureLinkPreviewViewWithState:[LinkPreviewLoading new]]; [self ensureLinkPreviewViewWithState:[LinkPreviewLoading new]];
__weak ConversationInputToolbar *weakSelf = self; __weak ConversationInputToolbar *weakSelf = self;
[OWSLinkPreview tryToBuildPreviewInfoWithPreviewUrl:previewUrl [[OWSLinkPreview tryToBuildPreviewInfoObjcWithPreviewUrl:previewUrl]
callbackQueue:dispatch_get_main_queue() .then(^(OWSLinkPreviewDraft *linkPreviewDraft) {
completion:^(OWSLinkPreviewDraft *_Nullable linkPreviewDraft) { ConversationInputToolbar *_Nullable strongSelf = weakSelf;
ConversationInputToolbar *_Nullable strongSelf = weakSelf; if (!strongSelf) {
if (!strongSelf) { return;
return; }
} if (strongSelf.inputLinkPreview != inputLinkPreview) {
if (strongSelf.inputLinkPreview != inputLinkPreview) { // Obsolete callback.
// Obsolete callback. return;
return; }
} inputLinkPreview.linkPreviewDraft = linkPreviewDraft;
if (!linkPreviewDraft) { LinkPreviewDraft *viewState = [[LinkPreviewDraft alloc] initWithLinkPreviewDraft:linkPreviewDraft];
// The link preview could not be loaded. [strongSelf ensureLinkPreviewViewWithState:viewState];
[strongSelf clearLinkPreviewView]; })
return; .catch(^(id error) {
} // The link preview could not be loaded.
inputLinkPreview.linkPreviewDraft = linkPreviewDraft; [weakSelf clearLinkPreviewView];
LinkPreviewDraft *viewState = [[LinkPreviewDraft alloc] }) retainUntilComplete];
initWithLinkPreviewDraft:linkPreviewDraft];
[strongSelf ensureLinkPreviewViewWithState:viewState];
}];
} }
- (void)ensureLinkPreviewViewWithState:(id<LinkPreviewState>)state - (void)ensureLinkPreviewViewWithState:(id<LinkPreviewState>)state

@ -6,9 +6,9 @@
#import "OWSAvatarBuilder.h" #import "OWSAvatarBuilder.h"
#import "Signal-Swift.h" #import "Signal-Swift.h"
#import <SignalMessaging/OWSFormat.h> #import <SignalMessaging/OWSFormat.h>
#import <SignalMessaging/OWSMath.h>
#import <SignalMessaging/OWSUserProfile.h> #import <SignalMessaging/OWSUserProfile.h>
#import <SignalMessaging/SignalMessaging-Swift.h> #import <SignalMessaging/SignalMessaging-Swift.h>
#import <SignalServiceKit/OWSMath.h>
#import <SignalServiceKit/OWSMessageManager.h> #import <SignalServiceKit/OWSMessageManager.h>
#import <SignalServiceKit/TSContactThread.h> #import <SignalServiceKit/TSContactThread.h>
#import <SignalServiceKit/TSGroupThread.h> #import <SignalServiceKit/TSGroupThread.h>

@ -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 "OWSProgressView.h"
#import <SignalMessaging/OWSMath.h>
#import <SignalMessaging/UIView+OWS.h> #import <SignalMessaging/UIView+OWS.h>
#import <SignalServiceKit/OWSMath.h>
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN

@ -31,7 +31,6 @@ FOUNDATION_EXPORT const unsigned char SignalMessagingVersionString[];
#import <SignalMessaging/OWSDatabaseMigration.h> #import <SignalMessaging/OWSDatabaseMigration.h>
#import <SignalMessaging/OWSFormat.h> #import <SignalMessaging/OWSFormat.h>
#import <SignalMessaging/OWSGroupAvatarBuilder.h> #import <SignalMessaging/OWSGroupAvatarBuilder.h>
#import <SignalMessaging/OWSMath.h>
#import <SignalMessaging/OWSNavigationController.h> #import <SignalMessaging/OWSNavigationController.h>
#import <SignalMessaging/OWSPreferences.h> #import <SignalMessaging/OWSPreferences.h>
#import <SignalMessaging/OWSProfileManager.h> #import <SignalMessaging/OWSProfileManager.h>

@ -2,8 +2,8 @@
// Copyright (c) 2019 Open Whisper Systems. All rights reserved. // Copyright (c) 2019 Open Whisper Systems. All rights reserved.
// //
#import "OWSMath.h"
#import <PureLayout/PureLayout.h> #import <PureLayout/PureLayout.h>
#import <SignalServiceKit/OWSMath.h>
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN

@ -3,11 +3,16 @@
// //
import Foundation import Foundation
import PromiseKit
@objc @objc
public enum LinkPreviewError: Int, Error { public enum LinkPreviewError: Int, Error {
case invalidInput case invalidInput
case noPreview case noPreview
case assertionFailure
case couldNotDownload
case featureDisabled
case invalidContent
} }
// MARK: - OWSLinkPreviewDraft // MARK: - OWSLinkPreviewDraft
@ -424,70 +429,57 @@ public class OWSLinkPreview: MTLModel {
// This cache should only be accessed on serialQueue. // This cache should only be accessed on serialQueue.
private static var linkPreviewDraftCache: NSCache<AnyObject, OWSLinkPreviewDraft> = NSCache() private static var linkPreviewDraftCache: NSCache<AnyObject, OWSLinkPreviewDraft> = NSCache()
// Completion will always be invoked exactly once. private class func cachedLinkPreview(forPreviewUrl previewUrl: String) -> OWSLinkPreviewDraft? {
// var result: OWSLinkPreviewDraft?
// The completion is called with a link preview if one can be built for serialQueue.sync {
// the message body. It building the preview fails, completion will be result = linkPreviewDraftCache.object(forKey: previewUrl as AnyObject)
// 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)
}
} }
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<OWSLinkPreviewDraft> {
guard OWSLinkPreview.featureEnabled else { guard OWSLinkPreview.featureEnabled else {
completion(nil) return Promise(error: LinkPreviewError.featureDisabled)
return
} }
guard SSKPreferences.areLinkPreviewsEnabled() else { guard SSKPreferences.areLinkPreviewsEnabled() else {
completion(nil) return Promise(error: LinkPreviewError.featureDisabled)
return
} }
guard let previewUrl = previewUrl else { guard let previewUrl = previewUrl else {
completion(nil) return Promise(error: LinkPreviewError.invalidInput)
return
} }
serialQueue.async { if let cachedInfo = cachedLinkPreview(forPreviewUrl: previewUrl) {
if let cachedInfo = linkPreviewDraftCache.object(forKey: previewUrl as AnyObject) { Logger.verbose("Link preview info cache hit.")
Logger.verbose("Link preview info cache hit.") return Promise.value(cachedInfo)
completion(cachedInfo) }
return return downloadLink(url: previewUrl)
} .then(on: DispatchQueue.global()) { (data) -> Promise<OWSLinkPreviewDraft> in
downloadLink(url: previewUrl, completion: { (data) in return parse(linkData: data, linkUrlString: previewUrl)
DispatchQueue.global().async { .then(on: DispatchQueue.global()) { (linkPreviewDraft) -> Promise<OWSLinkPreviewDraft> in
guard let data = data else {
completion(nil)
return
}
parse(linkData: data, linkUrlString: previewUrl) { (linkPreviewDraft) in
guard let linkPreviewDraft = linkPreviewDraft else {
completion(nil)
return
}
guard linkPreviewDraft.isValid() else { guard linkPreviewDraft.isValid() else {
completion(nil) return Promise(error: LinkPreviewError.noPreview)
return
} }
serialQueue.async { setCachedLinkPreview(linkPreviewDraft, forPreviewUrl: previewUrl)
previewUrlCache.setObject(linkPreviewDraft, forKey: previewUrl as AnyObject)
completion(linkPreviewDraft) return Promise.value(linkPreviewDraft)
}
}
} }
})
} }
} }
private class func downloadLink(url: String, private class func downloadLink(url: String,
completion: @escaping (Data?) -> Void, remainingRetries: UInt = 3) -> Promise<Data> {
remainingRetries: UInt = 3) {
Logger.verbose("url: \(url)") Logger.verbose("url: \(url)")
@ -507,6 +499,7 @@ public class OWSLinkPreview: MTLModel {
sessionManager.requestSerializer.setValue(nil, forHTTPHeaderField: headerField) sessionManager.requestSerializer.setValue(nil, forHTTPHeaderField: headerField)
} }
let (promise, resolver) = Promise<Data>.pending()
sessionManager.get(url, sessionManager.get(url,
parameters: [String: AnyObject](), parameters: [String: AnyObject](),
progress: nil, progress: nil,
@ -514,64 +507,91 @@ public class OWSLinkPreview: MTLModel {
guard let data = value as? Data else { guard let data = value as? Data else {
Logger.warn("Result is not data: \(type(of: value)).") Logger.warn("Result is not data: \(type(of: value)).")
completion(nil) resolver.reject( LinkPreviewError.assertionFailure)
return return
} }
completion(data) resolver.fulfill(data)
}, },
failure: { _, error in failure: { _, error in
Logger.verbose("Error: \(error)") Logger.verbose("Error: \(error)")
guard isRetryable(error: error) else { guard isRetryable(error: error) else {
Logger.warn("Error is not retryable.") Logger.warn("Error is not retryable.")
completion(nil) resolver.reject( LinkPreviewError.couldNotDownload)
return return
} }
guard remainingRetries > 0 else { guard remainingRetries > 0 else {
Logger.warn("No more retries.") Logger.warn("No more retries.")
completion(nil) resolver.reject( LinkPreviewError.couldNotDownload)
return 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, private class func downloadImage(url urlString: String, imageMimeType: String) -> Promise<Data> {
completion: @escaping (Data?) -> Void) {
Logger.verbose("url: \(urlString)") Logger.verbose("url: \(urlString)")
guard let url = URL(string: urlString) else { guard let url = URL(string: urlString) else {
Logger.error("Could not parse URL.") Logger.error("Could not parse URL.")
return completion(nil) return Promise(error: LinkPreviewError.invalidInput)
} }
guard let assetDescription = ProxiedContentAssetDescription(url: url as NSURL) else { guard let assetDescription = ProxiedContentAssetDescription(url: url as NSURL) else {
Logger.error("Could not create asset description.") Logger.error("Could not create asset description.")
return completion(nil) return Promise(error: LinkPreviewError.invalidInput)
} }
let (promise, resolver) = Promise<ProxiedContentAsset>.pending()
DispatchQueue.main.async { DispatchQueue.main.async {
_ = ProxiedContentDownloader.defaultDownloader.requestAsset(assetDescription: assetDescription, _ = ProxiedContentDownloader.defaultDownloader.requestAsset(assetDescription: assetDescription,
priority: .high, priority: .high,
success: { (_, asset) in success: { (_, asset) in
DispatchQueue.global().async { resolver.fulfill(asset)
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)
}
}
}, failure: { (_) in }, failure: { (_) in
DispatchQueue.global().async { Logger.warn("Error downloading asset")
Logger.verbose("Error downloading asset") resolver.reject(LinkPreviewError.couldNotDownload)
})
}
return promise.then(on: DispatchQueue.global()) { (asset: ProxiedContentAsset) -> Promise<Data> 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)
}
completion(nil) 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)
}
} }
} }
@ -589,12 +609,10 @@ public class OWSLinkPreview: MTLModel {
// <meta property="og:title" content="Randomness is Random - Numberphile"> // <meta property="og:title" content="Randomness is Random - Numberphile">
// <meta property="og:image" content="https://i.ytimg.com/vi/tP-Ipsat90c/maxresdefault.jpg"> // <meta property="og:image" content="https://i.ytimg.com/vi/tP-Ipsat90c/maxresdefault.jpg">
private class func parse(linkData: Data, private class func parse(linkData: Data,
linkUrlString: String, linkUrlString: String) -> Promise<OWSLinkPreviewDraft> {
completion: @escaping (OWSLinkPreviewDraft?) -> Void) {
guard let linkText = String(bytes: linkData, encoding: .utf8) else { guard let linkText = String(bytes: linkData, encoding: .utf8) else {
owsFailDebug("Could not parse link text.") owsFailDebug("Could not parse link text.")
completion(nil) return Promise(error: LinkPreviewError.invalidInput)
return
} }
var title: String? var title: String?
@ -610,25 +628,66 @@ public class OWSLinkPreview: MTLModel {
Logger.verbose("title: \(String(describing: title))") Logger.verbose("title: \(String(describing: title))")
guard let rawImageUrlString = NSRegularExpression.parseFirstMatch(pattern: "<meta\\s+property\\s*=\\s*\"og:image\"\\s+content\\s*=\\s*\"(.*?)\"\\s*/?>", text: linkText) else { guard let rawImageUrlString = NSRegularExpression.parseFirstMatch(pattern: "<meta\\s+property\\s*=\\s*\"og:image\"\\s+content\\s*=\\s*\"(.*?)\"\\s*/?>", 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 { 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 { guard isValidMediaUrl(imageUrlString) else {
Logger.error("Invalid image URL.") 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 imageFileExtension = fileExtension(forImageUrl: imageUrlString) else {
guard let imageUrl = URL(string: 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<OWSLinkPreviewDraft> 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<OWSLinkPreviewDraft> 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.") Logger.error("Could not parse image URL.")
return completion(OWSLinkPreviewDraft(urlString: linkUrlString, title: title)) return nil
} }
let imageFilename = imageUrl.lastPathComponent let imageFilename = imageUrl.lastPathComponent
let imageFileExtension = (imageFilename as NSString).pathExtension.lowercased() 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 { guard let imageMimeType = MIMETypeUtil.mimeType(forFileExtension: imageFileExtension) else {
Logger.error("Image URL has unknown content type: \(imageFileExtension).") Logger.error("Image URL has unknown content type: \(imageFileExtension).")
return completion(OWSLinkPreviewDraft(urlString: linkUrlString, title: title)) return nil
} }
let kValidMimeTypes = [ let kValidMimeTypes = [
OWSMimeTypeImagePng, OWSMimeTypeImagePng,
@ -636,36 +695,9 @@ public class OWSLinkPreview: MTLModel {
] ]
guard kValidMimeTypes.contains(imageMimeType) else { guard kValidMimeTypes.contains(imageMimeType) else {
Logger.error("Image URL has invalid content type: \(imageMimeType).") Logger.error("Image URL has invalid content type: \(imageMimeType).")
return completion(OWSLinkPreviewDraft(urlString: linkUrlString, title: title)) return nil
} }
return imageMimeType
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)
})
} }
private class func decodeHTMLEntities(inString value: String) -> String? { private class func decodeHTMLEntities(inString value: String) -> String? {

@ -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 NS_ASSUME_NONNULL_BEGIN

@ -1,5 +1,5 @@
// //
// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // Copyright (c) 2019 Open Whisper Systems. All rights reserved.
// //
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
@ -14,7 +14,6 @@
#import <SignalMessaging/DebugLogger.h> #import <SignalMessaging/DebugLogger.h>
#import <SignalMessaging/Environment.h> #import <SignalMessaging/Environment.h>
#import <SignalMessaging/OWSContactsManager.h> #import <SignalMessaging/OWSContactsManager.h>
#import <SignalMessaging/OWSMath.h>
#import <SignalMessaging/OWSPreferences.h> #import <SignalMessaging/OWSPreferences.h>
#import <SignalMessaging/UIColor+OWS.h> #import <SignalMessaging/UIColor+OWS.h>
#import <SignalMessaging/UIFont+OWS.h> #import <SignalMessaging/UIFont+OWS.h>
@ -23,5 +22,6 @@
#import <SignalServiceKit/AppContext.h> #import <SignalServiceKit/AppContext.h>
#import <SignalServiceKit/AppReadiness.h> #import <SignalServiceKit/AppReadiness.h>
#import <SignalServiceKit/AppVersion.h> #import <SignalServiceKit/AppVersion.h>
#import <SignalServiceKit/OWSMath.h>
#import <SignalServiceKit/OWSMessageSender.h> #import <SignalServiceKit/OWSMessageSender.h>
#import <SignalServiceKit/TSAccountManager.h> #import <SignalServiceKit/TSAccountManager.h>

Loading…
Cancel
Save