Refine image validation.
This commit is contained in:
parent
5b04a421b0
commit
34a05cdb85
|
@ -180,16 +180,11 @@ public class SignalAttachment: NSObject {
|
|||
|
||||
// MARK: Constants
|
||||
|
||||
/**
|
||||
* Media Size constraints from Signal-Android
|
||||
*
|
||||
* https://github.com/signalapp/Signal-Android/blob/master/src/org/thoughtcrime/securesms/mms/PushMediaConstraints.java
|
||||
*/
|
||||
static let kMaxFileSizeAnimatedImage = UInt(25 * 1024 * 1024)
|
||||
static let kMaxFileSizeImage = UInt(6 * 1024 * 1024)
|
||||
static let kMaxFileSizeVideo = UInt(100 * 1024 * 1024)
|
||||
static let kMaxFileSizeAudio = UInt(100 * 1024 * 1024)
|
||||
static let kMaxFileSizeGeneric = UInt(100 * 1024 * 1024)
|
||||
static let kMaxFileSizeAnimatedImage = OWSMediaUtils.kMaxFileSizeAnimatedImage
|
||||
static let kMaxFileSizeImage = OWSMediaUtils.kMaxFileSizeImage
|
||||
static let kMaxFileSizeVideo = OWSMediaUtils.kMaxFileSizeVideo
|
||||
static let kMaxFileSizeAudio = OWSMediaUtils.kMaxFileSizeAudio
|
||||
static let kMaxFileSizeGeneric = OWSMediaUtils.kMaxFileSizeGeneric
|
||||
|
||||
// MARK: Constructor
|
||||
|
||||
|
|
|
@ -16,13 +16,14 @@ public enum OWSMediaError: Error {
|
|||
}
|
||||
|
||||
@objc public class func thumbnail(forImageAtPath path: String, maxDimension: CGFloat) throws -> UIImage {
|
||||
Logger.verbose("thumbnailing image: \(path)")
|
||||
|
||||
guard FileManager.default.fileExists(atPath: path) else {
|
||||
throw OWSMediaError.failure(description: "Media file missing.")
|
||||
}
|
||||
guard NSData.ows_isValidImage(atPath: path) else {
|
||||
throw OWSMediaError.failure(description: "Invalid image.")
|
||||
}
|
||||
|
||||
guard let originalImage = UIImage(contentsOfFile: path) else {
|
||||
throw OWSMediaError.failure(description: "Could not load original image.")
|
||||
}
|
||||
|
@ -33,11 +34,13 @@ public enum OWSMediaError: Error {
|
|||
}
|
||||
|
||||
@objc public class func thumbnail(forVideoAtPath path: String, maxDimension: CGFloat) throws -> UIImage {
|
||||
let maxSize = CGSize(width: maxDimension, height: maxDimension)
|
||||
Logger.verbose("thumbnailing video: \(path)")
|
||||
|
||||
guard FileManager.default.fileExists(atPath: path) else {
|
||||
throw OWSMediaError.failure(description: "Media file missing.")
|
||||
guard isVideoOfValidContentTypeAndSize(path: path) else {
|
||||
throw OWSMediaError.failure(description: "Media file has missing or invalid length.")
|
||||
}
|
||||
|
||||
let maxSize = CGSize(width: maxDimension, height: maxDimension)
|
||||
let url = URL(fileURLWithPath: path)
|
||||
let asset = AVURLAsset(url: url, options: nil)
|
||||
guard isValidVideo(asset: asset) else {
|
||||
|
@ -54,13 +57,36 @@ public enum OWSMediaError: Error {
|
|||
}
|
||||
|
||||
@objc public class func isValidVideo(path: String) -> Bool {
|
||||
guard isVideoOfValidContentTypeAndSize(path: path) else {
|
||||
Logger.error("Media file has missing or invalid length.")
|
||||
return false
|
||||
}
|
||||
|
||||
let url = URL(fileURLWithPath: path)
|
||||
let asset = AVURLAsset(url: url, options: nil)
|
||||
return isValidVideo(asset: asset)
|
||||
}
|
||||
|
||||
private class func isVideoOfValidContentTypeAndSize(path: String) -> Bool {
|
||||
guard FileManager.default.fileExists(atPath: path) else {
|
||||
Logger.error("Media file missing.")
|
||||
return false
|
||||
}
|
||||
let url = URL(fileURLWithPath: path)
|
||||
let asset = AVURLAsset(url: url, options: nil)
|
||||
return isValidVideo(asset: asset)
|
||||
let fileExtension = URL(fileURLWithPath: path).pathExtension
|
||||
guard let contentType = MIMETypeUtil.mimeType(forFileExtension: fileExtension) else {
|
||||
Logger.error("Media file has unknown content type.")
|
||||
return false
|
||||
}
|
||||
guard MIMETypeUtil.isSupportedVideoMIMEType(contentType) else {
|
||||
Logger.error("Media file has invalid content type.")
|
||||
return false
|
||||
}
|
||||
|
||||
guard let fileSize = OWSFileSystem.fileSize(ofPath: path) else {
|
||||
Logger.error("Media file has unknown length.")
|
||||
return false
|
||||
}
|
||||
return fileSize.uintValue <= kMaxFileSizeVideo
|
||||
}
|
||||
|
||||
private class func isValidVideo(asset: AVURLAsset) -> Bool {
|
||||
|
@ -74,11 +100,35 @@ public enum OWSMediaError: Error {
|
|||
Logger.error("Invalid video size: \(maxTrackSize)")
|
||||
return false
|
||||
}
|
||||
let kMaxValidSize: CGFloat = 3 * 1024.0
|
||||
if maxTrackSize.width > kMaxValidSize || maxTrackSize.height > kMaxValidSize {
|
||||
if maxTrackSize.width > kMaxVideoDimensions || maxTrackSize.height > kMaxVideoDimensions {
|
||||
Logger.error("Invalid video dimensions: \(maxTrackSize)")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// MARK: Constants
|
||||
|
||||
/**
|
||||
* Media Size constraints from Signal-Android
|
||||
*
|
||||
* https://github.com/signalapp/Signal-Android/blob/master/src/org/thoughtcrime/securesms/mms/PushMediaConstraints.java
|
||||
*/
|
||||
@objc
|
||||
public static let kMaxFileSizeAnimatedImage = UInt(25 * 1024 * 1024)
|
||||
@objc
|
||||
public static let kMaxFileSizeImage = UInt(6 * 1024 * 1024)
|
||||
@objc
|
||||
public static let kMaxFileSizeVideo = UInt(100 * 1024 * 1024)
|
||||
@objc
|
||||
public static let kMaxFileSizeAudio = UInt(100 * 1024 * 1024)
|
||||
@objc
|
||||
public static let kMaxFileSizeGeneric = UInt(100 * 1024 * 1024)
|
||||
|
||||
@objc
|
||||
public static let kMaxVideoDimensions: CGFloat = 3 * 1024
|
||||
@objc
|
||||
public static let kMaxAnimatedImageDimensions: UInt = 1 * 1024
|
||||
@objc
|
||||
public static let kMaxStillImageDimensions: UInt = 8 * 1024
|
||||
}
|
||||
|
|
|
@ -2,9 +2,11 @@
|
|||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSData+Image.h"
|
||||
#import "MIMETypeUtil.h"
|
||||
#import "NSData+Image.h"
|
||||
#import "OWSFileSystem.h"
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <SignalServiceKit/SignalServiceKit-Swift.h>
|
||||
|
||||
typedef NS_ENUM(NSInteger, ImageFormat) {
|
||||
ImageFormat_Unknown,
|
||||
|
@ -24,11 +26,23 @@ typedef NS_ENUM(NSInteger, ImageFormat) {
|
|||
|
||||
- (BOOL)ows_isValidImage
|
||||
{
|
||||
if (![self ows_isValidImageWithMimeType:nil]) {
|
||||
ImageFormat imageFormat = [self ows_guessImageFormat];
|
||||
|
||||
BOOL isAnimated = imageFormat == ImageFormat_Gif;
|
||||
|
||||
const NSUInteger kMaxFileSize
|
||||
= (isAnimated ? OWSMediaUtils.kMaxFileSizeAnimatedImage : OWSMediaUtils.kMaxFileSizeImage);
|
||||
NSUInteger fileSize = self.length;
|
||||
if (fileSize > kMaxFileSize) {
|
||||
DDLogWarn(@"Oversize image.");
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (![self ows_hasValidImageDimensions]) {
|
||||
if (![self ows_isValidImageWithMimeType:nil imageFormat:imageFormat]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (![self ows_hasValidImageDimensions:isAnimated]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
@ -37,6 +51,37 @@ typedef NS_ENUM(NSInteger, ImageFormat) {
|
|||
|
||||
+ (BOOL)ows_isValidImageAtPath:(NSString *)filePath mimeType:(nullable NSString *)mimeType
|
||||
{
|
||||
if (mimeType.length < 1) {
|
||||
NSString *fileExtension = [filePath pathExtension].lowercaseString;
|
||||
mimeType = [MIMETypeUtil mimeTypeForFileExtension:fileExtension];
|
||||
}
|
||||
if (mimeType.length < 1) {
|
||||
DDLogError(@"Image has unknown MIME type.");
|
||||
return NO;
|
||||
}
|
||||
NSNumber *_Nullable fileSize = [OWSFileSystem fileSizeOfPath:filePath];
|
||||
if (!fileSize) {
|
||||
DDLogError(@"Could not determine file size.");
|
||||
return NO;
|
||||
}
|
||||
|
||||
BOOL isAnimated = NO;
|
||||
if ([MIMETypeUtil isSupportedAnimatedMIMEType:mimeType]) {
|
||||
isAnimated = YES;
|
||||
if (fileSize.unsignedIntegerValue > OWSMediaUtils.kMaxFileSizeAnimatedImage) {
|
||||
DDLogWarn(@"Oversize animated image.");
|
||||
return NO;
|
||||
}
|
||||
} else if ([MIMETypeUtil isSupportedImageMIMEType:mimeType]) {
|
||||
if (fileSize.unsignedIntegerValue > OWSMediaUtils.kMaxFileSizeImage) {
|
||||
DDLogWarn(@"Oversize still image.");
|
||||
return NO;
|
||||
}
|
||||
} else {
|
||||
DDLogError(@"Image has unsupported MIME type.");
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSError *error = nil;
|
||||
NSData *_Nullable data = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedIfSafe error:&error];
|
||||
if (!data || error) {
|
||||
|
@ -48,7 +93,7 @@ typedef NS_ENUM(NSInteger, ImageFormat) {
|
|||
return NO;
|
||||
}
|
||||
|
||||
if (![self ows_hasValidImageDimensionsAtPath:filePath]) {
|
||||
if (![self ows_hasValidImageDimensionsAtPath:filePath isAnimated:isAnimated]) {
|
||||
DDLogError(@"%@ image had invalid dimensions.", self.logTag);
|
||||
return NO;
|
||||
}
|
||||
|
@ -56,18 +101,18 @@ typedef NS_ENUM(NSInteger, ImageFormat) {
|
|||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)ows_hasValidImageDimensions
|
||||
- (BOOL)ows_hasValidImageDimensions:(BOOL)isAnimated
|
||||
{
|
||||
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)self, NULL);
|
||||
if (imageSource == NULL) {
|
||||
return NO;
|
||||
}
|
||||
BOOL result = [NSData ows_hasValidImageDimensionWithImageSource:imageSource];
|
||||
BOOL result = [NSData ows_hasValidImageDimensionWithImageSource:imageSource isAnimated:isAnimated];
|
||||
CFRelease(imageSource);
|
||||
return result;
|
||||
}
|
||||
|
||||
+ (BOOL)ows_hasValidImageDimensionsAtPath:(NSString *)path
|
||||
+ (BOOL)ows_hasValidImageDimensionsAtPath:(NSString *)path isAnimated:(BOOL)isAnimated
|
||||
{
|
||||
NSURL *url = [NSURL fileURLWithPath:path];
|
||||
if (!url) {
|
||||
|
@ -78,12 +123,12 @@ typedef NS_ENUM(NSInteger, ImageFormat) {
|
|||
if (imageSource == NULL) {
|
||||
return NO;
|
||||
}
|
||||
BOOL result = [self ows_hasValidImageDimensionWithImageSource:imageSource];
|
||||
BOOL result = [self ows_hasValidImageDimensionWithImageSource:imageSource isAnimated:isAnimated];
|
||||
CFRelease(imageSource);
|
||||
return result;
|
||||
}
|
||||
|
||||
+ (BOOL)ows_hasValidImageDimensionWithImageSource:(CGImageSourceRef)imageSource
|
||||
+ (BOOL)ows_hasValidImageDimensionWithImageSource:(CGImageSourceRef)imageSource isAnimated:(BOOL)isAnimated
|
||||
{
|
||||
OWSAssert(imageSource);
|
||||
|
||||
|
@ -116,6 +161,7 @@ typedef NS_ENUM(NSInteger, ImageFormat) {
|
|||
return NO;
|
||||
}
|
||||
NSUInteger depthBits = depthNumber.unsignedIntegerValue;
|
||||
// This should usually be 1.
|
||||
CGFloat depthBytes = (CGFloat)ceil(depthBits / 8.f);
|
||||
|
||||
/* The color model of the image such as "RGB", "CMYK", "Gray", or "Lab".
|
||||
|
@ -136,7 +182,8 @@ typedef NS_ENUM(NSInteger, ImageFormat) {
|
|||
CGFloat bytesPerPixel = kWorseCastComponentsPerPixel * depthBytes;
|
||||
|
||||
const CGFloat kExpectedBytePerPixel = 4;
|
||||
const CGFloat kMaxValidImageDimension = 8 * 1024;
|
||||
CGFloat kMaxValidImageDimension
|
||||
= (isAnimated ? OWSMediaUtils.kMaxAnimatedImageDimensions : OWSMediaUtils.kMaxStillImageDimensions);
|
||||
CGFloat kMaxBytes = kMaxValidImageDimension * kMaxValidImageDimension * kExpectedBytePerPixel;
|
||||
CGFloat actualBytes = width * height * bytesPerPixel;
|
||||
if (actualBytes > kMaxBytes) {
|
||||
|
@ -148,6 +195,12 @@ typedef NS_ENUM(NSInteger, ImageFormat) {
|
|||
}
|
||||
|
||||
- (BOOL)ows_isValidImageWithMimeType:(nullable NSString *)mimeType
|
||||
{
|
||||
ImageFormat imageFormat = [self ows_guessImageFormat];
|
||||
return [self ows_isValidImageWithMimeType:mimeType imageFormat:imageFormat];
|
||||
}
|
||||
|
||||
- (BOOL)ows_isValidImageWithMimeType:(nullable NSString *)mimeType imageFormat:(ImageFormat)imageFormat
|
||||
{
|
||||
// Don't trust the file extension; iOS (e.g. UIKit, Core Graphics) will happily
|
||||
// load a .gif with a .png file extension.
|
||||
|
@ -156,7 +209,6 @@ typedef NS_ENUM(NSInteger, ImageFormat) {
|
|||
//
|
||||
// If the image has a declared MIME type, ensure that agrees with the
|
||||
// deduced image format.
|
||||
ImageFormat imageFormat = [self ows_guessImageFormat];
|
||||
switch (imageFormat) {
|
||||
case ImageFormat_Unknown:
|
||||
return NO;
|
||||
|
|
|
@ -63,7 +63,11 @@
|
|||
}
|
||||
|
||||
UIGraphicsBeginImageContext(CGSizeMake(thumbnailSize.width, thumbnailSize.height));
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
CGContextRef _Nullable context = UIGraphicsGetCurrentContext();
|
||||
if (context == NULL) {
|
||||
DDLogError(@"Couldn't create context.");
|
||||
return nil;
|
||||
}
|
||||
CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
|
||||
[self drawInRect:CGRectMake(0, 0, thumbnailSize.width, thumbnailSize.height)];
|
||||
UIImage *_Nullable resized = UIGraphicsGetImageFromCurrentImageContext();
|
||||
|
|
Loading…
Reference in New Issue