Skip to content

Commit a621133

Browse files
committed
Strip media metadata.
- removes non-orientation metadata from image and video attachments - option to disable the feature // FREEBIE
1 parent 87fa553 commit a621133

File tree

5 files changed

+109
-2
lines changed

5 files changed

+109
-2
lines changed

Signal/src/ViewControllers/PrivacySettingsTableViewController.m

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,15 @@ - (void)updateTableContents
6565
target:weakSelf
6666
selector:@selector(didToggleScreenSecuritySwitch:)]];
6767
[contents addSection:screenSecuritySection];
68+
69+
OWSTableSection *removeMetadataSection = [OWSTableSection new];
70+
removeMetadataSection.headerTitle = NSLocalizedString(@"SETTINGS_REMOVE_METADATA_TITLE", @"Remove metadata section header");
71+
removeMetadataSection.footerTitle = NSLocalizedString(@"SETTINGS_REMOVE_METADATA_DETAIL", @"Remove metadata section footer");
72+
[removeMetadataSection addItem:[OWSTableItem switchItemWithText:NSLocalizedString(@"SETTINGS_REMOVE_METADATA", @"Remove metadata table cell label")
73+
isOn:[Environment.preferences isRemoveMetadataEnabled]
74+
target:weakSelf
75+
selector:@selector(didToggleRemoveMetadataSwitch:)]];
76+
[contents addSection:removeMetadataSection];
6877

6978
// Allow calls to connect directly vs. using TURN exclusively
7079
OWSTableSection *callingSection = [OWSTableSection new];
@@ -171,6 +180,13 @@ - (void)didToggleScreenSecuritySwitch:(UISwitch *)sender
171180
[Environment.preferences setScreenSecurity:enabled];
172181
}
173182

183+
- (void)didToggleRemoveMetadataSwitch:(UISwitch *)sender
184+
{
185+
BOOL enabled = sender.isOn;
186+
DDLogInfo(@"%@ toggled remove metadata: %@", self.logTag, enabled ? @"ON" : @"OFF");
187+
[Environment.preferences setIsRemoveMetadataEnabled:enabled];
188+
}
189+
174190
- (void)didToggleReadReceiptsSwitch:(UISwitch *)sender
175191
{
176192
BOOL enabled = sender.isOn;

Signal/translations/en.lproj/Localizable.strings

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@
115115
/* Attachment error message for video attachments which could not be converted to MP4 */
116116
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Unable to process video.";
117117

118+
/* Attachment error message for image attachments in which metadata could not be removed */
119+
"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "Unable to remove metadata from image.";
120+
118121
/* Attachment error message for image attachments which cannot be parsed */
119122
"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "Image attachment could not be parsed.";
120123

@@ -1608,6 +1611,15 @@
16081611
/* No comment provided by engineer. */
16091612
"SETTINGS_SCREEN_SECURITY_DETAIL" = "Prevent Signal previews from appearing in the app switcher.";
16101613

1614+
/* Remove metadata table view header label. */
1615+
"SETTINGS_REMOVE_METADATA_TITLE" = "Metadata";
1616+
1617+
/* Label for the remove metadata setting. */
1618+
"SETTINGS_REMOVE_METADATA" = "Remove Media Metadata";
1619+
1620+
/* Footer label explanation of the remove metadata setting. */
1621+
"SETTINGS_REMOVE_METADATA_DETAIL" = "Removes user-identifying metadata and GPS information when sending image and video messages.";
1622+
16111623
/* Settings table section footer. */
16121624
"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.";
16131625

SignalMessaging/attachments/SignalAttachment.swift

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ enum SignalAttachmentError: Error {
1515
case couldNotParseImage
1616
case couldNotConvertToJpeg
1717
case couldNotConvertToMpeg4
18+
case couldNotRemoveMetadata
1819
case invalidFileFormat
1920
}
2021

@@ -53,6 +54,8 @@ extension SignalAttachmentError: LocalizedError {
5354
return NSLocalizedString("ATTACHMENT_ERROR_INVALID_FILE_FORMAT", comment: "Attachment error message for attachments with an invalid file format")
5455
case .couldNotConvertToMpeg4:
5556
return NSLocalizedString("ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4", comment: "Attachment error message for video attachments which could not be converted to MP4")
57+
case .couldNotRemoveMetadata:
58+
return NSLocalizedString("ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA", comment: "Attachment error message for image attachments in which metadata could not be removed")
5659
}
5760
}
5861
}
@@ -625,8 +628,13 @@ public class SignalAttachment: NSObject {
625628
}
626629

627630
if isValidOutput {
628-
Logger.verbose("\(TAG) Sending raw \(attachment.mimeType)")
629-
return attachment
631+
if Environment.preferences().isRemoveMetadataEnabled() {
632+
Logger.verbose("\(TAG) Rewriting attachment with metadata removed \(attachment.mimeType)")
633+
return removeImageMetadata(attachment : attachment)
634+
} else {
635+
Logger.verbose("\(TAG) Sending raw \(attachment.mimeType)")
636+
return attachment
637+
}
630638
} else {
631639
Logger.verbose("\(TAG) Compressing attachment as image/jpeg, \(dataSource.dataLength()) bytes")
632640
return compressImageAsJPEG(image: image, attachment: attachment, filename: dataSource.sourceFilename, imageQuality: imageQuality)
@@ -802,6 +810,59 @@ public class SignalAttachment: NSObject {
802810
return 0.5
803811
}
804812
}
813+
814+
private class func removeImageMetadata(attachment: SignalAttachment) -> SignalAttachment {
815+
816+
guard let source = CGImageSourceCreateWithData(attachment.data as CFData, nil) else {
817+
let attachment = SignalAttachment(dataSource : DataSourceValue.emptyDataSource(), dataUTI: attachment.dataUTI)
818+
attachment.error = .missingData
819+
return attachment
820+
}
821+
822+
guard let type = CGImageSourceGetType(source) else {
823+
let attachment = SignalAttachment(dataSource : DataSourceValue.emptyDataSource(), dataUTI: attachment.dataUTI)
824+
attachment.error = .invalidFileFormat
825+
return attachment
826+
}
827+
828+
let count = CGImageSourceGetCount(source)
829+
let mutableData = NSMutableData()
830+
guard let destination = CGImageDestinationCreateWithData(mutableData as CFMutableData, type, count, nil) else {
831+
attachment.error = .couldNotRemoveMetadata
832+
return attachment
833+
}
834+
835+
let removeMetadataProperties : [String : AnyObject] =
836+
[
837+
kCGImagePropertyExifDictionary as String : kCFNull,
838+
kCGImagePropertyExifAuxDictionary as String : kCFNull,
839+
kCGImagePropertyGPSDictionary as String : kCFNull,
840+
kCGImagePropertyTIFFDictionary as String : kCFNull,
841+
kCGImagePropertyJFIFDictionary as String : kCFNull,
842+
kCGImagePropertyPNGDictionary as String : kCFNull,
843+
kCGImagePropertyIPTCDictionary as String : kCFNull,
844+
kCGImagePropertyMakerAppleDictionary as String : kCFNull
845+
]
846+
847+
for index in 0...count-1 {
848+
CGImageDestinationAddImageFromSource(destination, source, index, removeMetadataProperties as CFDictionary)
849+
}
850+
851+
if CGImageDestinationFinalize(destination) {
852+
guard let dataSource = DataSourceValue.dataSource(with:mutableData as Data, utiType:attachment.dataUTI) else {
853+
attachment.error = .couldNotRemoveMetadata
854+
return attachment
855+
}
856+
857+
let strippedAttachment = SignalAttachment(dataSource : dataSource, dataUTI: attachment.dataUTI)
858+
return strippedAttachment
859+
860+
} else {
861+
Logger.verbose("\(TAG) CGImageDestinationFinalize failed")
862+
attachment.error = .couldNotRemoveMetadata
863+
return attachment
864+
}
865+
}
805866

806867
// MARK: Video Attachments
807868

@@ -863,6 +924,9 @@ public class SignalAttachment: NSObject {
863924

864925
exportSession.shouldOptimizeForNetworkUse = true
865926
exportSession.outputFileType = AVFileTypeMPEG4
927+
if Environment.preferences().isRemoveMetadataEnabled() {
928+
exportSession.metadataItemFilter = AVMetadataItemFilter.forSharing()
929+
}
866930

867931
let exportURL = videoTempPath.appendingPathComponent(UUID().uuidString).appendingPathExtension("mp4")
868932
exportSession.outputURL = exportURL

SignalMessaging/utils/OWSPreferences.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ extern NSString *const OWSPreferencesKeyEnableDebugLog;
4242
- (BOOL)screenSecurityIsEnabled;
4343
- (void)setScreenSecurity:(BOOL)flag;
4444

45+
- (BOOL)isRemoveMetadataEnabled;
46+
- (void)setIsRemoveMetadataEnabled:(BOOL)enabled;
47+
4548
- (NotificationType)notificationPreviewType;
4649
- (void)setNotificationPreviewType:(NotificationType)type;
4750
- (NSString *)nameForNotificationPreviewType:(NotificationType)notificationType;

SignalMessaging/utils/OWSPreferences.m

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
NSString *const OWSPreferencesKeyCallKitEnabled = @"CallKitEnabled";
2424
NSString *const OWSPreferencesKeyCallKitPrivacyEnabled = @"CallKitPrivacyEnabled";
2525
NSString *const OWSPreferencesKeyCallsHideIPAddress = @"CallsHideIPAddress";
26+
NSString *const OWSPreferencesKeyRemoveMetadata = @"Remove Metadata Key";
2627
NSString *const OWSPreferencesKeyHasDeclinedNoContactsView = @"hasDeclinedNoContactsView";
2728
NSString *const OWSPreferencesKeyIOSUpgradeNagDate = @"iOSUpgradeNagDate";
2829
NSString *const OWSPreferencesKey_IsReadyForAppExtensions = @"isReadyForAppExtensions_5";
@@ -96,6 +97,17 @@ - (void)setScreenSecurity:(BOOL)flag
9697
[self setValueForKey:OWSPreferencesKeyScreenSecurity toValue:@(flag)];
9798
}
9899

100+
- (BOOL)isRemoveMetadataEnabled
101+
{
102+
NSNumber *preference = [self tryGetValueForKey:OWSPreferencesKeyRemoveMetadata];
103+
return preference ? [preference boolValue] : YES;
104+
}
105+
106+
- (void)setIsRemoveMetadataEnabled:(BOOL)enabled
107+
{
108+
[self setValueForKey:OWSPreferencesKeyRemoveMetadata toValue:@(enabled)];
109+
}
110+
99111
- (BOOL)getHasSentAMessage
100112
{
101113
NSNumber *preference = [self tryGetValueForKey:OWSPreferencesKeyHasSentAMessage];

0 commit comments

Comments
 (0)