Strip media metadata.

- removes non-orientation metadata from image and video attachments

- option to disable the feature

// FREEBIE
pull/1/head
Collin B. Stuart 7 years ago committed by Michael Kirk
parent f35fe4946a
commit 6f7b4a6e43

@ -67,6 +67,15 @@ NS_ASSUME_NONNULL_BEGIN
target:weakSelf target:weakSelf
selector:@selector(didToggleScreenSecuritySwitch:)]]; selector:@selector(didToggleScreenSecuritySwitch:)]];
[contents addSection:screenSecuritySection]; [contents addSection:screenSecuritySection];
OWSTableSection *removeMetadataSection = [OWSTableSection new];
removeMetadataSection.headerTitle = NSLocalizedString(@"SETTINGS_REMOVE_METADATA_TITLE", @"Remove metadata section header");
removeMetadataSection.footerTitle = NSLocalizedString(@"SETTINGS_REMOVE_METADATA_DETAIL", @"Remove metadata section footer");
[removeMetadataSection addItem:[OWSTableItem switchItemWithText:NSLocalizedString(@"SETTINGS_REMOVE_METADATA", @"Remove metadata table cell label")
isOn:[Environment.preferences isRemoveMetadataEnabled]
target:weakSelf
selector:@selector(didToggleRemoveMetadataSwitch:)]];
[contents addSection:removeMetadataSection];
// Allow calls to connect directly vs. using TURN exclusively // Allow calls to connect directly vs. using TURN exclusively
OWSTableSection *callingSection = [OWSTableSection new]; OWSTableSection *callingSection = [OWSTableSection new];
@ -192,6 +201,13 @@ NS_ASSUME_NONNULL_BEGIN
[Environment.preferences setScreenSecurity:enabled]; [Environment.preferences setScreenSecurity:enabled];
} }
- (void)didToggleRemoveMetadataSwitch:(UISwitch *)sender
{
BOOL enabled = sender.isOn;
DDLogInfo(@"%@ toggled remove metadata: %@", self.logTag, enabled ? @"ON" : @"OFF");
[Environment.preferences setIsRemoveMetadataEnabled:enabled];
}
- (void)didToggleReadReceiptsSwitch:(UISwitch *)sender - (void)didToggleReadReceiptsSwitch:(UISwitch *)sender
{ {
BOOL enabled = sender.isOn; BOOL enabled = sender.isOn;

@ -118,6 +118,9 @@
/* Attachment error message for video attachments which could not be converted to MP4 */ /* Attachment error message for video attachments which could not be converted to MP4 */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Unable to process video."; "ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Unable to process video.";
/* Attachment error message for image attachments in which metadata could not be removed */
"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "Unable to remove metadata from image.";
/* Attachment error message for image attachments which cannot be parsed */ /* Attachment error message for image attachments which cannot be parsed */
"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "Image attachment could not be parsed."; "ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "Image attachment could not be parsed.";
@ -1690,6 +1693,15 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"SETTINGS_SCREEN_SECURITY_DETAIL" = "Prevent Signal previews from appearing in the app switcher."; "SETTINGS_SCREEN_SECURITY_DETAIL" = "Prevent Signal previews from appearing in the app switcher.";
/* Remove metadata table view header label. */
"SETTINGS_REMOVE_METADATA_TITLE" = "Metadata";
/* Label for the remove metadata setting. */
"SETTINGS_REMOVE_METADATA" = "Remove Media Metadata";
/* Footer label explanation of the remove metadata setting. */
"SETTINGS_REMOVE_METADATA_DETAIL" = "Removes user-identifying metadata and GPS information when sending image and video messages.";
/* Settings table section footer. */ /* Settings table section footer. */
"SETTINGS_SECTION_CALL_KIT_DESCRIPTION" = "iOS Call Integration shows Signal calls on your lock screen and in the system's call history. You may optionally show your contact's name and number. If iCloud is enabled, this call history will be shared with Apple."; "SETTINGS_SECTION_CALL_KIT_DESCRIPTION" = "iOS Call Integration shows Signal calls on your lock screen and in the system's call history. You may optionally show your contact's name and number. If iCloud is enabled, this call history will be shared with Apple.";

@ -15,6 +15,7 @@ enum SignalAttachmentError: Error {
case couldNotParseImage case couldNotParseImage
case couldNotConvertToJpeg case couldNotConvertToJpeg
case couldNotConvertToMpeg4 case couldNotConvertToMpeg4
case couldNotRemoveMetadata
case invalidFileFormat case invalidFileFormat
} }
@ -53,6 +54,8 @@ extension SignalAttachmentError: LocalizedError {
return NSLocalizedString("ATTACHMENT_ERROR_INVALID_FILE_FORMAT", comment: "Attachment error message for attachments with an invalid file format") return NSLocalizedString("ATTACHMENT_ERROR_INVALID_FILE_FORMAT", comment: "Attachment error message for attachments with an invalid file format")
case .couldNotConvertToMpeg4: case .couldNotConvertToMpeg4:
return NSLocalizedString("ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4", comment: "Attachment error message for video attachments which could not be converted to MP4") return NSLocalizedString("ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4", comment: "Attachment error message for video attachments which could not be converted to MP4")
case .couldNotRemoveMetadata:
return NSLocalizedString("ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA", comment: "Attachment error message for image attachments in which metadata could not be removed")
} }
} }
} }
@ -625,8 +628,13 @@ public class SignalAttachment: NSObject {
} }
if isValidOutput { if isValidOutput {
Logger.verbose("\(TAG) Sending raw \(attachment.mimeType)") if Environment.preferences().isRemoveMetadataEnabled() {
return attachment Logger.verbose("\(TAG) Rewriting attachment with metadata removed \(attachment.mimeType)")
return removeImageMetadata(attachment : attachment)
} else {
Logger.verbose("\(TAG) Sending raw \(attachment.mimeType)")
return attachment
}
} else { } else {
Logger.verbose("\(TAG) Compressing attachment as image/jpeg, \(dataSource.dataLength()) bytes") Logger.verbose("\(TAG) Compressing attachment as image/jpeg, \(dataSource.dataLength()) bytes")
return compressImageAsJPEG(image: image, attachment: attachment, filename: dataSource.sourceFilename, imageQuality: imageQuality) return compressImageAsJPEG(image: image, attachment: attachment, filename: dataSource.sourceFilename, imageQuality: imageQuality)
@ -802,6 +810,59 @@ public class SignalAttachment: NSObject {
return 0.5 return 0.5
} }
} }
private class func removeImageMetadata(attachment: SignalAttachment) -> SignalAttachment {
guard let source = CGImageSourceCreateWithData(attachment.data as CFData, nil) else {
let attachment = SignalAttachment(dataSource : DataSourceValue.emptyDataSource(), dataUTI: attachment.dataUTI)
attachment.error = .missingData
return attachment
}
guard let type = CGImageSourceGetType(source) else {
let attachment = SignalAttachment(dataSource : DataSourceValue.emptyDataSource(), dataUTI: attachment.dataUTI)
attachment.error = .invalidFileFormat
return attachment
}
let count = CGImageSourceGetCount(source)
let mutableData = NSMutableData()
guard let destination = CGImageDestinationCreateWithData(mutableData as CFMutableData, type, count, nil) else {
attachment.error = .couldNotRemoveMetadata
return attachment
}
let removeMetadataProperties : [String : AnyObject] =
[
kCGImagePropertyExifDictionary as String : kCFNull,
kCGImagePropertyExifAuxDictionary as String : kCFNull,
kCGImagePropertyGPSDictionary as String : kCFNull,
kCGImagePropertyTIFFDictionary as String : kCFNull,
kCGImagePropertyJFIFDictionary as String : kCFNull,
kCGImagePropertyPNGDictionary as String : kCFNull,
kCGImagePropertyIPTCDictionary as String : kCFNull,
kCGImagePropertyMakerAppleDictionary as String : kCFNull
]
for index in 0...count-1 {
CGImageDestinationAddImageFromSource(destination, source, index, removeMetadataProperties as CFDictionary)
}
if CGImageDestinationFinalize(destination) {
guard let dataSource = DataSourceValue.dataSource(with:mutableData as Data, utiType:attachment.dataUTI) else {
attachment.error = .couldNotRemoveMetadata
return attachment
}
let strippedAttachment = SignalAttachment(dataSource : dataSource, dataUTI: attachment.dataUTI)
return strippedAttachment
} else {
Logger.verbose("\(TAG) CGImageDestinationFinalize failed")
attachment.error = .couldNotRemoveMetadata
return attachment
}
}
// MARK: Video Attachments // MARK: Video Attachments
@ -863,6 +924,9 @@ public class SignalAttachment: NSObject {
exportSession.shouldOptimizeForNetworkUse = true exportSession.shouldOptimizeForNetworkUse = true
exportSession.outputFileType = AVFileTypeMPEG4 exportSession.outputFileType = AVFileTypeMPEG4
if Environment.preferences().isRemoveMetadataEnabled() {
exportSession.metadataItemFilter = AVMetadataItemFilter.forSharing()
}
let exportURL = videoTempPath.appendingPathComponent(UUID().uuidString).appendingPathExtension("mp4") let exportURL = videoTempPath.appendingPathComponent(UUID().uuidString).appendingPathExtension("mp4")
exportSession.outputURL = exportURL exportSession.outputURL = exportURL

@ -44,6 +44,9 @@ extern NSString *const OWSPreferencesKeyEnableDebugLog;
- (BOOL)screenSecurityIsEnabled; - (BOOL)screenSecurityIsEnabled;
- (void)setScreenSecurity:(BOOL)flag; - (void)setScreenSecurity:(BOOL)flag;
- (BOOL)isRemoveMetadataEnabled;
- (void)setIsRemoveMetadataEnabled:(BOOL)enabled;
- (NotificationType)notificationPreviewType; - (NotificationType)notificationPreviewType;
- (void)setNotificationPreviewType:(NotificationType)type; - (void)setNotificationPreviewType:(NotificationType)type;
- (NSString *)nameForNotificationPreviewType:(NotificationType)notificationType; - (NSString *)nameForNotificationPreviewType:(NotificationType)notificationType;

@ -24,6 +24,7 @@ NSString *const OWSPreferencesKeyLastRecordedVoipToken = @"LastRecordedVoipToken
NSString *const OWSPreferencesKeyCallKitEnabled = @"CallKitEnabled"; NSString *const OWSPreferencesKeyCallKitEnabled = @"CallKitEnabled";
NSString *const OWSPreferencesKeyCallKitPrivacyEnabled = @"CallKitPrivacyEnabled"; NSString *const OWSPreferencesKeyCallKitPrivacyEnabled = @"CallKitPrivacyEnabled";
NSString *const OWSPreferencesKeyCallsHideIPAddress = @"CallsHideIPAddress"; NSString *const OWSPreferencesKeyCallsHideIPAddress = @"CallsHideIPAddress";
NSString *const OWSPreferencesKeyRemoveMetadata = @"Remove Metadata Key";
NSString *const OWSPreferencesKeyHasDeclinedNoContactsView = @"hasDeclinedNoContactsView"; NSString *const OWSPreferencesKeyHasDeclinedNoContactsView = @"hasDeclinedNoContactsView";
NSString *const OWSPreferencesKeyIOSUpgradeNagDate = @"iOSUpgradeNagDate"; NSString *const OWSPreferencesKeyIOSUpgradeNagDate = @"iOSUpgradeNagDate";
NSString *const OWSPreferencesKey_IsReadyForAppExtensions = @"isReadyForAppExtensions_5"; NSString *const OWSPreferencesKey_IsReadyForAppExtensions = @"isReadyForAppExtensions_5";
@ -116,6 +117,17 @@ NSString *const OWSPreferencesKeySystemCallLogEnabled = @"OWSPreferencesKeySyste
[self setValueForKey:OWSPreferencesKeyScreenSecurity toValue:@(flag)]; [self setValueForKey:OWSPreferencesKeyScreenSecurity toValue:@(flag)];
} }
- (BOOL)isRemoveMetadataEnabled
{
NSNumber *preference = [self tryGetValueForKey:OWSPreferencesKeyRemoveMetadata];
return preference ? [preference boolValue] : YES;
}
- (void)setIsRemoveMetadataEnabled:(BOOL)enabled
{
[self setValueForKey:OWSPreferencesKeyRemoveMetadata toValue:@(enabled)];
}
- (BOOL)getHasSentAMessage - (BOOL)getHasSentAMessage
{ {
NSNumber *preference = [self tryGetValueForKey:OWSPreferencesKeyHasSentAMessage]; NSNumber *preference = [self tryGetValueForKey:OWSPreferencesKeyHasSentAMessage];

Loading…
Cancel
Save