Skip to content
Permalink
Browse files

Fix NetNewsWire bug #1020. When an app is quarantined its bundle URL …

…reflects its location on the translocated disk image Apple mounts and launches the app from. Instead, we want to always examine the original app location as the user sees it, so that we don't detect false positive movement by observing that the translocated position is different from the original.
  • Loading branch information...
danielpunkass committed Sep 14, 2019
1 parent 3339b6c commit 91f774873c18220e9f45e6afacda378206b25a6d
Showing with 36 additions and 25 deletions.
  1. +36 −25 RSCore/RSAppMovementMonitor.swift
@@ -26,8 +26,8 @@ public class RSAppMovementMonitor: NSObject {
//
// See: https://christiantietze.de/posts/2018/09/nsurl-filereferenceurl-swift/
//
let originalAppURL = Bundle.main.bundleURL
var movedAppURL: NSURL? = (Bundle.main.bundleURL as NSURL).fileReferenceURL() as NSURL?
let originalAppURL: URL?
var appTrackingURL: NSURL?

// We load these strings at launch time so that they can be localized. If we wait until
// the application has been moved, the localization will fail.
@@ -36,6 +36,15 @@ public class RSAppMovementMonitor: NSObject {
let alertRelaunchButtonText: String

override public init() {

// Establish baseline URLs. Note that simply asking for Bundle.main.bundleURL will return
// the translocated location of an app when it is launched in quarantine state. This leads
// to a permanent false-positive detection that the app has moved. To work around this, we
// ask for the fileReferenceURL's absoluteURL at launch time, and compare to the absoluteURL
// later to detect bona fide user-driven app movement.
self.appTrackingURL = (Bundle.main.bundleURL as NSURL).fileReferenceURL() as NSURL?
self.originalAppURL = appTrackingURL?.absoluteURL

let appName = Bundle.main.infoDictionary?[kCFBundleNameKey as String] as? String ?? NSLocalizedString("This app", comment: "Backup name if the app name cannot be deduced from the bundle")
let informativeTextTemplate = NSLocalizedString("%@ was moved or renamed while open.", comment: "Message text for app moved while running alert")
self.alertMessageText = String(format: informativeTextTemplate, arguments: [appName])
@@ -47,31 +56,33 @@ public class RSAppMovementMonitor: NSObject {
// Monitor for direct changes to the app bundle's folder - this will catch the
// majority of direct manipulations to the app's location on disk immediately,
// right as it happens.
self.fileDescriptor = open(originalAppURL.path, O_EVTONLY)
if self.fileDescriptor != -1 {
self.dispatchSource = DispatchSource.makeFileSystemObjectSource(fileDescriptor: self.fileDescriptor, eventMask: [.delete, .rename], queue: DispatchQueue.main)
if let source = self.dispatchSource {
source.setEventHandler {
self.invokeEventHandler()
}

source.setCancelHandler {
self.invalidate()
if let originalAppPath = originalAppURL?.path {
self.fileDescriptor = open(originalAppPath, O_EVTONLY)
if self.fileDescriptor != -1 {
self.dispatchSource = DispatchSource.makeFileSystemObjectSource(fileDescriptor: self.fileDescriptor, eventMask: [.delete, .rename], queue: DispatchQueue.main)
if let source = self.dispatchSource {
source.setEventHandler {
self.invokeEventHandler()
}

source.setCancelHandler {
self.invalidate()
}

source.resume()
}

source.resume()
}
}

// Also install a notification to re-check the location of the app on disk
// every time the app becomes active. This catches a good number of edge-case
// changes to the app bundle's path, such as when a containing folder or the
// volume name changes.
NotificationCenter.default.addObserver(forName: NSApplication.didBecomeActiveNotification, object: nil, queue: nil) { notification in
// Removing observer in invalidate doesn't seem to prevent this getting called? Maybe
// because it's on the same invocation of the runloop?
if self.isValid() && self.originalAppURL.absoluteURL != self.movedAppURL?.absoluteURL {
self.invokeEventHandler()
// Also install a notification to re-check the location of the app on disk
// every time the app becomes active. This catches a good number of edge-case
// changes to the app bundle's path, such as when a containing folder or the
// volume name changes.
NotificationCenter.default.addObserver(forName: NSApplication.didBecomeActiveNotification, object: nil, queue: nil) { notification in
// Removing observer in invalidate doesn't seem to prevent this getting called? Maybe
// because it's on the same invocation of the runloop?
if self.isValid() && self.originalAppURL != self.appTrackingURL?.absoluteURL {
self.invokeEventHandler()
}
}
}
}
@@ -133,7 +144,7 @@ public class RSAppMovementMonitor: NSObject {
if modalResponse == .alertFirstButtonReturn {
self.invalidate()

if let movedAppURL = self.movedAppURL as URL? {
if let movedAppURL = self.appTrackingURL as URL? {
self.relaunchFromURL(movedAppURL)
}
}

0 comments on commit 91f7748

Please sign in to comment.
You can’t perform that action at this time.