damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

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:
Mdamus.xcodeproj/project.pbxproj | 16++++++++++++++++
Mdamus/Util/Images/ImageMetadata.swift | 28++++++++++++++++++++++++++++
AdamusTests/Assets/img_with_location.jpeg | 0
AdamusTests/ImageMetadataTest.swift | 43+++++++++++++++++++++++++++++++++++++++++++
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 + } + } +}