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
selector:@selector(didToggleScreenSecuritySwitch:)]];
[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
OWSTableSection *callingSection = [OWSTableSection new];
@ -192,6 +201,13 @@ NS_ASSUME_NONNULL_BEGIN
[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
{
BOOL enabled = sender.isOn;

@ -118,6 +118,9 @@
/* 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 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_COULD_NOT_PARSE_IMAGE" = "Image attachment could not be parsed.";
@ -1690,6 +1693,15 @@
/* No comment provided by engineer. */
"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_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 couldNotConvertToJpeg
case couldNotConvertToMpeg4
case couldNotRemoveMetadata
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")
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")
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 {
Logger.verbose("\(TAG) Sending raw \(attachment.mimeType)")
return attachment
if Environment.preferences().isRemoveMetadataEnabled() {
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 {
Logger.verbose("\(TAG) Compressing attachment as image/jpeg, \(dataSource.dataLength()) bytes")
return compressImageAsJPEG(image: image, attachment: attachment, filename: dataSource.sourceFilename, imageQuality: imageQuality)
@ -802,6 +810,59 @@ public class SignalAttachment: NSObject {
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
@ -863,6 +924,9 @@ public class SignalAttachment: NSObject {
exportSession.shouldOptimizeForNetworkUse = true
exportSession.outputFileType = AVFileTypeMPEG4
if Environment.preferences().isRemoveMetadataEnabled() {
exportSession.metadataItemFilter = AVMetadataItemFilter.forSharing()
}
let exportURL = videoTempPath.appendingPathComponent(UUID().uuidString).appendingPathExtension("mp4")
exportSession.outputURL = exportURL

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

@ -24,6 +24,7 @@ NSString *const OWSPreferencesKeyLastRecordedVoipToken = @"LastRecordedVoipToken
NSString *const OWSPreferencesKeyCallKitEnabled = @"CallKitEnabled";
NSString *const OWSPreferencesKeyCallKitPrivacyEnabled = @"CallKitPrivacyEnabled";
NSString *const OWSPreferencesKeyCallsHideIPAddress = @"CallsHideIPAddress";
NSString *const OWSPreferencesKeyRemoveMetadata = @"Remove Metadata Key";
NSString *const OWSPreferencesKeyHasDeclinedNoContactsView = @"hasDeclinedNoContactsView";
NSString *const OWSPreferencesKeyIOSUpgradeNagDate = @"iOSUpgradeNagDate";
NSString *const OWSPreferencesKey_IsReadyForAppExtensions = @"isReadyForAppExtensions_5";
@ -116,6 +117,17 @@ NSString *const OWSPreferencesKeySystemCallLogEnabled = @"OWSPreferencesKeySyste
[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
{
NSNumber *preference = [self tryGetValueForKey:OWSPreferencesKeyHasSentAMessage];

Loading…
Cancel
Save