commit 8d815fe4d6542960b85d21872a0cf554b074b1b9
parent 58326f679e3fb5b19c0bce667bdc7ea75066813a
Author: kernelkind <kernelkind@gmail.com>
Date: Mon, 19 Feb 2024 15:53:54 -0500
privacy: add function to strip location data from photos
Add a function to strip GPS data from images before uploading to
hosting service.
Lightning-address: kernelkind@getalby.com
Signed-off-by: kernelkind <kernelkind@gmail.com>
Signed-off-by: William Casarin <jb55@jb55.com>
Diffstat:
4 files changed, 87 insertions(+), 0 deletions(-)
diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj
@@ -628,6 +628,8 @@
E02B54182B4DFADA0077FF42 /* Bech32ObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E02B54172B4DFADA0077FF42 /* Bech32ObjectTests.swift */; };
E04A37C62B544F090029650D /* URIParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = E04A37C52B544F090029650D /* URIParsing.swift */; };
E0E024112B7C19C20075735D /* TranslationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0E024102B7C19C20075735D /* TranslationTests.swift */; };
+ E06336AA2B75832100A88E6B /* ImageMetadataTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E06336A92B75832100A88E6B /* ImageMetadataTest.swift */; };
+ E06336AB2B75850100A88E6B /* img_with_location.jpeg in Resources */ = {isa = PBXBuildFile; fileRef = E06336A82B7582E000A88E6B /* img_with_location.jpeg */; };
E4FA1C032A24BB7F00482697 /* SearchSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */; };
E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; };
E9E4ED0B295867B900DD7078 /* ThreadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E4ED0A295867B900DD7078 /* ThreadView.swift */; };
@@ -1404,6 +1406,8 @@
E02B54172B4DFADA0077FF42 /* Bech32ObjectTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bech32ObjectTests.swift; sourceTree = "<group>"; };
E04A37C52B544F090029650D /* URIParsing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URIParsing.swift; sourceTree = "<group>"; };
E0E024102B7C19C20075735D /* TranslationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslationTests.swift; sourceTree = "<group>"; };
+ E06336A82B7582E000A88E6B /* img_with_location.jpeg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = img_with_location.jpeg; sourceTree = "<group>"; };
+ E06336A92B75832100A88E6B /* ImageMetadataTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageMetadataTest.swift; sourceTree = "<group>"; };
E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSettingsView.swift; sourceTree = "<group>"; };
E990020E2955F837003BBC5A /* EditMetadataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMetadataView.swift; sourceTree = "<group>"; };
E9E4ED0A295867B900DD7078 /* ThreadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadView.swift; sourceTree = "<group>"; };
@@ -2471,6 +2475,7 @@
4CE6DEF627F7A08200C66700 /* damusTests */ = {
isa = PBXGroup;
children = (
+ E06336A72B7582D600A88E6B /* Assets */,
D72A2D032AD9C165002AFF62 /* Mocking */,
4C9B0DEC2A65A74000CBDA21 /* Util */,
4C0C03962A61E2670098B3B8 /* Fixtures */,
@@ -2507,6 +2512,7 @@
D7315A2B2ACDF4DA0036E30A /* DamusCacheManagerTests.swift */,
B501062C2B363036003874F5 /* AuthIntegrationTests.swift */,
E0E024102B7C19C20075735D /* TranslationTests.swift */,
+ E06336A92B75832100A88E6B /* ImageMetadataTest.swift */,
);
path = damusTests;
sourceTree = "<group>";
@@ -2695,6 +2701,14 @@
path = DamusNotificationService;
sourceTree = "<group>";
};
+ E06336A72B7582D600A88E6B /* Assets */ = {
+ isa = PBXGroup;
+ children = (
+ E06336A82B7582E000A88E6B /* img_with_location.jpeg */,
+ );
+ path = Assets;
+ sourceTree = "<group>";
+ };
F71694E82A66221E001F4053 /* Onboarding */ = {
isa = PBXGroup;
children = (
@@ -2918,6 +2932,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ E06336AB2B75850100A88E6B /* img_with_location.jpeg in Resources */,
4C0C039A2A61E27B0098B3B8 /* bool_setting.wasm in Resources */,
4C0C03992A61E27B0098B3B8 /* primal.wasm in Resources */,
);
@@ -3452,6 +3467,7 @@
B501062D2B363036003874F5 /* AuthIntegrationTests.swift in Sources */,
4CB883AE2976FA9300DC99E7 /* FormatTests.swift in Sources */,
D72A2D052AD9C1B5002AFF62 /* MockDamusState.swift in Sources */,
+ E06336AA2B75832100A88E6B /* ImageMetadataTest.swift in Sources */,
4C363AA02828A8DD006E126D /* LikeTests.swift in Sources */,
4C90BD1C283AC38E008EE7EF /* Bech32Tests.swift in Sources */,
50A50A8D29A09E1C00C01BE7 /* RequestTests.swift in Sources */,
diff --git a/damus/Util/Images/ImageMetadata.swift b/damus/Util/Images/ImageMetadata.swift
@@ -211,3 +211,31 @@ func process_image_metadatas(cache: EventCache, ev: NostrEvent) {
}
}
+func removeGPSDataFromImage(fromImageURL imageURL: URL) -> Bool {
+ guard let source = CGImageSourceCreateWithURL(imageURL as CFURL, nil) else {
+ print("Failed to create image source.")
+ return false
+ }
+ let data = NSMutableData()
+ guard let type = CGImageSourceGetType(source),
+ let destination = CGImageDestinationCreateWithData(data, type, 1, nil) else {
+ print("Failed to create image destination.")
+ return false
+ }
+
+ let removeGPSProperties: CFDictionary = [kCGImagePropertyGPSDictionary as String: kCFNull] as CFDictionary
+
+ CGImageDestinationAddImageFromSource(destination, source, 0, removeGPSProperties)
+ if CGImageDestinationFinalize(destination) {
+ do {
+ try data.write(to: imageURL, options: .atomic)
+ return true
+ } catch {
+ print("Failed to write image data: \(error)")
+ return false
+ }
+ } else {
+ print("Failed to finalize image destination.")
+ return false
+ }
+}
diff --git a/damusTests/Assets/img_with_location.jpeg b/damusTests/Assets/img_with_location.jpeg
Binary files differ.
diff --git a/damusTests/ImageMetadataTest.swift b/damusTests/ImageMetadataTest.swift
@@ -0,0 +1,43 @@
+//
+// LocationStrippingTest.swift
+// damusTests
+//
+// Created by KernelKind on 2/8/24.
+//
+
+import XCTest
+@testable import damus
+
+final class ImageMetadataTest : XCTestCase {
+ func testRemoveGPSData() {
+ let bundle = Bundle(for: type(of: self))
+ guard let imageURL = bundle.url(forResource: "img_with_location", withExtension: "jpeg"),
+ let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
+ else {
+ XCTFail("Failed to load test image from bundle")
+ return
+ }
+
+ let testOutputURL = documentsDirectory.appendingPathComponent("img_with_location.jpeg")
+ do {
+ if FileManager.default.fileExists(atPath: testOutputURL.path) {
+ try FileManager.default.removeItem(at: testOutputURL)
+ }
+ try FileManager.default.copyItem(at: imageURL, to: testOutputURL)
+ } catch {
+ XCTFail("Setup failed: Unable to copy test image to documents directory - \(error)")
+ return
+ }
+
+ let removalSuccess = removeGPSDataFromImage(fromImageURL: testOutputURL)
+
+ XCTAssertTrue(removalSuccess, "GPS data removal was not successful")
+
+ guard let sourceAfterRemoval = CGImageSourceCreateWithURL(testOutputURL as CFURL, nil),
+ let imagePropertiesAfterRemoval = CGImageSourceCopyPropertiesAtIndex(sourceAfterRemoval, 0, nil) as? [String: Any],
+ imagePropertiesAfterRemoval[kCGImagePropertyGPSDictionary as String] == nil else {
+ XCTFail("GPS data was not removed from the image")
+ return
+ }
+ }
+}