Skip to content

Commit

Permalink
Allow choosing downloads location in App Store builds (#2532)
Browse files Browse the repository at this point in the history
  • Loading branch information
mallexxx authored Apr 10, 2024
1 parent b73b0b3 commit c3b0c7b
Show file tree
Hide file tree
Showing 19 changed files with 714 additions and 265 deletions.
8 changes: 4 additions & 4 deletions Configuration/App/DuckDuckGo.xcconfig
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*] =
PROVISIONING_PROFILE_SPECIFIER[config=Release][sdk=macosx*] = MacOS Browser
PROVISIONING_PROFILE_SPECIFIER[config=Review][sdk=macosx*] = MacOS Browser Product Review

GCC_PREPROCESSOR_DEFINITIONS[arch=*][sdk=*] = NETP_SYSTEM_EXTENSION=1
GCC_PREPROCESSOR_DEFINITIONS[config=CI][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION=1 DEBUG=1 CI=1 $(inherited)
GCC_PREPROCESSOR_DEFINITIONS[config=Debug][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION=1 DEBUG=1 $(inherited)
GCC_PREPROCESSOR_DEFINITIONS[config=Review][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION=1 REVIEW=1 $(inherited)
GCC_PREPROCESSOR_DEFINITIONS[arch=*][sdk=*] = NETP_SYSTEM_EXTENSION=1 SWIFT_OBJC_INTERFACE_HEADER_NAME=$(SWIFT_OBJC_INTERFACE_HEADER_NAME)
GCC_PREPROCESSOR_DEFINITIONS[config=CI][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION=1 DEBUG=1 CI=1 SWIFT_OBJC_INTERFACE_HEADER_NAME=$(SWIFT_OBJC_INTERFACE_HEADER_NAME) $(inherited)
GCC_PREPROCESSOR_DEFINITIONS[config=Debug][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION=1 DEBUG=1 SWIFT_OBJC_INTERFACE_HEADER_NAME=$(SWIFT_OBJC_INTERFACE_HEADER_NAME) $(inherited)
GCC_PREPROCESSOR_DEFINITIONS[config=Review][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION=1 REVIEW=1 SWIFT_OBJC_INTERFACE_HEADER_NAME=$(SWIFT_OBJC_INTERFACE_HEADER_NAME) $(inherited)

SWIFT_ACTIVE_COMPILATION_CONDITIONS[arch=*][sdk=*] = NETP_SYSTEM_EXTENSION $(FEATURE_FLAGS)
SWIFT_ACTIVE_COMPILATION_CONDITIONS[config=CI][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION DEBUG CI $(FEATURE_FLAGS)
Expand Down
8 changes: 4 additions & 4 deletions Configuration/AppStore.xcconfig
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ MAIN_BUNDLE_IDENTIFIER[config=Debug][sdk=*] = $(MAIN_BUNDLE_IDENTIFIER_PREFIX).d
MAIN_BUNDLE_IDENTIFIER[config=CI][sdk=*] = $(MAIN_BUNDLE_IDENTIFIER_PREFIX).debug
MAIN_BUNDLE_IDENTIFIER[config=Review][sdk=*] = $(MAIN_BUNDLE_IDENTIFIER_PREFIX).review

GCC_PREPROCESSOR_DEFINITIONS[arch=*][sdk=*] = APPSTORE=1
GCC_PREPROCESSOR_DEFINITIONS[config=CI][arch=*][sdk=*] = APPSTORE=1 DEBUG=1 CI=1 $(inherited)
GCC_PREPROCESSOR_DEFINITIONS[config=Debug][arch=*][sdk=*] = APPSTORE=1 DEBUG=1 $(inherited)
GCC_PREPROCESSOR_DEFINITIONS[config=Review][arch=*][sdk=*] = APPSTORE=1 REVIEW=1 $(inherited)
GCC_PREPROCESSOR_DEFINITIONS[arch=*][sdk=*] = APPSTORE=1 SWIFT_OBJC_INTERFACE_HEADER_NAME=$(SWIFT_OBJC_INTERFACE_HEADER_NAME)
GCC_PREPROCESSOR_DEFINITIONS[config=CI][arch=*][sdk=*] = APPSTORE=1 DEBUG=1 CI=1 SWIFT_OBJC_INTERFACE_HEADER_NAME=$(SWIFT_OBJC_INTERFACE_HEADER_NAME) $(inherited)
GCC_PREPROCESSOR_DEFINITIONS[config=Debug][arch=*][sdk=*] = APPSTORE=1 DEBUG=1 SWIFT_OBJC_INTERFACE_HEADER_NAME=$(SWIFT_OBJC_INTERFACE_HEADER_NAME) $(inherited)
GCC_PREPROCESSOR_DEFINITIONS[config=Review][arch=*][sdk=*] = APPSTORE=1 REVIEW=1 SWIFT_OBJC_INTERFACE_HEADER_NAME=$(SWIFT_OBJC_INTERFACE_HEADER_NAME) $(inherited)

MACOSX_DEPLOYMENT_TARGET = 12.3

Expand Down
22 changes: 22 additions & 0 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3063,6 +3063,12 @@
B6ABC5962B4861D4008343B9 /* FocusableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6ABC5952B4861D4008343B9 /* FocusableTextField.swift */; };
B6ABC5972B4861D4008343B9 /* FocusableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6ABC5952B4861D4008343B9 /* FocusableTextField.swift */; };
B6ABC5982B4861D4008343B9 /* FocusableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6ABC5952B4861D4008343B9 /* FocusableTextField.swift */; };
B6ABD0CA2BC03F610000EB69 /* SecurityScopedFileURLController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6ABD0C92BC03F610000EB69 /* SecurityScopedFileURLController.swift */; };
B6ABD0CB2BC03F610000EB69 /* SecurityScopedFileURLController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6ABD0C92BC03F610000EB69 /* SecurityScopedFileURLController.swift */; };
B6ABD0CC2BC03F610000EB69 /* SecurityScopedFileURLController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6ABD0C92BC03F610000EB69 /* SecurityScopedFileURLController.swift */; };
B6ABD0CE2BC042CE0000EB69 /* NSURL+sandboxExtensionRetainCount.m in Sources */ = {isa = PBXBuildFile; fileRef = B6ABD0CD2BC042CE0000EB69 /* NSURL+sandboxExtensionRetainCount.m */; };
B6ABD0CF2BC042CE0000EB69 /* NSURL+sandboxExtensionRetainCount.m in Sources */ = {isa = PBXBuildFile; fileRef = B6ABD0CD2BC042CE0000EB69 /* NSURL+sandboxExtensionRetainCount.m */; };
B6ABD0D02BC042CE0000EB69 /* NSURL+sandboxExtensionRetainCount.m in Sources */ = {isa = PBXBuildFile; fileRef = B6ABD0CD2BC042CE0000EB69 /* NSURL+sandboxExtensionRetainCount.m */; };
B6AE39F129373AF200C37AA4 /* EmptyAttributionRulesProver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AE39F029373AF200C37AA4 /* EmptyAttributionRulesProver.swift */; };
B6AE39F329374AEC00C37AA4 /* OHHTTPStubs in Frameworks */ = {isa = PBXBuildFile; productRef = B6AE39F229374AEC00C37AA4 /* OHHTTPStubs */; };
B6AE39F529374AEC00C37AA4 /* OHHTTPStubsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = B6AE39F429374AEC00C37AA4 /* OHHTTPStubsSwift */; };
Expand Down Expand Up @@ -3203,6 +3209,9 @@
B6EC37FC29B83E99001ACE79 /* TestsURLExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EC37FB29B83E99001ACE79 /* TestsURLExtension.swift */; };
B6EC37FD29B83E99001ACE79 /* TestsURLExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EC37FB29B83E99001ACE79 /* TestsURLExtension.swift */; };
B6EC37FF29B8D915001ACE79 /* Configuration in Frameworks */ = {isa = PBXBuildFile; productRef = B6EC37FE29B8D915001ACE79 /* Configuration */; };
B6EECB302BC3FA5A00B3CB77 /* SecurityScopedFileURLController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6ABD0C92BC03F610000EB69 /* SecurityScopedFileURLController.swift */; };
B6EECB312BC3FAB100B3CB77 /* NSURL+sandboxExtensionRetainCount.m in Sources */ = {isa = PBXBuildFile; fileRef = B6ABD0CD2BC042CE0000EB69 /* NSURL+sandboxExtensionRetainCount.m */; };
B6EECB322BC40A1400B3CB77 /* FileManagerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E61EE2263AC0C8004E11AB /* FileManagerExtension.swift */; };
B6EEDD7D2B8C69E900637EBC /* TabContentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EEDD7C2B8C69E900637EBC /* TabContentTests.swift */; };
B6EEDD7E2B8C69E900637EBC /* TabContentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EEDD7C2B8C69E900637EBC /* TabContentTests.swift */; };
B6F1C80B2761C45400334924 /* LocalUnprotectedDomains.swift in Sources */ = {isa = PBXBuildFile; fileRef = 336B39E22726B4B700C417D3 /* LocalUnprotectedDomains.swift */; };
Expand Down Expand Up @@ -4645,6 +4654,8 @@
B6AAAC2C260330580029438D /* PublishedAfter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishedAfter.swift; sourceTree = "<group>"; };
B6AAAC3D26048F690029438D /* RandomAccessCollectionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomAccessCollectionExtension.swift; sourceTree = "<group>"; };
B6ABC5952B4861D4008343B9 /* FocusableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusableTextField.swift; sourceTree = "<group>"; };
B6ABD0C92BC03F610000EB69 /* SecurityScopedFileURLController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityScopedFileURLController.swift; sourceTree = "<group>"; };
B6ABD0CD2BC042CE0000EB69 /* NSURL+sandboxExtensionRetainCount.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSURL+sandboxExtensionRetainCount.m"; sourceTree = "<group>"; };
B6AE39F029373AF200C37AA4 /* EmptyAttributionRulesProver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyAttributionRulesProver.swift; sourceTree = "<group>"; };
B6AE74332609AFCE005B9B1A /* ProgressEstimationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressEstimationTests.swift; sourceTree = "<group>"; };
B6B040072B95C4C80085279D /* Downloads 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Downloads 2.xcdatamodel"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -6690,6 +6701,8 @@
B6C0B23826E742610031CB7F /* FileDownloadError.swift */,
856C98DE257014BD00A22F1F /* FileDownloadManager.swift */,
B6E6B9E22BA1F5F1008AA7E1 /* FilePresenter.swift */,
B6ABD0C92BC03F610000EB69 /* SecurityScopedFileURLController.swift */,
B6ABD0CD2BC042CE0000EB69 /* NSURL+sandboxExtensionRetainCount.m */,
B6A924D82664C72D001A28CA /* WebKitDownloadTask.swift */,
B6CC266B2BAD9CD800F53F8D /* FileProgressPresenter.swift */,
);
Expand Down Expand Up @@ -10727,6 +10740,7 @@
7B430EA22A71411A00BAC4A1 /* NetworkProtectionSimulateFailureMenu.swift in Sources */,
3706FBD5293F65D500E42796 /* TabCollection+NSSecureCoding.swift in Sources */,
3706FBD6293F65D500E42796 /* Instruments.swift in Sources */,
B6ABD0CF2BC042CE0000EB69 /* NSURL+sandboxExtensionRetainCount.m in Sources */,
B62B483F2ADE48DE000DECE5 /* ArrayBuilder.swift in Sources */,
569277C229DDCBB500B633EF /* HomePageContinueSetUpModel.swift in Sources */,
3706FBD7293F65D500E42796 /* ContentBlockerRulesLists.swift in Sources */,
Expand Down Expand Up @@ -10872,6 +10886,7 @@
3706FC32293F65D500E42796 /* MoreOptionsMenu.swift in Sources */,
3706FC34293F65D500E42796 /* PermissionAuthorizationViewController.swift in Sources */,
3706FC35293F65D500E42796 /* BookmarkNode.swift in Sources */,
B6ABD0CB2BC03F610000EB69 /* SecurityScopedFileURLController.swift in Sources */,
31EF1E822B63FFC200E6DB17 /* DataBrokerProtectionLoginItemScheduler.swift in Sources */,
B6B140892ABDBCC1004F8E85 /* HoverTrackingArea.swift in Sources */,
3706FC36293F65D500E42796 /* LongPressButton.swift in Sources */,
Expand Down Expand Up @@ -12027,6 +12042,7 @@
4B2F565D2B38F93E001214C0 /* NetworkProtectionSubscriptionEventHandler.swift in Sources */,
4B957B082AC7AE700062CA31 /* PixelDataStore.swift in Sources */,
4B957B092AC7AE700062CA31 /* WaitlistStorage.swift in Sources */,
B6ABD0D02BC042CE0000EB69 /* NSURL+sandboxExtensionRetainCount.m in Sources */,
4B957B0A2AC7AE700062CA31 /* Pixel.swift in Sources */,
4B957B0B2AC7AE700062CA31 /* PixelEvent.swift in Sources */,
4B957B0C2AC7AE700062CA31 /* TabBarFooter.swift in Sources */,
Expand All @@ -12051,6 +12067,7 @@
F1B33DF42BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift in Sources */,
4B957B1B2AC7AE700062CA31 /* ScriptSourceProviding.swift in Sources */,
4B957B1C2AC7AE700062CA31 /* CoreDataBookmarkImporter.swift in Sources */,
B6ABD0CC2BC03F610000EB69 /* SecurityScopedFileURLController.swift in Sources */,
4B957B1D2AC7AE700062CA31 /* SuggestionViewModel.swift in Sources */,
4B957B1E2AC7AE700062CA31 /* BookmarkManagedObject.swift in Sources */,
4B957B1F2AC7AE700062CA31 /* CSVLoginExporter.swift in Sources */,
Expand Down Expand Up @@ -12895,6 +12912,7 @@
4B37EE5F2B4CFC3C00A89A61 /* HomePageRemoteMessagingStorage.swift in Sources */,
4B9DB0472A983B24000927DB /* WaitlistRootView.swift in Sources */,
31F28C5128C8EEC500119F70 /* YoutubeOverlayUserScript.swift in Sources */,
B6ABD0CA2BC03F610000EB69 /* SecurityScopedFileURLController.swift in Sources */,
B6040856274B830F00680351 /* DictionaryExtension.swift in Sources */,
B684592725C93C0500DC17B6 /* Publishers.NestedObjectChanges.swift in Sources */,
B6DA06E62913F39400225DE2 /* MenuItemSelectors.swift in Sources */,
Expand Down Expand Up @@ -13016,6 +13034,7 @@
B6A9E46B2614618A0067D1B9 /* OperatingSystemVersionExtension.swift in Sources */,
4BDFA4AE27BF19E500648192 /* ToggleableScrollView.swift in Sources */,
1D36F4242A3B85C50052B527 /* TabCleanupPreparer.swift in Sources */,
B6ABD0CE2BC042CE0000EB69 /* NSURL+sandboxExtensionRetainCount.m in Sources */,
4B4D60DF2A0C875F00BCD287 /* NetworkProtectionOptionKeyExtension.swift in Sources */,
85AC3AEF25D5CE9800C7D2AA /* UserScripts.swift in Sources */,
B643BF1427ABF772000BACEC /* NSWorkspaceExtension.swift in Sources */,
Expand Down Expand Up @@ -13378,7 +13397,10 @@
B6E6BA062BA1FE10008AA7E1 /* NSApplicationExtension.swift in Sources */,
B6E6B9F62BA1FD90008AA7E1 /* SandboxTestTool.swift in Sources */,
B6E6BA252BA2EDDE008AA7E1 /* FileReadResult.swift in Sources */,
B6EECB322BC40A1400B3CB77 /* FileManagerExtension.swift in Sources */,
B6EECB302BC3FA5A00B3CB77 /* SecurityScopedFileURLController.swift in Sources */,
B6E6BA052BA1FE09008AA7E1 /* URLExtension.swift in Sources */,
B6EECB312BC3FAB100B3CB77 /* NSURL+sandboxExtensionRetainCount.m in Sources */,
B6E6BA202BA2E462008AA7E1 /* CollectionExtension.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
12 changes: 12 additions & 0 deletions DuckDuckGo/Common/Extensions/FileManagerExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,16 @@ extension FileManager {
return resolvedUrl.path.hasPrefix(trashUrl.path)
}

/// Check if location pointed by the URL is writable by writing an empty data to it and removing the file if write succeeds
/// - Throws error if writing to the location fails
func checkWritability(_ url: URL) throws {
if fileExists(atPath: url.path), isWritableFile(atPath: url.path) {
return // we can write
} else {
// either we can‘t write or there‘s no file at the url – try writing throwing access error if no permission
try Data().write(to: url)
try removeItem(at: url)
}
}

}
44 changes: 44 additions & 0 deletions DuckDuckGo/Common/Extensions/URLExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,50 @@ extension URL {

}

var isFileHidden: Bool {
get throws {
try self.resourceValues(forKeys: [.isHiddenKey]).isHidden ?? false
}
}

mutating func setFileHidden(_ hidden: Bool) throws {
var resourceValues = URLResourceValues()
resourceValues.isHidden = true
try setResourceValues(resourceValues)
}

/// Check if location pointed by the URL is writable
/// - Note: if there‘s no file at the URL, it will try to create a file and then remove it
func isWritableLocation() -> Bool {
do {
try FileManager.default.checkWritability(self)
return true
} catch {
return false
}
}

#if DEBUG && APPSTORE
/// sandbox extension URL access should be stopped after SecurityScopedFileURLController is deallocated - this function validates it and breaks if the file is still writable
func ensureUrlIsNotWritable(or handler: () -> Void) {
let fm = FileManager.default
// is the URL ~/Downloads?
if self.resolvingSymlinksInPath() == fm.urls(for: .downloadsDirectory, in: .userDomainMask).first!.resolvingSymlinksInPath() {
assert(isWritableLocation())
return
}
// is parent directory writable (e.g. ~/Downloads)?
if fm.isWritableFile(atPath: self.deletingLastPathComponent().path)
// trashed files are still accessible for some reason even after stopping access
|| fm.isInTrash(self)
// other file is being saved at the same URL
|| NSURL.activeSecurityScopedUrlUsages.contains(where: { $0.url !== self as NSURL && $0.url == self as NSURL })
|| !isWritableLocation() { return }

handler()
}
#endif

// MARK: - System Settings

static var fullDiskAccess = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles")!
Expand Down
42 changes: 42 additions & 0 deletions DuckDuckGo/Common/Logging/Logging.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,45 @@ func logOrAssertionFailure(_ message: String) {
os_log("%{public}s", type: .error, message)
#endif
}

#if DEBUG

func breakByRaisingSigInt(_ description: String, file: StaticString = #file, line: Int = #line) {
let fileLine = "\(("\(file)" as NSString).lastPathComponent):\(line)"
os_log("""
------------------------------------------------------------------------------------------------------
BREAK at %s:
------------------------------------------------------------------------------------------------------
%s
Hit Continue (^⌘Y) to continue program execution
------------------------------------------------------------------------------------------------------
""", type: .debug, fileLine, description.components(separatedBy: "\n").map { " " + $0.trimmingWhitespace() }.joined(separator: "\n"))
raise(SIGINT)
}

// get symbol from stack trace for a caller of a calling method
func callingSymbol() -> String {
let stackTrace = Thread.callStackSymbols
// find `callingSymbol` itself or dispatch_once_callout
var callingSymbolIdx = stackTrace.firstIndex(where: { $0.contains("_dispatch_once_callout") })
?? stackTrace.firstIndex(where: { $0.contains("callingSymbol") })!
// procedure calling `callingSymbol`
callingSymbolIdx += 1

var symbolName: String
repeat {
// caller for the procedure
callingSymbolIdx += 1
let line = stackTrace[callingSymbolIdx].replacingOccurrences(of: Bundle.main.executableURL!.lastPathComponent, with: "DDG")
symbolName = String(line.split(separator: " ", maxSplits: 3)[3]).components(separatedBy: " + ")[0]
} while stackTrace[callingSymbolIdx - 1].contains(symbolName.dropping(suffix: "To")) // skip objc wrappers

return symbolName
}

#endif
Loading

0 comments on commit c3b0c7b

Please sign in to comment.