From 1607aa7f577d015b065d7185f72fed1cc3712fbf Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 19 Jun 2018 14:34:44 -0400 Subject: [PATCH] Image content types. --- .../Cells/OWSMessageBubbleView.m | 2 +- .../DebugUI/DebugUIMessagesAssetLoader.m | 4 +-- .../MediaDetailViewController.m | 2 +- .../ConversationViewItemTest.m | 4 +-- .../translations/en.lproj/Localizable.strings | 2 +- .../Messages/Attachments/TSAttachmentStream.h | 4 +++ .../Messages/Attachments/TSAttachmentStream.m | 27 ++++++++++++--- SignalServiceKit/src/Util/DataSource.m | 21 +++++++++++- SignalServiceKit/src/Util/MIMETypeUtil.h | 5 +++ SignalServiceKit/src/Util/MIMETypeUtil.m | 11 +++++-- SignalServiceKit/src/Util/NSData+Image.h | 6 +++- SignalServiceKit/src/Util/NSData+Image.m | 33 +++++++++++++++++-- 12 files changed, 102 insertions(+), 19 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m index 587598c35..017baa740 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m @@ -645,7 +645,7 @@ NS_ASSUME_NONNULL_BEGIN NSString *_Nullable filePath = [strongSelf.attachmentStream filePath]; YYImage *_Nullable animatedImage = nil; - if (filePath && [NSData ows_isValidImageAtPath:filePath]) { + if (strongSelf.attachmentStream.isValidImage && filePath) { animatedImage = [YYImage imageWithContentsOfFile:filePath]; } return animatedImage; diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIMessagesAssetLoader.m b/Signal/src/ViewControllers/DebugUI/DebugUIMessagesAssetLoader.m index 2d7240764..83fd9f8d5 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUIMessagesAssetLoader.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUIMessagesAssetLoader.m @@ -383,7 +383,7 @@ NS_ASSUME_NONNULL_BEGIN dispatch_once(&onceToken, ^{ instance = [DebugUIMessagesAssetLoader fakeAssetLoaderWithUrl:@"https://s3.amazonaws.com/ows-data/example_attachment_media/random-gif.gif" - mimeType:@"image/gif"]; + mimeType:OWSMimeTypeImageGif]; }); return instance; } @@ -395,7 +395,7 @@ NS_ASSUME_NONNULL_BEGIN dispatch_once(&onceToken, ^{ instance = [DebugUIMessagesAssetLoader fakeAssetLoaderWithUrl:@"https://i.giphy.com/media/LTw0F3GAdaao8/source.gif" - mimeType:@"image/gif"]; + mimeType:OWSMimeTypeImageGif]; }); return instance; } diff --git a/Signal/src/ViewControllers/MediaDetailViewController.m b/Signal/src/ViewControllers/MediaDetailViewController.m index 84227eb15..a196643e3 100644 --- a/Signal/src/ViewControllers/MediaDetailViewController.m +++ b/Signal/src/ViewControllers/MediaDetailViewController.m @@ -224,7 +224,7 @@ NS_ASSUME_NONNULL_BEGIN [scrollView autoPinToSuperviewEdges]; if (self.isAnimated) { - if ([self.fileData ows_isValidImage]) { + if (self.attachmentStream.isValidImage) { YYImage *animatedGif = [YYImage imageWithData:self.fileData]; YYAnimatedImageView *animatedView = [YYAnimatedImageView new]; animatedView.image = animatedGif; diff --git a/Signal/test/ViewControllers/ConversationViewItemTest.m b/Signal/test/ViewControllers/ConversationViewItemTest.m index 5b820f0a8..767babc29 100644 --- a/Signal/test/ViewControllers/ConversationViewItemTest.m +++ b/Signal/test/ViewControllers/ConversationViewItemTest.m @@ -76,12 +76,12 @@ - (ConversationViewItem *)stillImageViewItem { - return [self viewItemWithAttachmentMimetype:@"image/jpeg" filename:@"test-jpg.jpg"]; + return [self viewItemWithAttachmentMimetype:OWSMimeTypeImageJpeg filename:@"test-jpg.jpg"]; } - (ConversationViewItem *)animatedImageViewItem { - return [self viewItemWithAttachmentMimetype:@"image/gif" filename:@"test-gif.gif"]; + return [self viewItemWithAttachmentMimetype:OWSMimeTypeImageGif filename:@"test-gif.gif"]; } - (ConversationViewItem *)videoViewItem diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 89aafcf4c..707b768cf 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -2270,7 +2270,7 @@ "VERIFICATION_STATE_CHANGE_GENERIC" = "Verification state changed."; /* Label for button or row which allows users to verify the safety number of another user. */ -"VERIFY_PRIVACY" = "Show Safety Number"; +"VERIFY_PRIVACY" = "View Safety Number"; /* Label for button or row which allows users to verify the safety numbers of multiple users. */ "VERIFY_PRIVACY_MULTIPLE" = "Review Safety Numbers"; diff --git a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.h b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.h index e1bf40fd6..2d2d43c8b 100644 --- a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.h +++ b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.h @@ -74,6 +74,10 @@ NS_ASSUME_NONNULL_BEGIN // Non-nil for attachments which need "lazy backup restore." - (nullable OWSBackupFragment *)lazyRestoreFragment; +#pragma mark - Image Validation + +- (BOOL)isValidImage; + #pragma mark - Update With... Methods // Marks attachment as needing "lazy backup restore." diff --git a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m index 178b6c2b4..a42666d52 100644 --- a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m +++ b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m @@ -312,6 +312,25 @@ NS_ASSUME_NONNULL_BEGIN return [MIMETypeUtil isAudio:self.contentType]; } +#pragma mark - Image Validation + +- (BOOL)isValidImageWithData:(NSData *)data +{ + OWSAssert(self.isImage || self.isAnimated); + OWSAssert(data); + + return [data ows_isValidImageWithMimeType:self.contentType]; +} + +- (BOOL)isValidImage +{ + OWSAssert(self.isImage || self.isAnimated); + + return [NSData ows_isValidImageAtPath:self.filePath mimeType:self.contentType]; +} + +#pragma mark - + - (nullable UIImage *)image { if ([self isVideo]) { @@ -322,7 +341,7 @@ NS_ASSUME_NONNULL_BEGIN return nil; } NSData *data = [NSData dataWithContentsOfURL:mediaUrl]; - if (![data ows_isValidImage]) { + if (![self isValidImageWithData:data]) { return nil; } return [UIImage imageWithData:data]; @@ -348,7 +367,7 @@ NS_ASSUME_NONNULL_BEGIN } NSData *data = [NSData dataWithContentsOfURL:mediaUrl]; - if (![data ows_isValidImage]) { + if (![self isValidImageWithData:data]) { return nil; } @@ -422,7 +441,7 @@ NS_ASSUME_NONNULL_BEGIN UIImage *_Nullable result; if (self.isImage || self.isAnimated) { - if (![NSData ows_isValidImageAtPath:self.filePath]) { + if (![self isValidImage]) { DDLogWarn(@"%@ skipping thumbnail generation for invalid image at path: %@", self.logTag, self.filePath); return; } @@ -516,7 +535,7 @@ NS_ASSUME_NONNULL_BEGIN if (!mediaUrl) { return CGSizeZero; } - if (![NSData ows_isValidImageAtPath:mediaUrl.path]) { + if (![self isValidImage]) { return CGSizeZero; } diff --git a/SignalServiceKit/src/Util/DataSource.m b/SignalServiceKit/src/Util/DataSource.m index 4de4f63e0..cca0a78ee 100755 --- a/SignalServiceKit/src/Util/DataSource.m +++ b/SignalServiceKit/src/Util/DataSource.m @@ -72,7 +72,7 @@ NS_ASSUME_NONNULL_BEGIN // if ows_isValidImage is given a file path, it will // avoid loading most of the data into memory, which // is considerably more performant, so try to do that. - return [NSData ows_isValidImageAtPath:dataPath]; + return [NSData ows_isValidImageAtPath:dataPath mimeType:self.mimeType]; } NSData *data = [self data]; return [data ows_isValidImage]; @@ -83,6 +83,14 @@ NS_ASSUME_NONNULL_BEGIN _sourceFilename = sourceFilename.filterFilename; } +// Returns the MIME type, if known. +- (nullable NSString *)mimeType +{ + OWS_ABSTRACT_METHOD(); + + return nil; +} + @end #pragma mark - @@ -222,6 +230,11 @@ NS_ASSUME_NONNULL_BEGIN } } +- (nullable NSString *)mimeType +{ + return (self.fileExtension ? [MIMETypeUtil mimeTypeForFileExtension:self.fileExtension] : nil); +} + @end #pragma mark - @@ -372,6 +385,12 @@ NS_ASSUME_NONNULL_BEGIN } } +- (nullable NSString *)mimeType +{ + NSString *_Nullable fileExtension = self.filePath.pathExtension; + return (fileExtension ? [MIMETypeUtil mimeTypeForFileExtension:fileExtension] : nil); +} + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/MIMETypeUtil.h b/SignalServiceKit/src/Util/MIMETypeUtil.h index 3757adf59..33b11b6d8 100644 --- a/SignalServiceKit/src/Util/MIMETypeUtil.h +++ b/SignalServiceKit/src/Util/MIMETypeUtil.h @@ -8,6 +8,11 @@ extern NSString *const OWSMimeTypeApplicationOctetStream; extern NSString *const OWSMimeTypeApplicationZip; extern NSString *const OWSMimeTypeImagePng; extern NSString *const OWSMimeTypeImageJpeg; +extern NSString *const OWSMimeTypeImageGif; +extern NSString *const OWSMimeTypeImageTiff1; +extern NSString *const OWSMimeTypeImageTiff2; +extern NSString *const OWSMimeTypeImageBmp1; +extern NSString *const OWSMimeTypeImageBmp2; extern NSString *const OWSMimeTypeOversizeTextMessage; extern NSString *const OWSMimeTypeUnknownForTests; diff --git a/SignalServiceKit/src/Util/MIMETypeUtil.m b/SignalServiceKit/src/Util/MIMETypeUtil.m index e107e2137..1ceca652d 100644 --- a/SignalServiceKit/src/Util/MIMETypeUtil.m +++ b/SignalServiceKit/src/Util/MIMETypeUtil.m @@ -18,6 +18,11 @@ NS_ASSUME_NONNULL_BEGIN NSString *const OWSMimeTypeApplicationOctetStream = @"application/octet-stream"; NSString *const OWSMimeTypeImagePng = @"image/png"; NSString *const OWSMimeTypeImageJpeg = @"image/jpeg"; +NSString *const OWSMimeTypeImageGif = @"image/gif"; +NSString *const OWSMimeTypeImageTiff1 = @"image/tiff"; +NSString *const OWSMimeTypeImageTiff2 = @"image/x-tiff"; +NSString *const OWSMimeTypeImageBmp1 = @"image/bmp"; +NSString *const OWSMimeTypeImageBmp2 = @"image/x-windows-bmp"; NSString *const OWSMimeTypeOversizeTextMessage = @"text/x-signal-plain"; NSString *const OWSMimeTypeUnknownForTests = @"unknown/mimetype"; NSString *const OWSMimeTypeApplicationZip = @"application/zip"; @@ -76,7 +81,7 @@ NSString *const kSyncMessageFileExtension = @"bin"; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ result = @{ - @"image/jpeg" : @"jpeg", + OWSMimeTypeImageJpeg : @"jpeg", @"image/pjpeg" : @"jpeg", OWSMimeTypeImagePng : @"png", @"image/tiff" : @"tif", @@ -93,7 +98,7 @@ NSString *const kSyncMessageFileExtension = @"bin"; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ result = @{ - @"image/gif" : @"gif", + OWSMimeTypeImageGif : @"gif", }; }); return result; @@ -183,7 +188,7 @@ NSString *const kSyncMessageFileExtension = @"bin"; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ result = @{ - @"gif" : @"image/gif", + @"gif" : OWSMimeTypeImageGif, }; }); return result; diff --git a/SignalServiceKit/src/Util/NSData+Image.h b/SignalServiceKit/src/Util/NSData+Image.h index 5397379cc..5c86b2bcc 100644 --- a/SignalServiceKit/src/Util/NSData+Image.h +++ b/SignalServiceKit/src/Util/NSData+Image.h @@ -1,10 +1,14 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // @interface NSData (Image) +// If mimeType is non-nil, we ensure that the magic numbers agree with the +// mimeType. + (BOOL)ows_isValidImageAtPath:(NSString *)filePath; ++ (BOOL)ows_isValidImageAtPath:(NSString *)filePath mimeType:(nullable NSString *)mimeType; - (BOOL)ows_isValidImage; +- (BOOL)ows_isValidImageWithMimeType:(nullable NSString *)mimeType; @end diff --git a/SignalServiceKit/src/Util/NSData+Image.m b/SignalServiceKit/src/Util/NSData+Image.m index 0e9af4427..7aa873da2 100644 --- a/SignalServiceKit/src/Util/NSData+Image.m +++ b/SignalServiceKit/src/Util/NSData+Image.m @@ -1,7 +1,8 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // +#import "MIMETypeUtil.h" #import "NSData+Image.h" typedef NS_ENUM(NSInteger, ImageFormat) { @@ -16,6 +17,16 @@ typedef NS_ENUM(NSInteger, ImageFormat) { @implementation NSData (Image) + (BOOL)ows_isValidImageAtPath:(NSString *)filePath +{ + return [self ows_isValidImageAtPath:filePath mimeType:nil]; +} + +- (BOOL)ows_isValidImage +{ + return [self ows_isValidImageWithMimeType:nil]; +} + ++ (BOOL)ows_isValidImageAtPath:(NSString *)filePath mimeType:(nullable NSString *)mimeType { NSError *error = nil; NSData *data = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedIfSafe error:&error]; @@ -23,16 +34,32 @@ typedef NS_ENUM(NSInteger, ImageFormat) { DDLogError(@"%@ could not read image data: %@", self.logTag, error); } - return [data ows_isValidImage]; + return [data ows_isValidImageWithMimeType:mimeType]; } -- (BOOL)ows_isValidImage +- (BOOL)ows_isValidImageWithMimeType:(nullable NSString *)mimeType { // Don't trust the file extension; iOS (e.g. UIKit, Core Graphics) will happily // load a .gif with a .png file extension. // // Instead, use the "magic numbers" in the file data to determine the image format. ImageFormat imageFormat = [self ows_guessImageFormat]; + switch (imageFormat) { + case ImageFormat_Unknown: + return NO; + case ImageFormat_Png: + return (mimeType == nil || [mimeType isEqualToString:OWSMimeTypeImagePng]); + case ImageFormat_Gif: + return (mimeType == nil || [mimeType isEqualToString:OWSMimeTypeImageGif]); + case ImageFormat_Tiff: + return (mimeType == nil || [mimeType isEqualToString:OWSMimeTypeImageTiff1] || + [mimeType isEqualToString:OWSMimeTypeImageTiff2]); + case ImageFormat_Jpeg: + return (mimeType == nil || [mimeType isEqualToString:OWSMimeTypeImageJpeg]); + case ImageFormat_Bmp: + return (mimeType == nil || [mimeType isEqualToString:OWSMimeTypeImageBmp1] || + [mimeType isEqualToString:OWSMimeTypeImageBmp2]); + } if (imageFormat == ImageFormat_Gif) { return [self ows_hasValidGifSize]; } else if (imageFormat == ImageFormat_Unknown) {