diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj index a992819b..b74c35ed 100644 --- 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 = ""; }; E04A37C52B544F090029650D /* URIParsing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URIParsing.swift; sourceTree = ""; }; E0E024102B7C19C20075735D /* TranslationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslationTests.swift; sourceTree = ""; }; + E06336A82B7582E000A88E6B /* img_with_location.jpeg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = img_with_location.jpeg; sourceTree = ""; }; + E06336A92B75832100A88E6B /* ImageMetadataTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageMetadataTest.swift; sourceTree = ""; }; E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSettingsView.swift; sourceTree = ""; }; E990020E2955F837003BBC5A /* EditMetadataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMetadataView.swift; sourceTree = ""; }; E9E4ED0A295867B900DD7078 /* ThreadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadView.swift; sourceTree = ""; }; @@ -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 = ""; @@ -2695,6 +2701,14 @@ path = DamusNotificationService; sourceTree = ""; }; + E06336A72B7582D600A88E6B /* Assets */ = { + isa = PBXGroup; + children = ( + E06336A82B7582E000A88E6B /* img_with_location.jpeg */, + ); + path = Assets; + sourceTree = ""; + }; 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 index d5defe8b..d85b9f44 100644 --- 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 new file mode 100644 index 00000000..6aedb502 Binary files /dev/null and b/damusTests/Assets/img_with_location.jpeg differ diff --git a/damusTests/ImageMetadataTest.swift b/damusTests/ImageMetadataTest.swift new file mode 100644 index 00000000..c9ec1509 --- /dev/null +++ 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 + } + } +}