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>
This commit is contained in:
kernelkind 2024-02-19 15:53:54 -05:00 committed by William Casarin
parent 58326f679e
commit 8d815fe4d6
4 changed files with 87 additions and 0 deletions

View File

@ -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 */,

View File

@ -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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

View File

@ -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
}
}
}