diff --git a/Frameworks/Sparkle/CHANGELOG b/Frameworks/Sparkle/CHANGELOG
index 985d84d94..d8896f4a4 100644
--- a/Frameworks/Sparkle/CHANGELOG
+++ b/Frameworks/Sparkle/CHANGELOG
@@ -1,3 +1,42 @@
+# 1.10.0
+
+* Massive improvements to the BinaryDelta tool (Zorg)
+ - Ability to track file permissions (Zorg)
+ - Nicely formatted log output (Zorg)
+ - Numerous bug fixes in handling of symlinks, empty directories, case-insensitive names, etc. (Zorg)
+ - Refactored and modernized code (Zorg)
+ - libxar is no longer weak-linked (C.W. Betts)
+* Double-check the code signature of the the app after installation (Isaac Wankerl)
+* Added headless guided package installation (Graham Miln)
+* Added ability to inject custom HTTP headers in appcast request (Mattias Gunneras)
+* Changes to make unarching more reliable (Zorg, Kornel Lesiński)
+* Have Sparkle build a framework module (C.W. Betts)
+* Stdout used for non error outputs (JDuquennoy)
+* French locale update (Kent Sutherland)
+
+# 1.9.0
+
+* Added SUUpdater delegate method for failures. (Benjamin Gordon)
+* Make the error definitions public (C.W. Betts)
+* Add support for lzma compressed tarballs (Kyle Fuller)
+* Back to SKIP_INSTALL=YES by default (Tony Arnold)
+* Properly set install names and rpaths for targets (Jake Petroules)
+* Use Library/Caches rather than app support directory (Kornel Lesiński)
+* Check for a modal window being onscreen before trying to put up the Sparkle prompt (Alf Watt)
+* Fixed crashes on 10.7 (Chris Campbell, Ger Teunis)
+* Fixed Sparkle tags parsing (Tamás Lustyik)
+* SULog code cleanups (Kevin Wojniak)
+* Make sure CFBundleVersion is a semantic version number. (Jake Petroules)
+* Replace typedef enums with typedef NS_ENUM to make Swift happier (C.W. Betts)
+* Fix warnings under Xcode 6.1 relating the SUUpdateAlert XIB (Tony Arnold)
+* Prefer string constants to strings (Jake Petroules)
+* Use Info.plist keys instead of macros (Jake Petroules)
+* Only export public symbols. (Jake Petroules)
+* BinaryDelta: avoid crash with bad paths (Jake Petroules)
+* Fixing Swedish translations (Erik Vikström)
+* Turkish localization fixes (Emir)
+* Proofing of Ukrainian localization (Vera Tkachenko)
+
# 1.8.0
* New SUDSAVerifier based on up-to-date OS X APIs (Zachary Waldowski)
diff --git a/Frameworks/Sparkle/Configurations/ConfigCommon.xcconfig b/Frameworks/Sparkle/Configurations/ConfigCommon.xcconfig
index 477cf7463..d8a107054 100644
--- a/Frameworks/Sparkle/Configurations/ConfigCommon.xcconfig
+++ b/Frameworks/Sparkle/Configurations/ConfigCommon.xcconfig
@@ -15,7 +15,7 @@ SPARKLE_AUTOMATED_DOWNGRADES = 0
SPARKLE_NORMALIZE_INSTALLED_APPLICATION_NAME = 0
SPARKLE_VERSION_MAJOR = 1
-SPARKLE_VERSION_MINOR = 8
+SPARKLE_VERSION_MINOR = 10
SPARKLE_VERSION_PATCH = 0
SPARKLE_VERSION = $(SPARKLE_VERSION_MAJOR).$(SPARKLE_VERSION_MINOR).$(SPARKLE_VERSION_PATCH)
@@ -35,6 +35,7 @@ CLANG_ENABLE_OBJC_ARC = YES
CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES
CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES
CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES
+CLANG_ENABLE_MODULES = YES
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES
CLANG_WARN_DOCUMENTATION_COMMENTS = YES
@@ -73,4 +74,4 @@ GCC_WARN_UNUSED_VARIABLE = YES
WARNING_CFLAGS_EXTRA = -Wno-custom-atomic-properties -Wno-implicit-atomic-properties
// Turn on all warnings, then disable a few which are almost impossible to avoid
-WARNING_CFLAGS = -Wall -Weverything -Wno-empty-translation-unit -Wno-unused-macros -Wno-gnu-statement-expression -Wno-receiver-is-weak -Wno-arc-repeated-use-of-weak $(WARNING_CFLAGS_EXTRA)
+WARNING_CFLAGS = -Wall -Weverything -Wno-empty-translation-unit -Wno-unused-macros -Wno-gnu-statement-expression -Wno-receiver-is-weak -Wno-arc-repeated-use-of-weak -Wno-auto-import -Wno-cstring-format-directive $(WARNING_CFLAGS_EXTRA)
diff --git a/Frameworks/Sparkle/Configurations/ConfigFramework.xcconfig b/Frameworks/Sparkle/Configurations/ConfigFramework.xcconfig
index e8e9d867d..ca9b25b63 100644
--- a/Frameworks/Sparkle/Configurations/ConfigFramework.xcconfig
+++ b/Frameworks/Sparkle/Configurations/ConfigFramework.xcconfig
@@ -9,3 +9,5 @@ FRAMEWORK_VERSION = A
INFOPLIST_FILE = Sparkle/Sparkle-Info.plist
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) BUILDING_SPARKLE=1
OTHER_LDFLAGS = -Wl,-U,_NSURLQuarantinePropertiesKey
+SKIP_INSTALL = YES
+DEFINES_MODULE = YES
diff --git a/Frameworks/Sparkle/Configurations/ConfigFrameworkDebug.xcconfig b/Frameworks/Sparkle/Configurations/ConfigFrameworkDebug.xcconfig
index 58d6b5aa6..184b8a7d4 100644
--- a/Frameworks/Sparkle/Configurations/ConfigFrameworkDebug.xcconfig
+++ b/Frameworks/Sparkle/Configurations/ConfigFrameworkDebug.xcconfig
@@ -1,2 +1,4 @@
#include "ConfigFramework.xcconfig"
+// Unit tests need access to non-public classes
+GCC_SYMBOLS_PRIVATE_EXTERN = NO
diff --git a/Frameworks/Sparkle/Configurations/ConfigRelaunch.xcconfig b/Frameworks/Sparkle/Configurations/ConfigRelaunch.xcconfig
index 1537a724f..292437c93 100644
--- a/Frameworks/Sparkle/Configurations/ConfigRelaunch.xcconfig
+++ b/Frameworks/Sparkle/Configurations/ConfigRelaunch.xcconfig
@@ -4,4 +4,5 @@ INFOPLIST_FILE = Sparkle/Autoupdate/Autoupdate-Info.plist
PRODUCT_NAME = $(SPARKLE_RELAUNCH_TOOL_NAME)
SKIP_INSTALL = YES
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon
+CLANG_ENABLE_MODULES = NO
OTHER_LDFLAGS = -Wl,-U,_NSURLQuarantinePropertiesKey
diff --git a/Frameworks/Sparkle/LICENSE b/Frameworks/Sparkle/LICENSE
index f91fce3df..f4020f8d3 100644
--- a/Frameworks/Sparkle/LICENSE
+++ b/Frameworks/Sparkle/LICENSE
@@ -30,6 +30,9 @@ EXTERNAL LICENSES
bspatch.c and bsdiff.c, from bsdiff 4.3 :
Copyright (c) 2003-2005 Colin Percival.
+sais.c and sais.c, from sais-lite (2010/08/07) :
+ Copyright (c) 2008-2010 Yuta Mori.
+
SUDSAVerifier.m:
Copyright (c) 2011 Mark Hamlin.
diff --git a/Frameworks/Sparkle/README.markdown b/Frameworks/Sparkle/README.markdown
index 912b6ee3f..0e557c536 100644
--- a/Frameworks/Sparkle/README.markdown
+++ b/Frameworks/Sparkle/README.markdown
@@ -1,7 +1,8 @@
-# Sparkle
-is an easy-to-use software update framework for Cocoa developers.
+# Sparkle [](https://travis-ci.org/sparkle-project/Sparkle)
-[](https://travis-ci.org/sparkle-project/Sparkle)
+An easy-to-use software update framework for Cocoa developers.
+
+
## Changes since 1.5b
diff --git a/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_128x128.png b/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_128x128.png
index cab9bc5ae..4d7985201 100644
Binary files a/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_128x128.png and b/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_128x128.png differ
diff --git a/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png b/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png
index ea89c0608..2bf2b0b8a 100644
Binary files a/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png and b/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png differ
diff --git a/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_16x16.png b/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_16x16.png
index dea3255ce..23eada4e0 100644
Binary files a/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_16x16.png and b/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_16x16.png differ
diff --git a/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png b/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png
index 0e7f543e3..fa1e0dacd 100644
Binary files a/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png and b/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png differ
diff --git a/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_256x256.png b/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_256x256.png
index b4a17b8d0..2bf2b0b8a 100644
Binary files a/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_256x256.png and b/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_256x256.png differ
diff --git a/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_32x32.png b/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_32x32.png
index 77e8fde55..eb1d810a2 100644
Binary files a/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_32x32.png and b/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_32x32.png differ
diff --git a/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png b/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png
index a3f6f7890..f44f97798 100644
Binary files a/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png and b/Frameworks/Sparkle/Resources/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png differ
diff --git a/Frameworks/Sparkle/Resources/Screenshot.png b/Frameworks/Sparkle/Resources/Screenshot.png
new file mode 100644
index 000000000..19c755d44
Binary files /dev/null and b/Frameworks/Sparkle/Resources/Screenshot.png differ
diff --git a/Frameworks/Sparkle/Resources/Sparkle.png b/Frameworks/Sparkle/Resources/Sparkle.png
index 8ccf2e734..13d59cd27 100644
Binary files a/Frameworks/Sparkle/Resources/Sparkle.png and b/Frameworks/Sparkle/Resources/Sparkle.png differ
diff --git a/Frameworks/Sparkle/Sparkle.podspec b/Frameworks/Sparkle/Sparkle.podspec
index 36c44bdc3..be51c9447 100644
--- a/Frameworks/Sparkle/Sparkle.podspec
+++ b/Frameworks/Sparkle/Sparkle.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "Sparkle"
- s.version = "1.8.0"
+ s.version = "1.9.0"
s.summary = "A software update framework for OS X"
s.description = "Sparkle is an easy-to-use software update framework for Cocoa developers."
s.homepage = "http://sparkle-project.org"
diff --git a/Frameworks/Sparkle/Sparkle.xcodeproj/project.pbxproj b/Frameworks/Sparkle/Sparkle.xcodeproj/project.pbxproj
index 22ec44113..fc1c0c1ee 100644
--- a/Frameworks/Sparkle/Sparkle.xcodeproj/project.pbxproj
+++ b/Frameworks/Sparkle/Sparkle.xcodeproj/project.pbxproj
@@ -34,9 +34,7 @@
14652F7C19A9725300959E44 /* SUBinaryDeltaCommon.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D06E8E20FD68CC7005AE3F6 /* SUBinaryDeltaCommon.m */; };
14652F7D19A9726700959E44 /* SUBinaryDeltaApply.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D06E8E00FD68CC7005AE3F6 /* SUBinaryDeltaApply.m */; };
14652F7E19A9728A00959E44 /* bspatch.c in Sources */ = {isa = PBXBuildFile; fileRef = 5D06E8DC0FD68CB9005AE3F6 /* bspatch.c */; settings = {COMPILER_FLAGS = "-w"; }; };
- 14652F7F19A973F900959E44 /* SUDSAVerifier.m in Sources */ = {isa = PBXBuildFile; fileRef = 61299A2E09CA2DAB00B7442F /* SUDSAVerifier.m */; };
14652F8019A9740F00959E44 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 61B5F8F609C4CEB300B25A18 /* Security.framework */; };
- 14652F8119A9744200959E44 /* SUBinaryDeltaCommon.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D06E8E20FD68CC7005AE3F6 /* SUBinaryDeltaCommon.m */; };
14652F8219A9746000959E44 /* SULog.m in Sources */ = {isa = PBXBuildFile; fileRef = 55C14F05136EF6DB00649790 /* SULog.m */; };
14652F8419A978C200959E44 /* SUExport.h in Headers */ = {isa = PBXBuildFile; fileRef = 14652F8319A9759F00959E44 /* SUExport.h */; settings = {ATTRIBUTES = (Public, ); }; };
14732BC01960F2C200593899 /* test_app_only_dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = 14732BBF1960F0AC00593899 /* test_app_only_dsa_pub.pem */; };
@@ -49,7 +47,6 @@
14950071195FCE3D00BC5B5B /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D6A5FE840307C02AAC07 /* AppKit.framework */; };
14950072195FCE4B00BC5B5B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D69BFE84028FC02AAC07 /* Foundation.framework */; };
14950073195FCE4E00BC5B5B /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D6A5FE840307C02AAC07 /* AppKit.framework */; };
- 14950075195FDF5900BC5B5B /* SUUpdaterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 14950074195FDF5900BC5B5B /* SUUpdaterTest.m */; };
14958C6E19AEBC950061B14F /* signed-test-file.txt in Resources */ = {isa = PBXBuildFile; fileRef = 14958C6B19AEBC530061B14F /* signed-test-file.txt */; };
14958C6F19AEBC980061B14F /* test-pubkey.pem in Resources */ = {isa = PBXBuildFile; fileRef = 14958C6C19AEBC610061B14F /* test-pubkey.pem */; };
3772FEA913DE0B6B00F79537 /* SUVersionDisplayProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 3772FEA813DE0B6B00F79537 /* SUVersionDisplayProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -75,6 +72,11 @@
55C14F7E136F005000649790 /* SUPlainInstallerInternals.m in Sources */ = {isa = PBXBuildFile; fileRef = 61B5F8E509C4CE3C00B25A18 /* SUPlainInstallerInternals.m */; };
55C14F9A136F045400649790 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 61B5F8F609C4CEB300B25A18 /* Security.framework */; };
55C14FC7136F05E100649790 /* Sparkle.strings in Resources */ = {isa = PBXBuildFile; fileRef = 61AAE8220A321A7F00D8810D /* Sparkle.strings */; };
+ 55E6F33319EC9F6C00005E76 /* SUErrors.h in Headers */ = {isa = PBXBuildFile; fileRef = 55E6F33219EC9F6C00005E76 /* SUErrors.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 5AF6C1E51AE86A410014A3AB /* SUPipedUnarchiverTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5AF6C1EA1AE870A90014A3AB /* SUPipedUnarchiverTest.m */; };
+ 5AF6C1E91AE86E270014A3AB /* test archive.zip in Resources */ = {isa = PBXBuildFile; fileRef = 5AF6C1E81AE86E270014A3AB /* test archive.zip */; };
+ 5AF6C74F1AEA46D10014A3AB /* test.sparkle_guided.pkg in Resources */ = {isa = PBXBuildFile; fileRef = 5AF6C74E1AEA46D10014A3AB /* test.sparkle_guided.pkg */; };
+ 5AF6C7541AEA49840014A3AB /* SUInstallerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5AF6C74C1AEA40760014A3AB /* SUInstallerTest.m */; };
5AF9DC3C1981DBEE001EA135 /* SUDSAVerifierTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5AF9DC3B1981DBEE001EA135 /* SUDSAVerifierTest.m */; };
5D06E8E90FD68CDB005AE3F6 /* bsdiff.c in Sources */ = {isa = PBXBuildFile; fileRef = 5D06E8DB0FD68CB9005AE3F6 /* bsdiff.c */; settings = {COMPILER_FLAGS = "-w"; }; };
5D06E8EA0FD68CDB005AE3F6 /* SUBinaryDeltaTool.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D06E8E30FD68CC7005AE3F6 /* SUBinaryDeltaTool.m */; };
@@ -106,13 +108,12 @@
61180BCB0D64138900B4E0D1 /* SUWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 61180BC90D64138900B4E0D1 /* SUWindowController.m */; };
6120721209CC5C4B007FE0F6 /* SUAutomaticUpdateAlert.h in Headers */ = {isa = PBXBuildFile; fileRef = 6120721009CC5C4B007FE0F6 /* SUAutomaticUpdateAlert.h */; settings = {ATTRIBUTES = (); }; };
6120721309CC5C4B007FE0F6 /* SUAutomaticUpdateAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = 6120721109CC5C4B007FE0F6 /* SUAutomaticUpdateAlert.m */; };
- 61227A160DB548B800AB99EA /* SUVersionComparisonTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 61227A150DB548B800AB99EA /* SUVersionComparisonTest.m */; };
61299A2F09CA2DAB00B7442F /* SUDSAVerifier.h in Headers */ = {isa = PBXBuildFile; fileRef = 61299A2D09CA2DAB00B7442F /* SUDSAVerifier.h */; settings = {ATTRIBUTES = (); }; };
61299A3009CA2DAB00B7442F /* SUDSAVerifier.m in Sources */ = {isa = PBXBuildFile; fileRef = 61299A2E09CA2DAB00B7442F /* SUDSAVerifier.m */; settings = {COMPILER_FLAGS = "-Wno-deprecated-declarations"; }; };
61299A4A09CA2DD000B7442F /* SUPlainInstallerInternals.h in Headers */ = {isa = PBXBuildFile; fileRef = 6129984309C9E2DA00B7442F /* SUPlainInstallerInternals.h */; settings = {ATTRIBUTES = (); }; };
61299A5C09CA6D4500B7442F /* SUConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 61299A5B09CA6D4500B7442F /* SUConstants.h */; settings = {ATTRIBUTES = (); }; };
61299A6009CA6EB100B7442F /* SUConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 61299A5F09CA6EB100B7442F /* SUConstants.m */; };
- 61299A8D09CA790200B7442F /* SUUnarchiver.h in Headers */ = {isa = PBXBuildFile; fileRef = 61299A8B09CA790200B7442F /* SUUnarchiver.h */; settings = {ATTRIBUTES = (); }; };
+ 61299A8D09CA790200B7442F /* SUUnarchiver.h in Headers */ = {isa = PBXBuildFile; fileRef = 61299A8B09CA790200B7442F /* SUUnarchiver.h */; settings = {ATTRIBUTES = (Private, ); }; };
61299A8E09CA790200B7442F /* SUUnarchiver.m in Sources */ = {isa = PBXBuildFile; fileRef = 61299A8C09CA790200B7442F /* SUUnarchiver.m */; };
61299B3609CB04E000B7442F /* Sparkle.h in Headers */ = {isa = PBXBuildFile; fileRef = 61299B3509CB04E000B7442F /* Sparkle.h */; settings = {ATTRIBUTES = (Public, ); }; };
612DCBAF0D488BC60015DBEA /* SUUpdatePermissionPrompt.h in Headers */ = {isa = PBXBuildFile; fileRef = 612DCBAD0D488BC60015DBEA /* SUUpdatePermissionPrompt.h */; settings = {ATTRIBUTES = (); }; };
@@ -166,6 +167,20 @@
61F83F720DBFE140006FDD30 /* SUBasicUpdateDriver.m in Sources */ = {isa = PBXBuildFile; fileRef = 61F83F700DBFE137006FDD30 /* SUBasicUpdateDriver.m */; };
61F83F740DBFE141006FDD30 /* SUBasicUpdateDriver.h in Headers */ = {isa = PBXBuildFile; fileRef = 61F83F6F0DBFE137006FDD30 /* SUBasicUpdateDriver.h */; settings = {ATTRIBUTES = (); }; };
61FA52880E2D9EA400EF58AD /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DC2EF5B0486A6940098B216 /* Sparkle.framework */; settings = {ATTRIBUTES = (Required, ); }; };
+ 721CF1A71AD7643600D9AC09 /* bsdiff.c in Sources */ = {isa = PBXBuildFile; fileRef = 5D06E8DB0FD68CB9005AE3F6 /* bsdiff.c */; };
+ 721CF1A81AD7644100D9AC09 /* bspatch.c in Sources */ = {isa = PBXBuildFile; fileRef = 5D06E8DC0FD68CB9005AE3F6 /* bspatch.c */; };
+ 721CF1A91AD7644C00D9AC09 /* sais.c in Sources */ = {isa = PBXBuildFile; fileRef = 7223E7611AD1AEFF008E3161 /* sais.c */; };
+ 721CF1AA1AD7647000D9AC09 /* libxar.1.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D1AF5890FD7678C0065DB48 /* libxar.1.dylib */; };
+ 721CF1AB1AD764EB00D9AC09 /* libbz2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D06E8FB0FD68D61005AE3F6 /* libbz2.dylib */; };
+ 7223E7631AD1AEFF008E3161 /* sais.c in Sources */ = {isa = PBXBuildFile; fileRef = 7223E7611AD1AEFF008E3161 /* sais.c */; };
+ 7268AC631AD634C200C3E0C1 /* SUBinaryDeltaCreate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7268AC621AD634C200C3E0C1 /* SUBinaryDeltaCreate.m */; };
+ 72D4DAA11AD7632900B211E2 /* SUBinaryDeltaCreate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7268AC621AD634C200C3E0C1 /* SUBinaryDeltaCreate.m */; };
+ 767B61AC1972D488004E0C3C /* SUGuidedPackageInstaller.h in Headers */ = {isa = PBXBuildFile; fileRef = 767B61AA1972D488004E0C3C /* SUGuidedPackageInstaller.h */; };
+ 767B61AD1972D488004E0C3C /* SUGuidedPackageInstaller.m in Sources */ = {isa = PBXBuildFile; fileRef = 767B61AB1972D488004E0C3C /* SUGuidedPackageInstaller.m */; };
+ 767B61AE1972D488004E0C3C /* SUGuidedPackageInstaller.m in Sources */ = {isa = PBXBuildFile; fileRef = 767B61AB1972D488004E0C3C /* SUGuidedPackageInstaller.m */; };
+ F8761EB11ADC5068000C9034 /* SUCodeSigningVerifierTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F8761EB01ADC5068000C9034 /* SUCodeSigningVerifierTest.m */; };
+ F8761EB31ADC50EB000C9034 /* SparkleTestCodeSignApp.zip in Resources */ = {isa = PBXBuildFile; fileRef = F8761EB21ADC50EB000C9034 /* SparkleTestCodeSignApp.zip */; };
+ F8761EB61ADC5E7A000C9034 /* SUCodeSigningVerifier.m in Sources */ = {isa = PBXBuildFile; fileRef = 61B078CD15A5FB6100600039 /* SUCodeSigningVerifier.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -398,7 +413,12 @@
55C14F04136EF6DB00649790 /* SULog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SULog.h; sourceTree = ""; };
55C14F05136EF6DB00649790 /* SULog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SULog.m; sourceTree = ""; };
55C14F31136EFC2400649790 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
+ 55E6F33219EC9F6C00005E76 /* SUErrors.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUErrors.h; sourceTree = ""; };
5AEF45D9189D1CC90030D7DC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Sparkle.strings; sourceTree = ""; };
+ 5AF6C1E81AE86E270014A3AB /* test archive.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = "test archive.zip"; sourceTree = ""; };
+ 5AF6C1EA1AE870A90014A3AB /* SUPipedUnarchiverTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUPipedUnarchiverTest.m; sourceTree = ""; };
+ 5AF6C74C1AEA40760014A3AB /* SUInstallerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUInstallerTest.m; sourceTree = ""; };
+ 5AF6C74E1AEA46D10014A3AB /* test.sparkle_guided.pkg */ = {isa = PBXFileReference; lastKnownFileType = file; path = test.sparkle_guided.pkg; sourceTree = ""; };
5AF9DC3B1981DBEE001EA135 /* SUDSAVerifierTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUDSAVerifierTest.m; sourceTree = ""; };
5D06E8D00FD68C7C005AE3F6 /* BinaryDelta */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = BinaryDelta; sourceTree = BUILT_PRODUCTS_DIR; };
5D06E8DB0FD68CB9005AE3F6 /* bsdiff.c */ = {isa = PBXFileReference; comments = "-Wno-shorten-64-to-32"; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = bsdiff.c; sourceTree = ""; };
@@ -529,8 +549,16 @@
61F614540E24A12D009F47E7 /* it */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Sparkle.strings; sourceTree = ""; };
61F83F6F0DBFE137006FDD30 /* SUBasicUpdateDriver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUBasicUpdateDriver.h; sourceTree = ""; };
61F83F700DBFE137006FDD30 /* SUBasicUpdateDriver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUBasicUpdateDriver.m; sourceTree = ""; };
+ 7223E7611AD1AEFF008E3161 /* sais.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sais.c; sourceTree = ""; };
+ 7223E7621AD1AEFF008E3161 /* sais.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sais.h; sourceTree = ""; };
+ 7268AC621AD634C200C3E0C1 /* SUBinaryDeltaCreate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUBinaryDeltaCreate.m; sourceTree = ""; };
+ 7268AC641AD634E400C3E0C1 /* SUBinaryDeltaCreate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUBinaryDeltaCreate.h; sourceTree = ""; };
+ 767B61AA1972D488004E0C3C /* SUGuidedPackageInstaller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUGuidedPackageInstaller.h; sourceTree = ""; };
+ 767B61AB1972D488004E0C3C /* SUGuidedPackageInstaller.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUGuidedPackageInstaller.m; sourceTree = ""; };
8DC2EF5A0486A6940098B216 /* Sparkle-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Sparkle-Info.plist"; sourceTree = ""; };
8DC2EF5B0486A6940098B216 /* Sparkle.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Sparkle.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ F8761EB01ADC5068000C9034 /* SUCodeSigningVerifierTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUCodeSigningVerifierTest.m; sourceTree = ""; };
+ F8761EB21ADC50EB000C9034 /* SparkleTestCodeSignApp.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = SparkleTestCodeSignApp.zip; sourceTree = ""; };
FA1941CA0D94A70100DD942E /* ConfigFrameworkDebug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = ConfigFrameworkDebug.xcconfig; sourceTree = ""; };
FA1941CB0D94A70100DD942E /* ConfigTestAppDebug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = ConfigTestAppDebug.xcconfig; sourceTree = ""; };
FA1941CC0D94A70100DD942E /* ConfigCommonRelease.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = ConfigCommonRelease.xcconfig; sourceTree = ""; };
@@ -579,8 +607,10 @@
files = (
14732BD119610A1200593899 /* AppKit.framework in Frameworks */,
14732BD019610A0D00593899 /* Foundation.framework in Frameworks */,
- 61FA52880E2D9EA400EF58AD /* Sparkle.framework in Frameworks */,
+ 721CF1AB1AD764EB00D9AC09 /* libbz2.dylib in Frameworks */,
+ 721CF1AA1AD7647000D9AC09 /* libxar.1.dylib in Frameworks */,
14652F8019A9740F00959E44 /* Security.framework in Frameworks */,
+ 61FA52880E2D9EA400EF58AD /* Sparkle.framework in Frameworks */,
14732BD319610A1800593899 /* XCTest.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -700,6 +730,8 @@
5D06E8DB0FD68CB9005AE3F6 /* bsdiff.c */,
5D06E8DC0FD68CB9005AE3F6 /* bspatch.c */,
611142E810FB1BE5009810AA /* bspatch.h */,
+ 7223E7611AD1AEFF008E3161 /* sais.c */,
+ 7223E7621AD1AEFF008E3161 /* sais.h */,
);
path = bsdiff;
sourceTree = "";
@@ -756,7 +788,10 @@
isa = PBXGroup;
children = (
14958C6B19AEBC530061B14F /* signed-test-file.txt */,
+ F8761EB21ADC50EB000C9034 /* SparkleTestCodeSignApp.zip */,
+ 5AF6C1E81AE86E270014A3AB /* test archive.zip */,
14958C6C19AEBC610061B14F /* test-pubkey.pem */,
+ 5AF6C74E1AEA46D10014A3AB /* test.sparkle_guided.pkg */,
);
path = Resources;
sourceTree = "";
@@ -777,6 +812,8 @@
5D06E8E00FD68CC7005AE3F6 /* SUBinaryDeltaApply.m */,
5D06E8E10FD68CC7005AE3F6 /* SUBinaryDeltaCommon.h */,
5D06E8E20FD68CC7005AE3F6 /* SUBinaryDeltaCommon.m */,
+ 7268AC641AD634E400C3E0C1 /* SUBinaryDeltaCreate.h */,
+ 7268AC621AD634C200C3E0C1 /* SUBinaryDeltaCreate.m */,
5D06E8E30FD68CC7005AE3F6 /* SUBinaryDeltaTool.m */,
5D06E9370FD69271005AE3F6 /* SUBinaryDeltaUnarchiver.h */,
5D06E9380FD69271005AE3F6 /* SUBinaryDeltaUnarchiver.m */,
@@ -802,12 +839,15 @@
61227A100DB5484000AB99EA /* Tests */ = {
isa = PBXGroup;
children = (
+ 14958C7019AEBE350061B14F /* Resources */,
612279DA0DB5470200AB99EA /* SparkleTests-Info.plist */,
142E0E0819A83AAC00E4312B /* SUBinaryDeltaTest.m */,
+ F8761EB01ADC5068000C9034 /* SUCodeSigningVerifierTest.m */,
5AF9DC3B1981DBEE001EA135 /* SUDSAVerifierTest.m */,
+ 5AF6C74C1AEA40760014A3AB /* SUInstallerTest.m */,
+ 5AF6C1EA1AE870A90014A3AB /* SUPipedUnarchiverTest.m */,
14950074195FDF5900BC5B5B /* SUUpdaterTest.m */,
61227A150DB548B800AB99EA /* SUVersionComparisonTest.m */,
- 14958C7019AEBE350061B14F /* Resources */,
);
path = Tests;
sourceTree = "";
@@ -849,6 +889,8 @@
618FA6DB0DB485440026945C /* Installation */ = {
isa = PBXGroup;
children = (
+ 767B61AA1972D488004E0C3C /* SUGuidedPackageInstaller.h */,
+ 767B61AB1972D488004E0C3C /* SUGuidedPackageInstaller.m */,
618FA4FF0DAE88B40026945C /* SUInstaller.h */,
618FA5000DAE88B40026945C /* SUInstaller.m */,
618FA5200DAE8E8A0026945C /* SUPackageInstaller.h */,
@@ -868,6 +910,7 @@
61CFB3280E385186007A1735 /* Sparkle.pch */,
61299A5B09CA6D4500B7442F /* SUConstants.h */,
61299A5F09CA6EB100B7442F /* SUConstants.m */,
+ 55E6F33219EC9F6C00005E76 /* SUErrors.h */,
14652F8319A9759F00959E44 /* SUExport.h */,
61EF67580E25C5B400F754E0 /* SUHost.h */,
61EF67550E25B58D00F754E0 /* SUHost.m */,
@@ -881,10 +924,10 @@
61B5F91D09C4CF7F00B25A18 /* Test Application */ = {
isa = PBXGroup;
children = (
- 14732BBF1960F0AC00593899 /* test_app_only_dsa_pub.pem */,
61B5F92A09C4CFD800B25A18 /* InfoPlist.strings */,
61B5F92409C4CFC900B25A18 /* main.m */,
61B5F92C09C4CFD800B25A18 /* MainMenu.xib */,
+ 14732BBF1960F0AC00593899 /* test_app_only_dsa_pub.pem */,
61B5F90409C4CEE200B25A18 /* TestApplication-Info.plist */,
);
name = "Test Application";
@@ -983,9 +1026,11 @@
5D06E9390FD69271005AE3F6 /* SUBinaryDeltaUnarchiver.h in Headers */,
61B078CE15A5FB6100600039 /* SUCodeSigningVerifier.h in Headers */,
61299A5C09CA6D4500B7442F /* SUConstants.h in Headers */,
- 14652F8419A978C200959E44 /* SUExport.h in Headers */,
6102FE4A0E07803800F85D09 /* SUDiskImageUnarchiver.h in Headers */,
61299A2F09CA2DAB00B7442F /* SUDSAVerifier.h in Headers */,
+ 55E6F33319EC9F6C00005E76 /* SUErrors.h in Headers */,
+ 14652F8419A978C200959E44 /* SUExport.h in Headers */,
+ 767B61AC1972D488004E0C3C /* SUGuidedPackageInstaller.h in Headers */,
61EF67590E25C5B400F754E0 /* SUHost.h in Headers */,
618FA5010DAE88B40026945C /* SUInstaller.h in Headers */,
55C14F06136EF6DB00649790 /* SULog.h in Headers */,
@@ -1156,7 +1201,7 @@
isa = PBXProject;
attributes = {
CLASSPREFIX = SU;
- LastUpgradeCheck = 0600;
+ LastUpgradeCheck = 0630;
ORGANIZATIONNAME = "Sparkle Project";
TargetAttributes = {
612279D80DB5470200AB99EA = {
@@ -1233,8 +1278,11 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 14958C6F19AEBC980061B14F /* test-pubkey.pem in Resources */,
14958C6E19AEBC950061B14F /* signed-test-file.txt in Resources */,
+ F8761EB31ADC50EB000C9034 /* SparkleTestCodeSignApp.zip in Resources */,
+ 5AF6C1E91AE86E270014A3AB /* test archive.zip in Resources */,
+ 14958C6F19AEBC980061B14F /* test-pubkey.pem in Resources */,
+ 5AF6C74F1AEA46D10014A3AB /* test.sparkle_guided.pkg in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1252,10 +1300,10 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 14732BC01960F2C200593899 /* test_app_only_dsa_pub.pem in Resources */,
1420DF50196247F900203BB0 /* Images.xcassets in Resources */,
61B5F92E09C4CFD800B25A18 /* InfoPlist.strings in Resources */,
61B5F92F09C4CFD800B25A18 /* MainMenu.xib in Resources */,
+ 14732BC01960F2C200593899 /* test_app_only_dsa_pub.pem in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1361,7 +1409,9 @@
buildActionMask = 2147483647;
files = (
55C14BD4136EEFCE00649790 /* Autoupdate.m in Sources */,
+ F8761EB61ADC5E7A000C9034 /* SUCodeSigningVerifier.m in Sources */,
55C14F00136EF6B700649790 /* SUConstants.m in Sources */,
+ 767B61AE1972D488004E0C3C /* SUGuidedPackageInstaller.m in Sources */,
55C14F0C136EF6EA00649790 /* SUHost.m in Sources */,
55C14F0D136EF6F200649790 /* SUInstaller.m in Sources */,
55C14F08136EF6DB00649790 /* SULog.m in Sources */,
@@ -1381,8 +1431,10 @@
files = (
5D06E8E90FD68CDB005AE3F6 /* bsdiff.c in Sources */,
14652F7E19A9728A00959E44 /* bspatch.c in Sources */,
+ 7223E7631AD1AEFF008E3161 /* sais.c in Sources */,
14652F7D19A9726700959E44 /* SUBinaryDeltaApply.m in Sources */,
14652F7C19A9725300959E44 /* SUBinaryDeltaCommon.m in Sources */,
+ 7268AC631AD634C200C3E0C1 /* SUBinaryDeltaCreate.m in Sources */,
5D06E8EA0FD68CDB005AE3F6 /* SUBinaryDeltaTool.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -1391,13 +1443,16 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 14652F8119A9744200959E44 /* SUBinaryDeltaCommon.m in Sources */,
- 14652F7F19A973F900959E44 /* SUDSAVerifier.m in Sources */,
- 14652F8219A9746000959E44 /* SULog.m in Sources */,
- 5AF9DC3C1981DBEE001EA135 /* SUDSAVerifierTest.m in Sources */,
+ 721CF1A71AD7643600D9AC09 /* bsdiff.c in Sources */,
+ 721CF1A81AD7644100D9AC09 /* bspatch.c in Sources */,
+ 721CF1A91AD7644C00D9AC09 /* sais.c in Sources */,
+ 72D4DAA11AD7632900B211E2 /* SUBinaryDeltaCreate.m in Sources */,
142E0E0919A83AAC00E4312B /* SUBinaryDeltaTest.m in Sources */,
- 14950075195FDF5900BC5B5B /* SUUpdaterTest.m in Sources */,
- 61227A160DB548B800AB99EA /* SUVersionComparisonTest.m in Sources */,
+ F8761EB11ADC5068000C9034 /* SUCodeSigningVerifierTest.m in Sources */,
+ 5AF9DC3C1981DBEE001EA135 /* SUDSAVerifierTest.m in Sources */,
+ 5AF6C7541AEA49840014A3AB /* SUInstallerTest.m in Sources */,
+ 14652F8219A9746000959E44 /* SULog.m in Sources */,
+ 5AF6C1E51AE86A410014A3AB /* SUPipedUnarchiverTest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1427,6 +1482,7 @@
61299A6009CA6EB100B7442F /* SUConstants.m in Sources */,
6102FE4B0E07803800F85D09 /* SUDiskImageUnarchiver.m in Sources */,
61299A3009CA2DAB00B7442F /* SUDSAVerifier.m in Sources */,
+ 767B61AD1972D488004E0C3C /* SUGuidedPackageInstaller.m in Sources */,
61EF67560E25B58D00F754E0 /* SUHost.m in Sources */,
618FA5020DAE88B40026945C /* SUInstaller.m in Sources */,
55C14F07136EF6DB00649790 /* SULog.m in Sources */,
diff --git a/Frameworks/Sparkle/Sparkle.xcodeproj/xcshareddata/xcschemes/Distribution.xcscheme b/Frameworks/Sparkle/Sparkle.xcodeproj/xcshareddata/xcschemes/Distribution.xcscheme
index 9d6cba27c..d7da85d75 100644
--- a/Frameworks/Sparkle/Sparkle.xcodeproj/xcshareddata/xcschemes/Distribution.xcscheme
+++ b/Frameworks/Sparkle/Sparkle.xcodeproj/xcshareddata/xcschemes/Distribution.xcscheme
@@ -1,6 +1,6 @@
+@interface TerminationListener : NSObject
@property (copy) NSString *hostpath;
@property (copy) NSString *executablepath;
@@ -32,6 +32,7 @@ static const NSTimeInterval SUParentQuitCheckInterval = .25;
@property (strong) SUHost *host;
@property (assign) BOOL shouldRelaunch;
@property (assign) BOOL shouldShowUI;
+@property (strong) SUStatusController *statusController;
- (void)parentHasQuit;
@@ -57,6 +58,7 @@ static const NSTimeInterval SUParentQuitCheckInterval = .25;
@synthesize host;
@synthesize shouldRelaunch;
@synthesize shouldShowUI;
+@synthesize statusController;
- (instancetype)initWithHostPath:(NSString *)inhostpath executablePath:(NSString *)execpath parentProcessId:(pid_t)ppid folderPath:(NSString *)infolderpath shouldRelaunch:(BOOL)relaunch shouldShowUI:(BOOL)showUI selfPath:(NSString *)inSelfPath
{
@@ -86,6 +88,7 @@ static const NSTimeInterval SUParentQuitCheckInterval = .25;
- (void)dealloc
{
[self.longInstallationTimer invalidate];
+ [self.statusController close];
}
@@ -152,40 +155,40 @@ static const NSTimeInterval SUParentQuitCheckInterval = .25;
[statusCtl beginActionWithTitle:SULocalizedString(@"Installing update...", @"")
maxProgressValue: 0 statusText: @""];
[statusCtl showWindow:self];
+
+ [self.statusController close]; // If there's an existing status controller, close it before we release our strong reference to it.
+ self.statusController = statusCtl; // Keep a strong reference to the status controller, or else it might get prematurely deallocated.
}
[SUInstaller installFromUpdateFolder:self.folderpath
overHost:self.host
installationPath:self.installationPath
- delegate:self
- versionComparator:[SUStandardVersionComparator defaultComparator]];
-}
-
-- (void)installerFinishedForHost:(SUHost *)__unused aHost
-{
- [self relaunch];
-}
-
-- (void)installerForHost:(SUHost *)__unused host failedWithError:(NSError *)error __attribute__((noreturn))
-{
- if (self.shouldShowUI)
- NSRunAlertPanel(@"", @"%@", @"OK", @"", @"", [error localizedDescription]);
- exit(EXIT_FAILURE);
+ versionComparator:[SUStandardVersionComparator defaultComparator]
+ completionHandler:^(NSError *error) {
+ if (error) {
+ if (self.shouldShowUI) {
+ NSRunAlertPanel(@"", @"%@", @"OK", @"", @"", [error localizedDescription]);
+ }
+ exit(EXIT_FAILURE);
+ } else {
+ [self relaunch];
+ }
+ }];
}
@end
int main(int __unused argc, const char __unused *argv[])
{
- @autoreleasepool {
+ @autoreleasepool
+ {
NSArray *args = [[NSProcessInfo processInfo] arguments];
if (args.count < 5 || args.count > 7) {
return EXIT_FAILURE;
}
BOOL shouldShowUI = (args.count > 6) ? [args[6] boolValue] : YES;
- if (shouldShowUI)
- {
+ if (shouldShowUI) {
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
}
@@ -198,9 +201,9 @@ int main(int __unused argc, const char __unused *argv[])
shouldShowUI:shouldShowUI
selfPath:[[NSBundle mainBundle] bundlePath]];
- [termListen class];
[[NSApplication sharedApplication] run];
-
+ // Ensure termListen is not deallocated by ARC before caling -[NSApplication run]
+ [termListen class];
}
return EXIT_SUCCESS;
diff --git a/Frameworks/Sparkle/Sparkle/SUAppcast.h b/Frameworks/Sparkle/Sparkle/SUAppcast.h
index 7d455231b..8f3efc8e8 100644
--- a/Frameworks/Sparkle/Sparkle/SUAppcast.h
+++ b/Frameworks/Sparkle/Sparkle/SUAppcast.h
@@ -9,24 +9,18 @@
#ifndef SUAPPCAST_H
#define SUAPPCAST_H
+#import
#import "SUExport.h"
-@protocol SUAppcastDelegate;
-
@class SUAppcastItem;
-SU_EXPORT @interface SUAppcast : NSObject
+SU_EXPORT @interface SUAppcast : NSObject
-@property (weak) id delegate;
@property (copy) NSString *userAgentString;
+@property (copy) NSDictionary *httpHeaders;
-- (void)fetchAppcastFromURL:(NSURL *)url;
+- (void)fetchAppcastFromURL:(NSURL *)url completionBlock:(void (^)(NSError *))err;
@property (readonly, copy) NSArray *items;
@end
-@protocol SUAppcastDelegate
-- (void)appcastDidFinishLoading:(SUAppcast *)appcast;
-- (void)appcast:(SUAppcast *)appcast failedToLoadWithError:(NSError *)error;
-@end
-
#endif
diff --git a/Frameworks/Sparkle/Sparkle/SUAppcast.m b/Frameworks/Sparkle/Sparkle/SUAppcast.m
index 196267fb9..eda206a97 100644
--- a/Frameworks/Sparkle/Sparkle/SUAppcast.m
+++ b/Frameworks/Sparkle/Sparkle/SUAppcast.m
@@ -33,6 +33,7 @@
@end
@interface SUAppcast ()
+@property (strong) void (^completionBlock)(NSError *);
@property (copy) NSString *downloadFilename;
@property (strong) NSURLDownload *download;
@property (copy) NSArray *items;
@@ -43,18 +44,28 @@
@implementation SUAppcast
@synthesize downloadFilename;
-@synthesize delegate;
+@synthesize completionBlock;
@synthesize userAgentString;
+@synthesize httpHeaders;
@synthesize download;
@synthesize items;
-- (void)fetchAppcastFromURL:(NSURL *)url
+- (void)fetchAppcastFromURL:(NSURL *)url completionBlock:(void (^)(NSError *))block
{
+ self.completionBlock = block;
+
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30.0];
if (self.userAgentString) {
[request setValue:self.userAgentString forHTTPHeaderField:@"User-Agent"];
}
+ if (self.httpHeaders) {
+ for (NSString *key in self.httpHeaders) {
+ id value = [self.httpHeaders objectForKey:key];
+ [request setValue:value forHTTPHeaderField:key];
+ }
+ }
+
[request setValue:@"application/rss+xml,*/*;q=0.1" forHTTPHeaderField:@"Accept"];
self.download = [[NSURLDownload alloc] initWithRequest:request delegate:self];
@@ -202,31 +213,23 @@
}
}
- if ([appcastItems count])
- {
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wselector"
- // @selector(date) is from SUAppcastItem
- NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:NSStringFromSelector(@selector(date)) ascending:NO];
-#pragma clang diagnostic pop
+ if ([appcastItems count]) {
+ NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"date" ascending:NO];
[appcastItems sortUsingDescriptors:@[sort]];
self.items = appcastItems;
}
- if (failed)
- {
+ if (failed) {
[self reportError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUAppcastParseError userInfo:@{ NSLocalizedDescriptionKey: SULocalizedString(@"An error occurred while parsing the update feed.", nil) }]];
- }
- else if ([self.delegate respondsToSelector:@selector(appcastDidFinishLoading:)])
- {
- [self.delegate appcastDidFinishLoading:self];
+ } else {
+ self.completionBlock(nil);
+ self.completionBlock = nil;
}
}
- (void)download:(NSURLDownload *)__unused aDownload didFailWithError:(NSError *)error
{
- if (self.downloadFilename)
- {
+ if (self.downloadFilename) {
[[NSFileManager defaultManager] removeItemAtPath:self.downloadFilename error:nil];
}
self.downloadFilename = nil;
@@ -241,10 +244,15 @@
- (void)reportError:(NSError *)error
{
- if ([self.delegate respondsToSelector:@selector(appcast:failedToLoadWithError:)])
- {
- [self.delegate appcast:self failedToLoadWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUAppcastError userInfo:@{NSLocalizedDescriptionKey: SULocalizedString(@"An error occurred in retrieving update information. Please try again later.", nil), NSLocalizedFailureReasonErrorKey: [error localizedDescription]}]];
- }
+ NSURL *failingUrl = error.userInfo[NSURLErrorFailingURLErrorKey];
+
+ self.completionBlock([NSError errorWithDomain:SUSparkleErrorDomain code:SUAppcastError userInfo:@{
+ NSLocalizedDescriptionKey: SULocalizedString(@"An error occurred in retrieving update information. Please try again later.", nil),
+ NSLocalizedFailureReasonErrorKey: [error localizedDescription],
+ NSUnderlyingErrorKey: error,
+ NSURLErrorFailingURLErrorKey: failingUrl ? failingUrl : [NSNull null],
+ }]);
+ self.completionBlock = nil;
}
- (NSXMLNode *)bestNodeInNodes:(NSArray *)nodes
diff --git a/Frameworks/Sparkle/Sparkle/SUAutomaticUpdateAlert.h b/Frameworks/Sparkle/Sparkle/SUAutomaticUpdateAlert.h
index 0a2c86102..1fcfcfb7b 100644
--- a/Frameworks/Sparkle/Sparkle/SUAutomaticUpdateAlert.h
+++ b/Frameworks/Sparkle/Sparkle/SUAutomaticUpdateAlert.h
@@ -11,8 +11,6 @@
#import "SUWindowController.h"
-@protocol SUAutomaticUpdateAlertDelegate;
-
typedef NS_ENUM(NSInteger, SUAutomaticInstallationChoice) {
SUInstallNowChoice,
SUInstallLaterChoice,
@@ -22,15 +20,11 @@ typedef NS_ENUM(NSInteger, SUAutomaticInstallationChoice) {
@class SUAppcastItem, SUHost;
@interface SUAutomaticUpdateAlert : SUWindowController
-- (instancetype)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)hostBundle delegate:(id)delegate;
+- (instancetype)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)hostBundle completionBlock:(void (^)(SUAutomaticInstallationChoice))c;
- (IBAction)installNow:sender;
- (IBAction)installLater:sender;
- (IBAction)doNotInstall:sender;
@end
-@protocol SUAutomaticUpdateAlertDelegate
-- (void)automaticUpdateAlert:(SUAutomaticUpdateAlert *)aua finishedWithChoice:(SUAutomaticInstallationChoice)choice;
-@end
-
#endif
diff --git a/Frameworks/Sparkle/Sparkle/SUAutomaticUpdateAlert.m b/Frameworks/Sparkle/Sparkle/SUAutomaticUpdateAlert.m
index 85cb0cbcb..4271e6084 100644
--- a/Frameworks/Sparkle/Sparkle/SUAutomaticUpdateAlert.m
+++ b/Frameworks/Sparkle/Sparkle/SUAutomaticUpdateAlert.m
@@ -11,23 +11,22 @@
#import "SUHost.h"
@interface SUAutomaticUpdateAlert ()
+@property (strong) void(^completionBlock)(SUAutomaticInstallationChoice);
@property (strong) SUAppcastItem *updateItem;
-@property (weak) id delegate;
@property (strong) SUHost *host;
@end
@implementation SUAutomaticUpdateAlert
-@synthesize delegate;
@synthesize host;
@synthesize updateItem;
+@synthesize completionBlock;
-- (instancetype)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)aHost delegate:(id)del
+- (instancetype)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)aHost completionBlock:(void (^)(SUAutomaticInstallationChoice))block
{
- self = [super initWithHost:aHost windowNibName:@"SUAutomaticUpdateAlert"];
- if (self)
- {
+ self = [super initWithWindowNibName:@"SUAutomaticUpdateAlert"];
+ if (self) {
self.updateItem = item;
- self.delegate = del;
+ self.completionBlock = block;
self.host = aHost;
[self setShouldCascadeWindows:NO];
[[self window] center];
@@ -40,19 +39,22 @@
- (IBAction)installNow:(id)__unused sender
{
[self close];
- [self.delegate automaticUpdateAlert:self finishedWithChoice:SUInstallNowChoice];
+ self.completionBlock(SUInstallNowChoice);
+ self.completionBlock = nil;
}
- (IBAction)installLater:(id)__unused sender
{
[self close];
- [self.delegate automaticUpdateAlert:self finishedWithChoice:SUInstallLaterChoice];
+ self.completionBlock(SUInstallLaterChoice);
+ self.completionBlock = nil;
}
- (IBAction)doNotInstall:(id)__unused sender
{
[self close];
- [self.delegate automaticUpdateAlert:self finishedWithChoice:SUDoNotInstallChoice];
+ self.completionBlock(SUDoNotInstallChoice);
+ self.completionBlock = nil;
}
- (NSImage *)applicationIcon
diff --git a/Frameworks/Sparkle/Sparkle/SUAutomaticUpdateDriver.h b/Frameworks/Sparkle/Sparkle/SUAutomaticUpdateDriver.h
index 9935936eb..4dd90e8f0 100644
--- a/Frameworks/Sparkle/Sparkle/SUAutomaticUpdateDriver.h
+++ b/Frameworks/Sparkle/Sparkle/SUAutomaticUpdateDriver.h
@@ -13,7 +13,7 @@
#import "SUBasicUpdateDriver.h"
#import "SUAutomaticUpdateAlert.h"
-@interface SUAutomaticUpdateDriver : SUBasicUpdateDriver
+@interface SUAutomaticUpdateDriver : SUBasicUpdateDriver
@end
diff --git a/Frameworks/Sparkle/Sparkle/SUAutomaticUpdateDriver.m b/Frameworks/Sparkle/Sparkle/SUAutomaticUpdateDriver.m
index 20a0a0658..45b3d5469 100644
--- a/Frameworks/Sparkle/Sparkle/SUAutomaticUpdateDriver.m
+++ b/Frameworks/Sparkle/Sparkle/SUAutomaticUpdateDriver.m
@@ -42,13 +42,14 @@ static const NSTimeInterval SUAutomaticUpdatePromptImpatienceTimer = 60 * 60 * 2
- (void)showUpdateAlert
{
self.interruptible = NO;
- self.alert = [[SUAutomaticUpdateAlert alloc] initWithAppcastItem:self.updateItem host:self.host delegate:self];
+ self.alert = [[SUAutomaticUpdateAlert alloc] initWithAppcastItem:self.updateItem host:self.host completionBlock:^(SUAutomaticInstallationChoice choice) {
+ [self automaticUpdateAlertFinishedWithChoice:choice];
+ }];
// If the app is a menubar app or the like, we need to focus it first and alter the
// update prompt to behave like a normal window. Otherwise if the window were hidden
// there may be no way for the application to be activated to make it visible again.
- if ([self.host isBackgroundApplication])
- {
+ if ([self.host isBackgroundApplication]) {
[[self.alert window] setHidesOnDeactivate:NO];
[NSApp activateIgnoringOtherApps:YES];
}
@@ -138,7 +139,7 @@ static const NSTimeInterval SUAutomaticUpdatePromptImpatienceTimer = 60 * 60 * 2
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationDidBecomeActiveNotification object:NSApp];
}
-- (void)automaticUpdateAlert:(SUAutomaticUpdateAlert *)__unused aua finishedWithChoice:(SUAutomaticInstallationChoice)choice
+- (void)automaticUpdateAlertFinishedWithChoice:(SUAutomaticInstallationChoice)choice
{
switch (choice)
{
@@ -161,8 +162,6 @@ static const NSTimeInterval SUAutomaticUpdatePromptImpatienceTimer = 60 * 60 * 2
}
}
-- (BOOL)shouldInstallSynchronously { return self.postponingInstallation; }
-
- (void)installWithToolAndRelaunch:(BOOL)relaunch displayingUserInterface:(BOOL)showUI
{
if (relaunch) {
@@ -180,10 +179,18 @@ static const NSTimeInterval SUAutomaticUpdatePromptImpatienceTimer = 60 * 60 * 2
- (void)abortUpdateWithError:(NSError *)error
{
- if (self.showErrors)
+ if (self.showErrors) {
[super abortUpdateWithError:error];
- else
+ } else {
+ // Call delegate separately here because otherwise it won't know we stopped.
+ // Normally this gets called by the superclass
+ id updaterDelegate = [self.updater delegate];
+ if ([updaterDelegate respondsToSelector:@selector(updater:didAbortWithError:)]) {
+ [updaterDelegate updater:self.updater didAbortWithError:error];
+ }
+
[self abortUpdate];
+ }
}
@end
diff --git a/Frameworks/Sparkle/Sparkle/SUBasicUpdateDriver.h b/Frameworks/Sparkle/Sparkle/SUBasicUpdateDriver.h
index 32e320bc6..8c6960d83 100644
--- a/Frameworks/Sparkle/Sparkle/SUBasicUpdateDriver.h
+++ b/Frameworks/Sparkle/Sparkle/SUBasicUpdateDriver.h
@@ -15,7 +15,7 @@
#import "SUAppcast.h"
@class SUAppcastItem, SUHost;
-@interface SUBasicUpdateDriver : SUUpdateDriver
+@interface SUBasicUpdateDriver : SUUpdateDriver
@property (strong, readonly) SUAppcastItem *updateItem;
@property (strong, readonly) NSURLDownload *download;
@@ -27,6 +27,7 @@
- (BOOL)hostSupportsItem:(SUAppcastItem *)ui;
- (BOOL)itemContainsSkippedVersion:(SUAppcastItem *)ui;
- (BOOL)itemContainsValidUpdate:(SUAppcastItem *)ui;
+- (void)appcastDidFinishLoading:(SUAppcast *)ac;
- (void)didFindValidUpdate;
- (void)didNotFindUpdate;
diff --git a/Frameworks/Sparkle/Sparkle/SUBasicUpdateDriver.m b/Frameworks/Sparkle/Sparkle/SUBasicUpdateDriver.m
index 12147df72..a0536faba 100644
--- a/Frameworks/Sparkle/Sparkle/SUBasicUpdateDriver.m
+++ b/Frameworks/Sparkle/Sparkle/SUBasicUpdateDriver.m
@@ -54,9 +54,15 @@
SUAppcast *appcast = [[SUAppcast alloc] init];
- [appcast setDelegate:self];
[appcast setUserAgentString:[self.updater userAgentString]];
- [appcast fetchAppcastFromURL:URL];
+ [appcast setHttpHeaders:[self.updater httpHeaders]];
+ [appcast fetchAppcastFromURL:URL completionBlock:^(NSError *error) {
+ if (error) {
+ [self abortUpdateWithError:error];
+ } else {
+ [self appcastDidFinishLoading:appcast];
+ }
+ }];
}
- (id)versionComparator
@@ -136,12 +142,10 @@
item = [updateEnumerator nextObject];
} while (item && ![self hostSupportsItem:item]);
- if (binaryDeltaSupported()) {
- SUAppcastItem *deltaUpdateItem = [item deltaUpdates][[self.host version]];
- if (deltaUpdateItem && [self hostSupportsItem:deltaUpdateItem]) {
- self.nonDeltaUpdateItem = item;
- item = deltaUpdateItem;
- }
+ SUAppcastItem *deltaUpdateItem = [item deltaUpdates][[self.host version]];
+ if (deltaUpdateItem && [self hostSupportsItem:deltaUpdateItem]) {
+ self.nonDeltaUpdateItem = item;
+ item = deltaUpdateItem;
}
}
@@ -154,11 +158,6 @@
}
}
-- (void)appcast:(SUAppcast *)__unused ac failedToLoadWithError:(NSError *)error
-{
- [self abortUpdateWithError:error];
-}
-
- (void)didFindValidUpdate
{
assert(self.updateItem);
@@ -200,11 +199,11 @@
NSString *downloadFileName = [NSString stringWithFormat:@"%@ %@", [self.host name], [self.updateItem versionString]];
- self.tempDir = [[self.host appSupportPath] stringByAppendingPathComponent:downloadFileName];
+ self.tempDir = [self.host.appCachePath stringByAppendingPathComponent:downloadFileName];
int cnt = 1;
while ([[NSFileManager defaultManager] fileExistsAtPath:self.tempDir] && cnt <= 999)
{
- self.tempDir = [[self.host appSupportPath] stringByAppendingPathComponent:[NSString stringWithFormat:@"%@ %d", downloadFileName, cnt++]];
+ self.tempDir = [self.host.appCachePath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@ %d", downloadFileName, cnt++]];
}
// Create the temporary directory if necessary.
@@ -227,7 +226,7 @@
if (newBundlePath) {
if ([SUCodeSigningVerifier hostApplicationIsCodeSigned]) {
NSError *error = nil;
- if ([SUCodeSigningVerifier codeSignatureIsValidAtPath:newBundlePath error:&error]) {
+ if ([SUCodeSigningVerifier codeSignatureMatchesHostAndIsValidAtPath:newBundlePath error:&error]) {
return YES;
} else {
SULog(@"Code signature check on update failed: %@. Sparkle will use DSA signature instead.", error);
@@ -254,7 +253,16 @@
- (void)download:(NSURLDownload *)__unused download didFailWithError:(NSError *)error
{
- [self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SURelaunchError userInfo:@{NSLocalizedDescriptionKey: SULocalizedString(@"An error occurred while downloading the update. Please try again later.", nil), NSLocalizedFailureReasonErrorKey: [error localizedDescription]}]];
+ NSURL *failingUrl = error.userInfo[NSURLErrorFailingURLErrorKey];
+ if (!failingUrl) {
+ failingUrl = [self.updateItem fileURL];
+ }
+
+ [self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SURelaunchError userInfo:@{
+ NSLocalizedDescriptionKey: SULocalizedString(@"An error occurred while downloading the update. Please try again later.", nil),
+ NSUnderlyingErrorKey: error,
+ NSURLErrorFailingURLErrorKey: failingUrl ? failingUrl : [NSNull null],
+ }]];
}
- (BOOL)download:(NSURLDownload *)__unused download shouldDecodeSourceDataOfMIMEType:(NSString *)encodingType
@@ -266,14 +274,13 @@
- (void)extractUpdate
{
- SUUnarchiver *unarchiver = [SUUnarchiver unarchiverForPath:self.downloadPath updatingHost:self.host];
- if (!unarchiver)
- {
+ SUUnarchiver *unarchiver = [SUUnarchiver unarchiverForPath:self.downloadPath updatingHostBundlePath:[[self.host bundle] bundlePath]];
+ if (!unarchiver) {
SULog(@"Error: No valid unarchiver for %@!", self.downloadPath);
[self unarchiverDidFail:nil];
return;
}
- [unarchiver setDelegate:self];
+ unarchiver.delegate = self;
[unarchiver start];
}
@@ -303,8 +310,6 @@
[self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUUnarchivingError userInfo:@{ NSLocalizedDescriptionKey: SULocalizedString(@"An error occurred while extracting the archive. Please try again later.", nil) }]];
}
-- (BOOL)shouldInstallSynchronously { return NO; }
-
- (void)installWithToolAndRelaunch:(BOOL)relaunch
{
// Perhaps a poor assumption but: if we're not relaunching, we assume we shouldn't be showing any UI either. Because non-relaunching installations are kicked off without any user interaction, we shouldn't be interrupting them.
@@ -356,27 +361,32 @@
// Copy the relauncher into a temporary directory so we can get to it after the new version's installed.
// Only the paranoid survive: if there's already a stray copy of relaunch there, we would have problems.
NSString *const relaunchPathToCopy = [sparkleBundle pathForResource:[[sparkleBundle infoDictionary] objectForKey:SURelaunchToolNameKey] ofType:@"app"];
- if (relaunchPathToCopy != nil)
- {
- NSString *targetPath = [[self.host appSupportPath] stringByAppendingPathComponent:[relaunchPathToCopy lastPathComponent]];
+ if (relaunchPathToCopy != nil) {
+ NSString *targetPath = [self.host.appCachePath stringByAppendingPathComponent:[relaunchPathToCopy lastPathComponent]];
// Only the paranoid survive: if there's already a stray copy of relaunch there, we would have problems.
NSError *error = nil;
[[NSFileManager defaultManager] createDirectoryAtPath:[targetPath stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:@{} error:&error];
- if ([SUPlainInstaller copyPathWithAuthentication:relaunchPathToCopy overPath:targetPath temporaryName:nil error:&error])
+ if ([SUPlainInstaller copyPathWithAuthentication:relaunchPathToCopy overPath:targetPath temporaryName:nil error:&error]) {
self.relaunchPath = targetPath;
- else
- [self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SURelaunchError userInfo:@{NSLocalizedDescriptionKey: SULocalizedString(@"An error occurred while extracting the archive. Please try again later.", nil), NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"Couldn't copy relauncher (%@) to temporary path (%@)! %@", relaunchPathToCopy, targetPath, (error ? [error localizedDescription] : @"")]}]];
+ } else {
+ [self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SURelaunchError userInfo:@{
+ NSLocalizedDescriptionKey: SULocalizedString(@"An error occurred while extracting the archive. Please try again later.", nil),
+ NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"Couldn't copy relauncher (%@) to temporary path (%@)! %@", relaunchPathToCopy, targetPath, (error ? [error localizedDescription] : @"")]
+ }]];
+ }
}
[[NSNotificationCenter defaultCenter] postNotificationName:SUUpdaterWillRestartNotification object:self];
if ([updaterDelegate respondsToSelector:@selector(updaterWillRelaunchApplication:)])
[updaterDelegate updaterWillRelaunchApplication:self.updater];
- if(!self.relaunchPath || ![[NSFileManager defaultManager] fileExistsAtPath:self.relaunchPath])
- {
+ if (!self.relaunchPath || ![[NSFileManager defaultManager] fileExistsAtPath:self.relaunchPath]) {
// Note that we explicitly use the host app's name here, since updating plugin for Mail relaunches Mail, not just the plugin.
- [self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SURelaunchError userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:SULocalizedString(@"An error occurred while relaunching %1$@, but the new version will be available next time you run %1$@.", nil), [self.host name]], NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"Couldn't find the relauncher (expected to find it at %@)", self.relaunchPath]}]];
+ [self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SURelaunchError userInfo:@{
+ NSLocalizedDescriptionKey: [NSString stringWithFormat:SULocalizedString(@"An error occurred while relaunching %1$@, but the new version will be available next time you run %1$@.", nil), [self.host name]],
+ NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"Couldn't find the relauncher (expected to find it at %@)", self.relaunchPath]
+ }]];
// We intentionally don't abandon the update here so that the host won't initiate another.
return;
}
@@ -414,10 +424,16 @@
- (void)installerForHost:(SUHost *)aHost failedWithError:(NSError *)error
{
- if (aHost != self.host) { return; }
+ if (aHost != self.host) {
+ return;
+ }
NSError *dontThrow = nil;
[[NSFileManager defaultManager] removeItemAtPath:self.relaunchPath error:&dontThrow]; // Clean up the copied relauncher
- [self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUInstallationError userInfo:@{NSLocalizedDescriptionKey: SULocalizedString(@"An error occurred while installing the update. Please try again later.", nil), NSLocalizedFailureReasonErrorKey: [error localizedDescription]}]];
+ [self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUInstallationError userInfo:@{
+ NSLocalizedDescriptionKey: SULocalizedString(@"An error occurred while installing the update. Please try again later.", nil),
+ NSLocalizedFailureReasonErrorKey: [error localizedDescription],
+ NSUnderlyingErrorKey: error,
+ }]];
}
- (void)abortUpdate
@@ -431,11 +447,18 @@
- (void)abortUpdateWithError:(NSError *)error
{
if ([error code] != SUNoUpdateError) { // Let's not bother logging this.
- SULog(@"Error: %@ %@", [error localizedDescription], [error localizedFailureReason]);
+ SULog(@"Error: %@ %@ (URL %@)", error.localizedDescription, error.localizedFailureReason, error.userInfo[NSURLErrorFailingURLErrorKey]);
}
if (self.download) {
[self.download cancel];
}
+
+ // Notify host app that update has aborted
+ id updaterDelegate = [self.updater delegate];
+ if ([updaterDelegate respondsToSelector:@selector(updater:didAbortWithError:)]) {
+ [updaterDelegate updater:self.updater didAbortWithError:error];
+ }
+
[self abortUpdate];
}
diff --git a/Frameworks/Sparkle/Sparkle/SUBinaryDeltaApply.h b/Frameworks/Sparkle/Sparkle/SUBinaryDeltaApply.h
index d44501093..293c1e11a 100644
--- a/Frameworks/Sparkle/Sparkle/SUBinaryDeltaApply.h
+++ b/Frameworks/Sparkle/Sparkle/SUBinaryDeltaApply.h
@@ -10,6 +10,6 @@
#define SUBINARYDELTAAPPLY_H
@class NSString;
-int applyBinaryDelta(NSString *source, NSString *destination, NSString *patchFile);
+int applyBinaryDelta(NSString *source, NSString *destination, NSString *patchFile, BOOL verbose);
#endif
diff --git a/Frameworks/Sparkle/Sparkle/SUBinaryDeltaApply.m b/Frameworks/Sparkle/Sparkle/SUBinaryDeltaApply.m
index 85dbd0cd5..43952df98 100644
--- a/Frameworks/Sparkle/Sparkle/SUBinaryDeltaApply.m
+++ b/Frameworks/Sparkle/Sparkle/SUBinaryDeltaApply.m
@@ -15,57 +15,125 @@
#include
#include
-static void applyBinaryDeltaToFile(xar_t x, xar_file_t file, NSString *sourceFilePath, NSString *destinationFilePath)
+static BOOL applyBinaryDeltaToFile(xar_t x, xar_file_t file, NSString *sourceFilePath, NSString *destinationFilePath)
{
NSString *patchFile = temporaryFilename(@"apply-binary-delta");
xar_extract_tofile(x, file, [patchFile fileSystemRepresentation]);
const char *argv[] = {"/usr/bin/bspatch", [sourceFilePath fileSystemRepresentation], [destinationFilePath fileSystemRepresentation], [patchFile fileSystemRepresentation]};
- bspatch(4, (char **)argv);
+ BOOL success = (bspatch(4, (char **)argv) == 0);
unlink([patchFile fileSystemRepresentation]);
+ return success;
}
-int applyBinaryDelta(NSString *source, NSString *destination, NSString *patchFile)
+int applyBinaryDelta(NSString *source, NSString *destination, NSString *patchFile, BOOL verbose)
{
- xar_t x = xar_open([patchFile UTF8String], READ);
+ xar_t x = xar_open([patchFile fileSystemRepresentation], READ);
if (!x) {
- fprintf(stderr, "Unable to open %s. Giving up.\n", [patchFile UTF8String]);
+ fprintf(stderr, "Unable to open %s. Giving up.\n", [patchFile fileSystemRepresentation]);
return 1;
}
+
+ SUBinaryDeltaMajorVersion majorDiffVersion = FIRST_DELTA_DIFF_MAJOR_VERSION;
+ SUBinaryDeltaMinorVersion minorDiffVersion = FIRST_DELTA_DIFF_MINOR_VERSION;
- NSString *expectedBeforeHash = nil;
- NSString *expectedAfterHash = nil;
+ NSString *expectedBeforeHashv1 = nil;
+ NSString *expectedAfterHashv1 = nil;
+
+ NSString *expectedNewBeforeHash = nil;
+ NSString *expectedNewAfterHash = nil;
+
xar_subdoc_t subdoc;
for (subdoc = xar_subdoc_first(x); subdoc; subdoc = xar_subdoc_next(subdoc)) {
- if (!strcmp(xar_subdoc_name(subdoc), "binary-delta-attributes")) {
+ if (!strcmp(xar_subdoc_name(subdoc), BINARY_DELTA_ATTRIBUTES_KEY)) {
const char *value = 0;
- xar_subdoc_prop_get(subdoc, "before-sha1", &value);
+
+ // available in version 2.0 or later
+ xar_subdoc_prop_get(subdoc, MAJOR_DIFF_VERSION_KEY, &value);
if (value)
- expectedBeforeHash = @(value);
-
- xar_subdoc_prop_get(subdoc, "after-sha1", &value);
+ majorDiffVersion = (SUBinaryDeltaMajorVersion)[@(value) intValue];
+
+ // available in version 2.0 or later
+ xar_subdoc_prop_get(subdoc, MINOR_DIFF_VERSION_KEY, &value);
if (value)
- expectedAfterHash = @(value);
+ minorDiffVersion = (SUBinaryDeltaMinorVersion)[@(value) intValue];
+
+ // available in version 2.0 or later
+ xar_subdoc_prop_get(subdoc, BEFORE_TREE_SHA1_KEY, &value);
+ if (value)
+ expectedNewBeforeHash = @(value);
+
+ // available in version 2.0 or later
+ xar_subdoc_prop_get(subdoc, AFTER_TREE_SHA1_KEY, &value);
+ if (value)
+ expectedNewAfterHash = @(value);
+
+ // only available in version 1.0
+ xar_subdoc_prop_get(subdoc, BEFORE_TREE_SHA1_OLD_KEY, &value);
+ if (value)
+ expectedBeforeHashv1 = @(value);
+
+ // only available in version 1.0
+ xar_subdoc_prop_get(subdoc, AFTER_TREE_SHA1_OLD_KEY, &value);
+ if (value)
+ expectedAfterHashv1 = @(value);
}
}
+
+ if (majorDiffVersion < FIRST_DELTA_DIFF_MAJOR_VERSION) {
+ fprintf(stderr, "Unable to identify diff-version %u in delta. Giving up.\n", majorDiffVersion);
+ return 1;
+ }
+
+ if (majorDiffVersion > LATEST_DELTA_DIFF_MAJOR_VERSION) {
+ fprintf(stderr, "A later version is needed to apply this patch (on major version %u, but patch requests version %u).\n", LATEST_DELTA_DIFF_MAJOR_VERSION, majorDiffVersion);
+ return 1;
+ }
+
+ BOOL usesNewTreeHash = MAJOR_VERSION_IS_AT_LEAST(majorDiffVersion, SUBeigeMajorVersion);
+
+ NSString *expectedBeforeHash = usesNewTreeHash ? expectedNewBeforeHash : expectedBeforeHashv1;
+ NSString *expectedAfterHash = usesNewTreeHash ? expectedNewAfterHash : expectedAfterHashv1;
if (!expectedBeforeHash || !expectedAfterHash) {
fprintf(stderr, "Unable to find before-sha1 or after-sha1 metadata in delta. Giving up.\n");
return 1;
}
- fprintf(stderr, "Verifying source... ");
- NSString *beforeHash = hashOfTree(source);
-
- if (![beforeHash isEqualToString:expectedBeforeHash]) {
- fprintf(stderr, "Source doesn't have expected hash (%s != %s). Giving up.\n", [expectedBeforeHash UTF8String], [beforeHash UTF8String]);
+ if (verbose) {
+ fprintf(stderr, "Applying version %u.%u patch...\n", majorDiffVersion, minorDiffVersion);
+ fprintf(stderr, "Verifying source...");
+ }
+
+ NSString *beforeHash = hashOfTreeWithVersion(source, majorDiffVersion);
+ if (!beforeHash) {
+ fprintf(stderr, "\nUnable to calculate hash of tree %s\n", [source fileSystemRepresentation]);
return 1;
}
- fprintf(stderr, "\nCopying files... ");
- removeTree(destination);
- copyTree(source, destination);
+ if (![beforeHash isEqualToString:expectedBeforeHash]) {
+ fprintf(stderr, "\nSource doesn't have expected hash (%s != %s). Giving up.\n", [expectedBeforeHash UTF8String], [beforeHash UTF8String]);
+ return 1;
+ }
- fprintf(stderr, "\nPatching... ");
+ if (verbose) {
+ fprintf(stderr, "\nCopying files...");
+ }
+
+ if (!removeTree(destination)) {
+ fprintf(stderr, "\nFailed to remove %s\n", [destination fileSystemRepresentation]);
+ return 1;
+ }
+ if (!copyTree(source, destination)) {
+ fprintf(stderr, "\nFailed to copy %s to %s\n", [source fileSystemRepresentation], [destination fileSystemRepresentation]);
+ return 1;
+ }
+
+ BOOL hasExtractKeyAvailable = MAJOR_VERSION_IS_AT_LEAST(majorDiffVersion, SUBeigeMajorVersion);
+
+ if (verbose) {
+ fprintf(stderr, "\nPatching...");
+ }
+ NSFileManager *fileManager = [[NSFileManager alloc] init];
xar_file_t file;
xar_iter_t iter = xar_iter_new();
for (file = xar_file_first(x, iter); file; file = xar_file_next(iter)) {
@@ -73,29 +141,86 @@ int applyBinaryDelta(NSString *source, NSString *destination, NSString *patchFil
NSString *sourceFilePath = [source stringByAppendingPathComponent:path];
NSString *destinationFilePath = [destination stringByAppendingPathComponent:path];
+ // Don't use -[NSFileManager fileExistsAtPath:] because it will follow symbolic links
+ BOOL fileExisted = verbose && [fileManager attributesOfItemAtPath:destinationFilePath error:nil];
+ BOOL removedFile = NO;
+
const char *value;
- if (!xar_prop_get(file, "delete", &value) || !xar_prop_get(file, "delete-then-extract", &value)) {
- removeTree(destinationFilePath);
- if (!xar_prop_get(file, "delete", &value))
+ if (!xar_prop_get(file, DELETE_KEY, &value) ||
+ (!hasExtractKeyAvailable && !xar_prop_get(file, DELETE_THEN_EXTRACT_OLD_KEY, &value))) {
+ if (!removeTree(destinationFilePath)) {
+ fprintf(stderr, "\n%s or %s: failed to remove %s\n", DELETE_KEY, DELETE_THEN_EXTRACT_OLD_KEY, [destination fileSystemRepresentation]);
+ return 1;
+ }
+ if (!hasExtractKeyAvailable && !xar_prop_get(file, DELETE_KEY, &value)) {
+ if (verbose) {
+ fprintf(stderr, "\n❌ %s %s", VERBOSE_DELETED, [path fileSystemRepresentation]);
+ }
continue;
+ }
+
+ removedFile = YES;
}
- if (!xar_prop_get(file, "binary-delta", &value))
- applyBinaryDeltaToFile(x, file, sourceFilePath, destinationFilePath);
- else
- xar_extract_tofile(x, file, [destinationFilePath fileSystemRepresentation]);
+ if (!xar_prop_get(file, BINARY_DELTA_KEY, &value)) {
+ if (!applyBinaryDeltaToFile(x, file, sourceFilePath, destinationFilePath)) {
+ fprintf(stderr, "\nUnable to patch %s to destination %s\n", [sourceFilePath fileSystemRepresentation], [destinationFilePath fileSystemRepresentation]);
+ return 1;
+ }
+
+ if (verbose) {
+ fprintf(stderr, "\n🔨 %s %s", VERBOSE_PATCHED, [path fileSystemRepresentation]);
+ }
+ } else if ((hasExtractKeyAvailable && !xar_prop_get(file, EXTRACT_KEY, &value)) ||
+ (!hasExtractKeyAvailable && xar_prop_get(file, MODIFY_PERMISSIONS_KEY, &value))) { // extract and permission modifications don't coexist
+
+ if (xar_extract_tofile(x, file, [destinationFilePath fileSystemRepresentation]) != 0) {
+ fprintf(stderr, "\nUnable to extract file to %s\n", [destinationFilePath fileSystemRepresentation]);
+ return 1;
+ }
+
+ if (verbose) {
+ if (fileExisted) {
+ fprintf(stderr, "\n✏️ %s %s", VERBOSE_UPDATED, [path fileSystemRepresentation]);
+ } else {
+ fprintf(stderr, "\n✅ %s %s", VERBOSE_ADDED, [path fileSystemRepresentation]);
+ }
+ }
+ } else if (verbose && removedFile) {
+ fprintf(stderr, "\n❌ %s %s", VERBOSE_DELETED, [path fileSystemRepresentation]);
+ }
+
+ if (!xar_prop_get(file, MODIFY_PERMISSIONS_KEY, &value)) {
+ mode_t mode = (mode_t)[[NSString stringWithUTF8String:value] intValue];
+ if (!modifyPermissions(destinationFilePath, mode)) {
+ fprintf(stderr, "\nUnable to modify permissions (%s) on file %s\n", value, [destinationFilePath fileSystemRepresentation]);
+ return 1;
+ }
+
+ if (verbose) {
+ fprintf(stderr, "\n👮 %s %s (0%o)", VERBOSE_MODIFIED, [path fileSystemRepresentation], mode);
+ }
+ }
}
xar_close(x);
- fprintf(stderr, "\nVerifying destination... ");
- NSString *afterHash = hashOfTree(destination);
+ if (verbose) {
+ fprintf(stderr, "\nVerifying destination...");
+ }
+ NSString *afterHash = hashOfTreeWithVersion(destination, majorDiffVersion);
+ if (!afterHash) {
+ fprintf(stderr, "\nUnable to calculate hash of tree %s\n", [destination fileSystemRepresentation]);
+ return 1;
+ }
if (![afterHash isEqualToString:expectedAfterHash]) {
- fprintf(stderr, "Destination doesn't have expected hash (%s != %s). Giving up.\n", [expectedAfterHash UTF8String], [afterHash UTF8String]);
+ fprintf(stderr, "\nDestination doesn't have expected hash (%s != %s). Giving up.\n", [expectedAfterHash UTF8String], [afterHash UTF8String]);
removeTree(destination);
return 1;
}
- fprintf(stderr, "\nDone!\n");
+ if (verbose) {
+ fprintf(stderr, "\nDone!\n");
+ }
return 0;
}
diff --git a/Frameworks/Sparkle/Sparkle/SUBinaryDeltaCommon.h b/Frameworks/Sparkle/Sparkle/SUBinaryDeltaCommon.h
index 4e330da7c..44a5d04e4 100644
--- a/Frameworks/Sparkle/Sparkle/SUBinaryDeltaCommon.h
+++ b/Frameworks/Sparkle/Sparkle/SUBinaryDeltaCommon.h
@@ -11,16 +11,71 @@
#include
+#define PERMISSION_FLAGS (S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID | S_ISVTX)
+
+#define IS_VALID_PERMISSIONS(mode) \
+ (((mode & PERMISSION_FLAGS) == 0755) || ((mode & PERMISSION_FLAGS) == 0644))
+
+#define BINARY_DELTA_ATTRIBUTES_KEY "binary-delta-attributes"
+#define MAJOR_DIFF_VERSION_KEY "major-version"
+#define MINOR_DIFF_VERSION_KEY "minor-version"
+#define BEFORE_TREE_SHA1_KEY "before-tree-sha1"
+#define AFTER_TREE_SHA1_KEY "after-tree-sha1"
+#define DELETE_KEY "delete"
+#define EXTRACT_KEY "extract"
+#define BINARY_DELTA_KEY "binary-delta"
+#define MODIFY_PERMISSIONS_KEY "mod-permissions"
+
+// Properties no longer used in new patches
+#define DELETE_THEN_EXTRACT_OLD_KEY "delete-then-extract"
+#define BEFORE_TREE_SHA1_OLD_KEY "before-sha1"
+#define AFTER_TREE_SHA1_OLD_KEY "after-sha1"
+
+#define VERBOSE_DELETED "Deleted" // file is deleted from the file system when applying a patch
+#define VERBOSE_REMOVED "Removed" // file is set to be removed when creating a patch
+#define VERBOSE_ADDED "Added" // file is added to the patch or file system
+#define VERBOSE_DIFFED "Diffed" // file is diffed when creating a patch
+#define VERBOSE_PATCHED "Patched" // file is patched when applying a patch
+#define VERBOSE_UPDATED "Updated" // file's contents are updated
+#define VERBOSE_MODIFIED "Modified" // file's metadata is modified
+
+#define MAJOR_VERSION_IS_AT_LEAST(actualMajor, expectedMajor) (actualMajor >= expectedMajor)
+
+// Each major version will be assigned a name of a color
+// Changes that break backwards compatibility will have different major versions
+// Changes that affect creating but not applying patches will have different minor versions
+
+typedef NS_ENUM(uint16_t, SUBinaryDeltaMajorVersion)
+{
+ SUAzureMajorVersion = 1,
+ SUBeigeMajorVersion = 2
+};
+
+// Only keep track of the latest minor version for each major version
+typedef NS_ENUM(uint16_t, SUBinaryDeltaMinorVersion)
+{
+ SUAzureMinorVersion = 1,
+ SUBeigeMinorVersion = 0,
+};
+
+#define FIRST_DELTA_DIFF_MAJOR_VERSION SUAzureMajorVersion
+#define FIRST_DELTA_DIFF_MINOR_VERSION 0
+
+#define LATEST_DELTA_DIFF_MAJOR_VERSION SUBeigeMajorVersion
+
@class NSString;
@class NSData;
-extern int binaryDeltaSupported(void);
extern int compareFiles(const FTSENT **a, const FTSENT **b);
-extern NSData *hashOfFile(FTSENT *ent);
+extern NSData *hashOfFileContents(FTSENT *ent);
+extern NSString *hashOfTreeWithVersion(NSString *path, uint16_t majorVersion);
extern NSString *hashOfTree(NSString *path);
-extern void removeTree(NSString *path);
-extern void copyTree(NSString *source, NSString *dest);
+extern BOOL removeTree(NSString *path);
+extern BOOL copyTree(NSString *source, NSString *dest);
+extern BOOL modifyPermissions(NSString *path, mode_t desiredPermissions);
extern NSString *pathRelativeToDirectory(NSString *directory, NSString *path);
NSString *temporaryFilename(NSString *base);
+NSString *temporaryDirectory(NSString *base);
NSString *stringWithFileSystemRepresentation(const char*);
+SUBinaryDeltaMinorVersion latestMinorVersionForMajorVersion(SUBinaryDeltaMajorVersion majorVersion);
#endif
diff --git a/Frameworks/Sparkle/Sparkle/SUBinaryDeltaCommon.m b/Frameworks/Sparkle/Sparkle/SUBinaryDeltaCommon.m
index 78ba2606b..9bde20c31 100644
--- a/Frameworks/Sparkle/Sparkle/SUBinaryDeltaCommon.m
+++ b/Frameworks/Sparkle/Sparkle/SUBinaryDeltaCommon.m
@@ -17,13 +17,6 @@
#include
#include
-int binaryDeltaSupported(void)
-{
- // OS X 10.4 didn't include libxar, so we link against it weakly.
- // This checks whether libxar is available at runtime.
- return xar_close != 0;
-}
-
int compareFiles(const FTSENT **a, const FTSENT **b)
{
return strcoll((*a)->fts_name, (*b)->fts_name);
@@ -42,6 +35,17 @@ NSString *stringWithFileSystemRepresentation(const char *input) {
return [[NSFileManager defaultManager] stringWithFileSystemRepresentation:input length:strlen(input)];
}
+SUBinaryDeltaMinorVersion latestMinorVersionForMajorVersion(SUBinaryDeltaMajorVersion majorVersion)
+{
+ switch (majorVersion) {
+ case SUAzureMajorVersion:
+ return SUAzureMinorVersion;
+ case SUBeigeMajorVersion:
+ return SUBeigeMinorVersion;
+ }
+ return 0;
+}
+
NSString *temporaryFilename(NSString *base)
{
NSString *template = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.XXXXXXXXXX", base]];
@@ -63,6 +67,22 @@ NSString *temporaryFilename(NSString *base)
return stringWithFileSystemRepresentation(buffer);
}
+NSString *temporaryDirectory(NSString *base)
+{
+ NSString *template = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.XXXXXXXXXX", base]];
+ NSMutableData *data = [NSMutableData data];
+ [data appendBytes:template.fileSystemRepresentation length:strlen(template.fileSystemRepresentation) + 1];
+
+ char *buffer = data.mutableBytes;
+ char *templateResult = mkdtemp(buffer);
+ if (templateResult == NULL) {
+ perror("mkdtemp");
+ return nil;
+ }
+
+ return stringWithFileSystemRepresentation(templateResult);
+}
+
static void _hashOfBuffer(unsigned char *hash, const char* buffer, ssize_t bufferLength)
{
assert(bufferLength >= 0 && bufferLength <= UINT32_MAX);
@@ -72,59 +92,57 @@ static void _hashOfBuffer(unsigned char *hash, const char* buffer, ssize_t buffe
CC_SHA1_Final(hash, &hashContext);
}
-static void _hashOfFile(unsigned char* hash, FTSENT *ent)
+static BOOL _hashOfFileContents(unsigned char* hash, FTSENT *ent)
{
if (ent->fts_info == FTS_SL) {
char linkDestination[MAXPATHLEN + 1];
ssize_t linkDestinationLength = readlink(ent->fts_path, linkDestination, MAXPATHLEN);
if (linkDestinationLength < 0) {
perror("readlink");
- return;
+ return NO;
}
_hashOfBuffer(hash, linkDestination, linkDestinationLength);
- return;
- }
-
- if (ent->fts_info == FTS_F) {
+ } else if (ent->fts_info == FTS_F) {
int fileDescriptor = open(ent->fts_path, O_RDONLY);
if (fileDescriptor == -1) {
perror("open");
- return;
+ return NO;
}
ssize_t fileSize = ent->fts_statp->st_size;
if (fileSize == 0) {
_hashOfBuffer(hash, NULL, 0);
- close(fileDescriptor);
- return;
+ } else {
+ void *buffer = mmap(0, (size_t)fileSize, PROT_READ, MAP_FILE | MAP_PRIVATE, fileDescriptor, 0);
+ if (buffer == (void*)-1) {
+ close(fileDescriptor);
+ perror("mmap");
+ return NO;
+ }
+
+ _hashOfBuffer(hash, buffer, fileSize);
+ munmap(buffer, (size_t)fileSize);
}
-
- void *buffer = mmap(0, (size_t)fileSize, PROT_READ, MAP_FILE | MAP_PRIVATE, fileDescriptor, 0);
- if (buffer == (void*)-1) {
- close(fileDescriptor);
- perror("mmap");
- return;
- }
-
- _hashOfBuffer(hash, buffer, fileSize);
- munmap(buffer, (size_t)fileSize);
close(fileDescriptor);
- return;
- }
-
- if (ent->fts_info == FTS_D)
+ } else if (ent->fts_info == FTS_D) {
memset(hash, 0xdd, CC_SHA1_DIGEST_LENGTH);
+ } else {
+ return NO;
+ }
+ return YES;
}
-NSData *hashOfFile(FTSENT *ent)
+NSData *hashOfFileContents(FTSENT *ent)
{
unsigned char fileHash[CC_SHA1_DIGEST_LENGTH];
- _hashOfFile(fileHash, ent);
+ if (!_hashOfFileContents(fileHash, ent)) {
+ return nil;
+ }
return [NSData dataWithBytes:fileHash length:CC_SHA1_DIGEST_LENGTH];
}
-NSString *hashOfTree(NSString *path)
+NSString *hashOfTreeWithVersion(NSString *path, uint16_t majorVersion)
{
const char *sourcePaths[] = {[path fileSystemRepresentation], 0};
FTS *fts = fts_open((char* const*)sourcePaths, FTS_PHYSICAL | FTS_NOCHDIR, compareFiles);
@@ -138,16 +156,34 @@ NSString *hashOfTree(NSString *path)
FTSENT *ent = 0;
while ((ent = fts_read(fts))) {
- if (ent->fts_info != FTS_F && ent->fts_info != FTS_SL)
+ if (ent->fts_info != FTS_F && ent->fts_info != FTS_SL && ent->fts_info != FTS_D)
+ continue;
+
+ if (ent->fts_info == FTS_D && !MAJOR_VERSION_IS_AT_LEAST(majorVersion, SUBeigeMajorVersion)) {
+ continue;
+ }
+
+ NSString *relativePath = pathRelativeToDirectory(path, stringWithFileSystemRepresentation(ent->fts_path));
+ if (relativePath.length == 0)
continue;
unsigned char fileHash[CC_SHA1_DIGEST_LENGTH];
- _hashOfFile(fileHash, ent);
+ if (!_hashOfFileContents(fileHash, ent)) {
+ return nil;
+ }
CC_SHA1_Update(&hashContext, fileHash, sizeof(fileHash));
- NSString *relativePath = pathRelativeToDirectory(path, stringWithFileSystemRepresentation(ent->fts_path));
const char *relativePathBytes = [relativePath fileSystemRepresentation];
CC_SHA1_Update(&hashContext, relativePathBytes, (CC_LONG)strlen(relativePathBytes));
+
+ if (MAJOR_VERSION_IS_AT_LEAST(majorVersion, SUBeigeMajorVersion)) {
+ uint16_t mode = ent->fts_statp->st_mode;
+ uint16_t type = ent->fts_info;
+ uint16_t permissions = mode & PERMISSION_FLAGS;
+
+ CC_SHA1_Update(&hashContext, &type, sizeof(type));
+ CC_SHA1_Update(&hashContext, &permissions, sizeof(permissions));
+ }
}
fts_close(fts);
@@ -162,12 +198,41 @@ NSString *hashOfTree(NSString *path)
return @(hexHash);
}
-void removeTree(NSString *path)
+extern NSString *hashOfTree(NSString *path)
{
- [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
+ return hashOfTreeWithVersion(path, LATEST_DELTA_DIFF_MAJOR_VERSION);
}
-void copyTree(NSString *source, NSString *dest)
+BOOL removeTree(NSString *path)
{
- [[NSFileManager defaultManager] copyItemAtPath:source toPath:dest error:nil];
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ // Don't use fileExistsForPath: because it will try to follow symbolic links
+ if (![fileManager attributesOfItemAtPath:path error:nil]) {
+ return YES;
+ }
+ return [fileManager removeItemAtPath:path error:nil];
+}
+
+BOOL copyTree(NSString *source, NSString *dest)
+{
+ return [[NSFileManager defaultManager] copyItemAtPath:source toPath:dest error:nil];
+}
+
+BOOL modifyPermissions(NSString *path, mode_t desiredPermissions)
+{
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ NSDictionary *attributes = [fileManager attributesOfItemAtPath:path error:nil];
+ if (!attributes) {
+ return NO;
+ }
+ NSNumber *permissions = attributes[NSFilePosixPermissions];
+ if (!permissions) {
+ return NO;
+ }
+ mode_t newMode = ([permissions unsignedShortValue] & ~PERMISSION_FLAGS) | desiredPermissions;
+ int (*changeModeFunc)(const char *, mode_t) = [attributes[NSFileType] isEqualToString:NSFileTypeSymbolicLink] ? lchmod : chmod;
+ if (changeModeFunc([path fileSystemRepresentation], newMode) != 0) {
+ return NO;
+ }
+ return YES;
}
diff --git a/Frameworks/Sparkle/Sparkle/SUBinaryDeltaCreate.h b/Frameworks/Sparkle/Sparkle/SUBinaryDeltaCreate.h
new file mode 100644
index 000000000..4afc22817
--- /dev/null
+++ b/Frameworks/Sparkle/Sparkle/SUBinaryDeltaCreate.h
@@ -0,0 +1,17 @@
+//
+// SUBinaryDeltaCreate.m
+// Sparkle
+//
+// Created by Mayur Pawashe on 4/9/15.
+// Copyright (c) 2015 Sparkle Project. All rights reserved.
+//
+
+#ifndef SUBINARYDELTACREATE_H
+#define SUBINARYDELTACREATE_H
+
+#import "SUBinaryDeltaCommon.h"
+
+@class NSString;
+int createBinaryDelta(NSString *source, NSString *destination, NSString *patchFile, SUBinaryDeltaMajorVersion majorVersion, BOOL verbose);
+
+#endif
diff --git a/Frameworks/Sparkle/Sparkle/SUBinaryDeltaCreate.m b/Frameworks/Sparkle/Sparkle/SUBinaryDeltaCreate.m
new file mode 100644
index 000000000..a477d6740
--- /dev/null
+++ b/Frameworks/Sparkle/Sparkle/SUBinaryDeltaCreate.m
@@ -0,0 +1,462 @@
+//
+// SUBinaryDeltaCreate.m
+// Sparkle
+//
+// Created by Mayur Pawashe on 4/9/15.
+// Copyright (c) 2015 Sparkle Project. All rights reserved.
+//
+
+#import "SUBinaryDeltaCreate.h"
+#import
+#include "SUBinaryDeltaCommon.h"
+#import
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+extern int bsdiff(int argc, const char **argv);
+
+@interface CreateBinaryDeltaOperation : NSOperation
+@property (copy) NSString *relativePath;
+@property (strong) NSString *resultPath;
+@property (strong) NSNumber *oldPermissions;
+@property (strong) NSNumber *permissions;
+@property (strong) NSString *_fromPath;
+@property (strong) NSString *_toPath;
+- (id)initWithRelativePath:(NSString *)relativePath oldTree:(NSString *)oldTree newTree:(NSString *)newTree oldPermissions:(NSNumber *)oldPermissions newPermissions:(NSNumber *)permissions;
+@end
+
+@implementation CreateBinaryDeltaOperation
+@synthesize relativePath = _relativePath;
+@synthesize resultPath = _resultPath;
+@synthesize oldPermissions = _oldPermissions;
+@synthesize permissions = _permissions;
+@synthesize _fromPath = _fromPath;
+@synthesize _toPath = _toPath;
+
+- (id)initWithRelativePath:(NSString *)relativePath oldTree:(NSString *)oldTree newTree:(NSString *)newTree oldPermissions:(NSNumber *)oldPermissions newPermissions:(NSNumber *)permissions
+{
+ if ((self = [super init])) {
+ self.relativePath = relativePath;
+ self.oldPermissions = oldPermissions;
+ self.permissions = permissions;
+ self._fromPath = [oldTree stringByAppendingPathComponent:relativePath];
+ self._toPath = [newTree stringByAppendingPathComponent:relativePath];
+ }
+ return self;
+}
+
+- (void)main
+{
+ NSString *temporaryFile = temporaryFilename(@"BinaryDelta");
+ const char *argv[] = {"/usr/bin/bsdiff", [self._fromPath fileSystemRepresentation], [self._toPath fileSystemRepresentation], [temporaryFile fileSystemRepresentation]};
+ int result = bsdiff(4, argv);
+ if (!result)
+ self.resultPath = temporaryFile;
+}
+
+@end
+
+#define INFO_HASH_KEY @"hash"
+#define INFO_TYPE_KEY @"type"
+#define INFO_PERMISSIONS_KEY @"permissions"
+#define INFO_SIZE_KEY @"size"
+
+static NSDictionary *infoForFile(FTSENT *ent)
+{
+ NSData *hash = hashOfFileContents(ent);
+ if (!hash) {
+ return nil;
+ }
+
+ off_t size = (ent->fts_info != FTS_D) ? ent->fts_statp->st_size : 0;
+
+ assert(ent->fts_statp != NULL);
+
+ mode_t permissions = ent->fts_statp->st_mode & PERMISSION_FLAGS;
+
+ return @{INFO_HASH_KEY: hash, INFO_TYPE_KEY: @(ent->fts_info), INFO_PERMISSIONS_KEY : @(permissions), INFO_SIZE_KEY: @(size)};
+}
+
+static bool aclExists(const FTSENT *ent)
+{
+ // OS X does not currently support ACLs for symlinks
+ if (ent->fts_info == FTS_SL) {
+ return NO;
+ }
+
+ acl_t acl = acl_get_link_np(ent->fts_path, ACL_TYPE_EXTENDED);
+ if (acl != NULL) {
+ acl_entry_t entry;
+ int result = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
+ assert(acl_free((void *)acl) == 0);
+ return (result == 0);
+ }
+ return false;
+}
+
+static NSString *absolutePath(NSString *path)
+{
+ NSURL *url = [[NSURL alloc] initFileURLWithPath:path];
+ return [[url absoluteURL] path];
+}
+
+static NSString *temporaryPatchFile(NSString *patchFile)
+{
+ NSString *path = absolutePath(patchFile);
+ NSString *directory = [path stringByDeletingLastPathComponent];
+ NSString *file = [path lastPathComponent];
+ return [NSString stringWithFormat:@"%@/.%@.tmp", directory, file];
+}
+
+#define MIN_FILE_SIZE_FOR_CREATING_DELTA 4096
+
+static BOOL shouldSkipDeltaCompression(NSDictionary* originalInfo, NSDictionary *newInfo)
+{
+ unsigned long long fileSize = [newInfo[INFO_SIZE_KEY] unsignedLongLongValue];
+ if (fileSize < MIN_FILE_SIZE_FOR_CREATING_DELTA) {
+ return YES;
+ }
+
+ if (!originalInfo) {
+ return YES;
+ }
+
+ if ([originalInfo[INFO_TYPE_KEY] unsignedShortValue] != [newInfo[INFO_TYPE_KEY] unsignedShortValue]) {
+ return YES;
+ }
+
+ if ([originalInfo[INFO_HASH_KEY] isEqual:newInfo[INFO_HASH_KEY]]) {
+ // this is possible if just the permissions have changed
+ return YES;
+ }
+
+ return NO;
+}
+
+static BOOL shouldDeleteThenExtract(NSDictionary* originalInfo, NSDictionary *newInfo)
+{
+ if (!originalInfo) {
+ return NO;
+ }
+
+ if ([originalInfo[INFO_TYPE_KEY] unsignedShortValue] != [newInfo[INFO_TYPE_KEY] unsignedShortValue]) {
+ return YES;
+ }
+
+ return NO;
+}
+
+static BOOL shouldSkipExtracting(NSDictionary *originalInfo, NSDictionary *newInfo)
+{
+ if (!originalInfo) {
+ return NO;
+ }
+
+ if ([originalInfo[INFO_TYPE_KEY] unsignedShortValue] != [newInfo[INFO_TYPE_KEY] unsignedShortValue]) {
+ return NO;
+ }
+
+ if (![originalInfo[INFO_HASH_KEY] isEqual:newInfo[INFO_HASH_KEY]]) {
+ return NO;
+ }
+
+ return YES;
+}
+
+static BOOL shouldChangePermissions(NSDictionary *originalInfo, NSDictionary *newInfo)
+{
+ if (!originalInfo) {
+ return NO;
+ }
+
+ if ([originalInfo[INFO_TYPE_KEY] unsignedShortValue] != [newInfo[INFO_TYPE_KEY] unsignedShortValue]) {
+ return NO;
+ }
+
+ if ([originalInfo[INFO_PERMISSIONS_KEY] unsignedShortValue] == [newInfo[INFO_PERMISSIONS_KEY] unsignedShortValue]) {
+ return NO;
+ }
+
+ return YES;
+}
+
+int createBinaryDelta(NSString *source, NSString *destination, NSString *patchFile, SUBinaryDeltaMajorVersion majorVersion, BOOL verbose)
+{
+ if (majorVersion < FIRST_DELTA_DIFF_MAJOR_VERSION) {
+ fprintf(stderr, "Version provided (%u) is not valid\n", majorVersion);
+ return 1;
+ }
+
+ if (majorVersion > LATEST_DELTA_DIFF_MAJOR_VERSION) {
+ fprintf(stderr, "This program is too old to create a version %u patch, or the version number provided is invalid\n", majorVersion);
+ return 1;
+ }
+
+ SUBinaryDeltaMinorVersion minorVersion = latestMinorVersionForMajorVersion(majorVersion);
+
+ NSMutableDictionary *originalTreeState = [NSMutableDictionary dictionary];
+
+ const char *sourcePaths[] = {[source fileSystemRepresentation], 0};
+ FTS *fts = fts_open((char* const*)sourcePaths, FTS_PHYSICAL | FTS_NOCHDIR, compareFiles);
+ if (!fts) {
+ perror("fts_open");
+ return 1;
+ }
+
+ if (verbose) {
+ fprintf(stderr, "Creating version %u.%u patch...\n", majorVersion, minorVersion);
+ fprintf(stderr, "Processing %s...", [source fileSystemRepresentation]);
+ }
+
+ FTSENT *ent = 0;
+ while ((ent = fts_read(fts))) {
+ if (ent->fts_info != FTS_F && ent->fts_info != FTS_SL && ent->fts_info != FTS_D) {
+ continue;
+ }
+
+ NSString *key = pathRelativeToDirectory(source, stringWithFileSystemRepresentation(ent->fts_path));
+ if (![key length]) {
+ continue;
+ }
+
+ NSDictionary *info = infoForFile(ent);
+ if (!info) {
+ fprintf(stderr, "\nFailed to retrieve info for file %s\n", ent->fts_path);
+ return 1;
+ }
+ originalTreeState[key] = info;
+
+ if (aclExists(ent)) {
+ fprintf(stderr, "\nDiffing ACLs are not supported. Detected ACL in before-tree on file %s\n", ent->fts_path);
+ return 1;
+ }
+ }
+ fts_close(fts);
+
+ NSString *beforeHash = hashOfTreeWithVersion(source, majorVersion);
+
+ if (!beforeHash) {
+ fprintf(stderr, "\nFailed to generate hash for tree %s\n", [source fileSystemRepresentation]);
+ return 1;
+ }
+
+ NSMutableDictionary *newTreeState = [NSMutableDictionary dictionary];
+ for (NSString *key in originalTreeState)
+ {
+ newTreeState[key] = [NSNull null];
+ }
+
+ if (verbose) {
+ fprintf(stderr, "\nProcessing %s...", [destination fileSystemRepresentation]);
+ }
+
+ sourcePaths[0] = [destination fileSystemRepresentation];
+ fts = fts_open((char* const*)sourcePaths, FTS_PHYSICAL | FTS_NOCHDIR, compareFiles);
+ if (!fts) {
+ perror("fts_open");
+ return 1;
+ }
+
+
+ while ((ent = fts_read(fts))) {
+ if (ent->fts_info != FTS_F && ent->fts_info != FTS_SL && ent->fts_info != FTS_D) {
+ continue;
+ }
+
+ NSString *key = pathRelativeToDirectory(destination, stringWithFileSystemRepresentation(ent->fts_path));
+ if (![key length]) {
+ continue;
+ }
+
+ NSDictionary *info = infoForFile(ent);
+ if (!info) {
+ fprintf(stderr, "\nFailed to retrieve info from file %s\n", ent->fts_path);
+ return 1;
+ }
+
+ // We should validate permissions and ACLs even if we don't store the info in the diff in the case of ACLs,
+ // or in the case of permissions if the patch version is 1
+
+ mode_t permissions = [info[INFO_PERMISSIONS_KEY] unsignedShortValue];
+ if (!IS_VALID_PERMISSIONS(permissions)) {
+ fprintf(stderr, "\nInvalid file permissions after-tree on file %s\nOnly permissions with modes 0755 and 0644 are supported\n", ent->fts_path);
+ return 1;
+ }
+
+ if (aclExists(ent)) {
+ fprintf(stderr, "\nDiffing ACLs are not supported. Detected ACL in after-tree on file %s\n", ent->fts_path);
+ return 1;
+ }
+
+ NSDictionary *oldInfo = originalTreeState[key];
+
+ if ([info isEqual:oldInfo]) {
+ [newTreeState removeObjectForKey:key];
+ } else {
+ newTreeState[key] = info;
+
+ if (oldInfo && [oldInfo[INFO_TYPE_KEY] unsignedShortValue] == FTS_D && [info[INFO_TYPE_KEY] unsignedShortValue] != FTS_D) {
+ NSArray *parentPathComponents = key.pathComponents;
+
+ for (NSString *childPath in originalTreeState) {
+ NSArray *childPathComponents = childPath.pathComponents;
+ if (childPathComponents.count > parentPathComponents.count &&
+ [parentPathComponents isEqualToArray:[childPathComponents subarrayWithRange:NSMakeRange(0, parentPathComponents.count)]]) {
+ [newTreeState removeObjectForKey:childPath];
+ }
+ }
+ }
+ }
+ }
+ fts_close(fts);
+
+ NSString *afterHash = hashOfTreeWithVersion(destination, majorVersion);
+ if (!afterHash) {
+ fprintf(stderr, "\nFailed to generate hash for tree %s\n", [destination fileSystemRepresentation]);
+ return 1;
+ }
+
+ if (verbose) {
+ fprintf(stderr, "\nGenerating delta...");
+ }
+
+ NSString *temporaryFile = temporaryPatchFile(patchFile);
+ xar_t x = xar_open([temporaryFile fileSystemRepresentation], WRITE);
+ xar_opt_set(x, XAR_OPT_COMPRESSION, "bzip2");
+
+ xar_subdoc_t attributes = xar_subdoc_new(x, BINARY_DELTA_ATTRIBUTES_KEY);
+
+ xar_subdoc_prop_set(attributes, MAJOR_DIFF_VERSION_KEY, [[NSString stringWithFormat:@"%u", majorVersion] UTF8String]);
+ xar_subdoc_prop_set(attributes, MINOR_DIFF_VERSION_KEY, [[NSString stringWithFormat:@"%u", minorVersion] UTF8String]);
+
+ // Version 1 patches don't have a major or minor version field, so we need to differentiate between the hash keys
+ const char *beforeHashKey =
+ MAJOR_VERSION_IS_AT_LEAST(majorVersion, SUBeigeMajorVersion) ? BEFORE_TREE_SHA1_KEY : BEFORE_TREE_SHA1_OLD_KEY;
+ const char *afterHashKey =
+ MAJOR_VERSION_IS_AT_LEAST(majorVersion, SUBeigeMajorVersion) ? AFTER_TREE_SHA1_KEY : AFTER_TREE_SHA1_OLD_KEY;
+
+ xar_subdoc_prop_set(attributes, beforeHashKey, [beforeHash UTF8String]);
+ xar_subdoc_prop_set(attributes, afterHashKey, [afterHash UTF8String]);
+
+ NSOperationQueue *deltaQueue = [[NSOperationQueue alloc] init];
+ NSMutableArray *deltaOperations = [NSMutableArray array];
+
+ // Sort the keys by preferring the ones from the original tree to appear first
+ // We want to enforce deleting before extracting in the case paths differ only by case
+ NSArray *keys = [[newTreeState allKeys] sortedArrayUsingComparator:^NSComparisonResult(NSString *key1, NSString *key2) {
+ NSComparisonResult insensitiveCompareResult = [key1 caseInsensitiveCompare:key2];
+ if (insensitiveCompareResult != NSOrderedSame) {
+ return insensitiveCompareResult;
+ }
+
+ return originalTreeState[key1] ? NSOrderedAscending : NSOrderedDescending;
+ }];
+ for (NSString* key in keys) {
+ id value = [newTreeState valueForKey:key];
+
+ if ([value isEqual:[NSNull null]]) {
+ xar_file_t newFile = xar_add_frombuffer(x, 0, [key fileSystemRepresentation], (char *)"", 1);
+ assert(newFile);
+ xar_prop_set(newFile, DELETE_KEY, "true");
+
+ if (verbose) {
+ fprintf(stderr, "\n❌ %s %s", VERBOSE_REMOVED, [key fileSystemRepresentation]);
+ }
+ continue;
+ }
+
+ NSDictionary *originalInfo = originalTreeState[key];
+ NSDictionary *newInfo = newTreeState[key];
+ if (shouldSkipDeltaCompression(originalInfo, newInfo)) {
+ if (MAJOR_VERSION_IS_AT_LEAST(majorVersion, SUBeigeMajorVersion) && shouldSkipExtracting(originalInfo, newInfo)) {
+ if (shouldChangePermissions(originalInfo, newInfo)) {
+ xar_file_t newFile = xar_add_frombuffer(x, 0, [key fileSystemRepresentation], (char *)"", 1);
+ assert(newFile);
+ xar_prop_set(newFile, MODIFY_PERMISSIONS_KEY, [[NSString stringWithFormat:@"%u", [newInfo[INFO_PERMISSIONS_KEY] unsignedShortValue]] UTF8String]);
+
+ if (verbose) {
+ fprintf(stderr, "\n👮 %s %s (0%o -> 0%o)", VERBOSE_MODIFIED, [key fileSystemRepresentation], [originalInfo[INFO_PERMISSIONS_KEY] unsignedShortValue], [newInfo[INFO_PERMISSIONS_KEY] unsignedShortValue]);
+ }
+ }
+ } else {
+ NSString *path = [destination stringByAppendingPathComponent:key];
+ xar_file_t newFile = xar_add_frompath(x, 0, [key fileSystemRepresentation], [path fileSystemRepresentation]);
+ assert(newFile);
+
+ if (shouldDeleteThenExtract(originalInfo, newInfo)) {
+ if (MAJOR_VERSION_IS_AT_LEAST(majorVersion, SUBeigeMajorVersion)) {
+ xar_prop_set(newFile, DELETE_KEY, "true");
+ } else {
+ xar_prop_set(newFile, DELETE_THEN_EXTRACT_OLD_KEY, "true");
+ }
+ }
+
+ if (MAJOR_VERSION_IS_AT_LEAST(majorVersion, SUBeigeMajorVersion)) {
+ xar_prop_set(newFile, EXTRACT_KEY, "true");
+ }
+
+ if (verbose) {
+ if (originalInfo) {
+ fprintf(stderr, "\n✏️ %s %s", VERBOSE_UPDATED, [key fileSystemRepresentation]);
+ } else {
+ fprintf(stderr, "\n✅ %s %s", VERBOSE_ADDED, [key fileSystemRepresentation]);
+ }
+ }
+ }
+ } else {
+ NSNumber *permissions =
+ (MAJOR_VERSION_IS_AT_LEAST(majorVersion, SUBeigeMajorVersion) && shouldChangePermissions(originalInfo, newInfo)) ?
+ newInfo[INFO_PERMISSIONS_KEY] :
+ nil;
+ CreateBinaryDeltaOperation *operation = [[CreateBinaryDeltaOperation alloc] initWithRelativePath:key oldTree:source newTree:destination oldPermissions:originalInfo[INFO_PERMISSIONS_KEY] newPermissions:permissions];
+ [deltaQueue addOperation:operation];
+ [deltaOperations addObject:operation];
+ }
+ }
+
+ [deltaQueue waitUntilAllOperationsAreFinished];
+
+ for (CreateBinaryDeltaOperation *operation in deltaOperations) {
+ NSString *resultPath = [operation resultPath];
+ if (!resultPath) {
+ fprintf(stderr, "\nFailed to create patch from source %s and destination %s\n", [[operation relativePath] fileSystemRepresentation], [resultPath fileSystemRepresentation]);
+ return 1;
+ }
+
+ if (verbose) {
+ fprintf(stderr, "\n🔨 %s %s", VERBOSE_DIFFED, [[operation relativePath] fileSystemRepresentation]);
+ }
+
+ xar_file_t newFile = xar_add_frompath(x, 0, [[operation relativePath] fileSystemRepresentation], [resultPath fileSystemRepresentation]);
+ assert(newFile);
+ xar_prop_set(newFile, BINARY_DELTA_KEY, "true");
+ unlink([resultPath fileSystemRepresentation]);
+
+ if (operation.permissions) {
+ xar_prop_set(newFile, MODIFY_PERMISSIONS_KEY, [[NSString stringWithFormat:@"%u", [operation.permissions unsignedShortValue]] UTF8String]);
+
+ if (verbose) {
+ fprintf(stderr, "\n👮 %s %s (0%o -> 0%o)", VERBOSE_MODIFIED, [[operation relativePath] fileSystemRepresentation], operation.oldPermissions.unsignedShortValue, operation.permissions.unsignedShortValue);
+ }
+ }
+ }
+
+ xar_close(x);
+
+ unlink([patchFile fileSystemRepresentation]);
+ link([temporaryFile fileSystemRepresentation], [patchFile fileSystemRepresentation]);
+ unlink([temporaryFile fileSystemRepresentation]);
+
+ if (verbose) {
+ fprintf(stderr, "\nDone!\n");
+ }
+
+ return 0;
+}
diff --git a/Frameworks/Sparkle/Sparkle/SUBinaryDeltaTool.m b/Frameworks/Sparkle/Sparkle/SUBinaryDeltaTool.m
index a09d8c6d5..804663dd8 100644
--- a/Frameworks/Sparkle/Sparkle/SUBinaryDeltaTool.m
+++ b/Frameworks/Sparkle/Sparkle/SUBinaryDeltaTool.m
@@ -6,275 +6,213 @@
// Copyright 2009 Mark Rowe. All rights reserved.
//
-#define _DARWIN_NO_64_BIT_INODE 1
-
-#include "SUBinaryDeltaCommon.h"
#include "SUBinaryDeltaApply.h"
-#include
+#include "SUBinaryDeltaCreate.h"
+#import "SUBinaryDeltaCommon.h"
#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
#include
-extern int bsdiff(int argc, const char **argv);
+#define VERBOSE_FLAG @"--verbose"
+#define VERSION_FLAG @"--version"
-@interface CreateBinaryDeltaOperation : NSOperation
-@property (copy) NSString *relativePath;
-@property (strong) NSString *resultPath;
-@property (strong) NSString *_fromPath;
-@property (strong) NSString *_toPath;
-- (id)initWithRelativePath:(NSString *)relativePath oldTree:(NSString *)oldTree newTree:(NSString *)newTree;
-@end
+#define CREATE_COMMAND @"create"
+#define APPLY_COMMAND @"apply"
+#define VERSION_COMMAND @"version"
+#define VERSION_ALTERNATE_COMMAND @"--version"
-@implementation CreateBinaryDeltaOperation
-@synthesize relativePath = _relativePath;
-@synthesize resultPath = _resultPath;
-@synthesize _fromPath = _fromPath;
-@synthesize _toPath = _toPath;
-
-- (id)initWithRelativePath:(NSString *)relativePath oldTree:(NSString *)oldTree newTree:(NSString *)newTree
+static void printUsage(NSString *programName)
{
- if ((self = [super init])) {
- self.relativePath = relativePath;
- self._fromPath = [oldTree stringByAppendingPathComponent:relativePath];
- self._toPath = [newTree stringByAppendingPathComponent:relativePath];
+ fprintf(stderr, "Usage:\n");
+ fprintf(stderr, "%s create [--verbose] [--version=] \n", [programName UTF8String]);
+ fprintf(stderr, "%s apply [--verbose] \n", [programName UTF8String]);
+ fprintf(stderr, "%s version []\n", [programName UTF8String]);
+}
+
+static int runCreateCommand(NSString *programName, NSArray *args)
+{
+ if (args.count < 3 || args.count > 5) {
+ printUsage(programName);
+ return 1;
}
- return self;
+
+ NSUInteger numberOflagsFound = 0;
+ NSUInteger verboseIndex = [args indexOfObject:VERBOSE_FLAG];
+ NSUInteger versionIndex = NSNotFound;
+ for (NSUInteger argumentIndex = 0; argumentIndex < args.count; ++argumentIndex) {
+ if ([args[argumentIndex] hasPrefix:VERSION_FLAG]) {
+ versionIndex = argumentIndex;
+ break;
+ }
+ }
+
+ if (verboseIndex != NSNotFound) {
+ ++numberOflagsFound;
+ }
+ if (versionIndex != NSNotFound) {
+ ++numberOflagsFound;
+ }
+
+ if (args.count - numberOflagsFound < 3) {
+ printUsage(programName);
+ return 1;
+ }
+
+ BOOL verbose = (verboseIndex != NSNotFound);
+ NSString *versionField = (versionIndex != NSNotFound) ? args[versionIndex] : nil;
+
+ NSArray *versionComponents = nil;
+ if (versionField) {
+ versionComponents = [versionField componentsSeparatedByString:@"="];
+ if (versionComponents.count != 2) {
+ printUsage(programName);
+ return 1;
+ }
+ }
+
+ SUBinaryDeltaMajorVersion patchVersion =
+ !versionComponents ?
+ LATEST_DELTA_DIFF_MAJOR_VERSION :
+ (SUBinaryDeltaMajorVersion)[[versionComponents[1] componentsSeparatedByString:@"."][0] intValue]; // ignore minor version if provided
+
+ NSMutableArray *fileArgs = [NSMutableArray array];
+ for (NSString *argument in args) {
+ if (![argument hasPrefix:VERSION_FLAG] && ![argument isEqualToString:VERBOSE_FLAG]) {
+ [fileArgs addObject:argument];
+ }
+ }
+
+ if (fileArgs.count != 3) {
+ printUsage(programName);
+ return 1;
+ }
+
+ BOOL isDirectory;
+ if (![[NSFileManager defaultManager] fileExistsAtPath:fileArgs[0] isDirectory:&isDirectory] || !isDirectory) {
+ printUsage(programName);
+ fprintf(stderr, "Error: before-tree must be a directory\n");
+ return 1;
+ }
+
+ if (![[NSFileManager defaultManager] fileExistsAtPath:fileArgs[1] isDirectory:&isDirectory] || !isDirectory) {
+ printUsage(programName);
+ fprintf(stderr, "Error: after-tree must be a directory\n");
+ return 1;
+ }
+
+ return createBinaryDelta(fileArgs[0], fileArgs[1], fileArgs[2], patchVersion, verbose);
}
-- (void)main
+static int runApplyCommand(NSString *programName, NSArray *args)
{
- NSString *temporaryFile = temporaryFilename(@"BinaryDelta");
- const char *argv[] = {"/usr/bin/bsdiff", [self._fromPath fileSystemRepresentation], [self._toPath fileSystemRepresentation], [temporaryFile fileSystemRepresentation]};
- int result = bsdiff(4, argv);
- if (!result)
- self.resultPath = temporaryFile;
+ if (args.count < 3 || args.count > 4) {
+ printUsage(programName);
+ return 1;
+ }
+
+ BOOL verbose = [args containsObject:VERBOSE_FLAG];
+
+ if (args.count == 4 && !verbose) {
+ printUsage(programName);
+ return 1;
+ }
+
+ NSMutableArray *fileArgs = [NSMutableArray array];
+ for (NSString *argument in args) {
+ if (![argument isEqualToString:VERBOSE_FLAG]) {
+ [fileArgs addObject:argument];
+ }
+ }
+
+ if (fileArgs.count != 3) {
+ printUsage(programName);
+ return 1;
+ }
+
+ BOOL isDirectory;
+ if (![[NSFileManager defaultManager] fileExistsAtPath:fileArgs[0] isDirectory:&isDirectory] || !isDirectory) {
+ printUsage(programName);
+ fprintf(stderr, "Error: before-tree must be a directory\n");
+ return 1;
+ }
+
+ if (![[NSFileManager defaultManager] fileExistsAtPath:fileArgs[2] isDirectory:&isDirectory] || isDirectory) {
+ printUsage(programName);
+ fprintf(stderr, "Error: patch-file must be a file %d\n", isDirectory);
+ return 1;
+ }
+
+ return applyBinaryDelta(fileArgs[0], fileArgs[1], fileArgs[2], verbose);
}
-@end
-
-static NSDictionary *infoForFile(FTSENT *ent)
+static int runVersionCommand(NSString *programName, NSArray *args)
{
- NSData *hash = hashOfFile(ent);
- NSNumber *size = @0;
- if (ent->fts_info != FTS_D) {
- size = @(ent->fts_statp->st_size);
- }
- return @{@"hash": hash, @"type": @(ent->fts_info), @"size": size};
-}
-
-static NSString *absolutePath(NSString *path)
-{
- NSURL *url = [[NSURL alloc] initFileURLWithPath:path];
- return [[url absoluteURL] path];
-}
-
-static NSString *temporaryPatchFile(NSString *patchFile)
-{
- NSString *path = absolutePath(patchFile);
- NSString *directory = [path stringByDeletingLastPathComponent];
- NSString *file = [path lastPathComponent];
- return [NSString stringWithFormat:@"%@/.%@.tmp", directory, file];
-}
-
-static BOOL shouldSkipDeltaCompression(NSString * __unused key, NSDictionary* originalInfo, NSDictionary *newInfo)
-{
- unsigned long long fileSize = [newInfo[@"size"] unsignedLongLongValue];
- if (fileSize < 4096) {
- return YES;
- }
-
- if (!originalInfo) {
- return YES;
- }
-
- if ([originalInfo[@"type"] unsignedShortValue] != [newInfo[@"type"] unsignedShortValue]) {
- return YES;
- }
-
- return NO;
-}
-
-static BOOL shouldDeleteThenExtract(NSString * __unused key, NSDictionary* originalInfo, NSDictionary *newInfo)
-{
- if (!originalInfo) {
- return NO;
- }
-
- if ([originalInfo[@"type"] unsignedShortValue] != [newInfo[@"type"] unsignedShortValue]) {
- return YES;
- }
-
- return NO;
+ if (args.count > 1) {
+ printUsage(programName);
+ return 1;
+ }
+
+ if (args.count == 0) {
+ fprintf(stdout, "%u.%u\n", LATEST_DELTA_DIFF_MAJOR_VERSION, latestMinorVersionForMajorVersion(LATEST_DELTA_DIFF_MAJOR_VERSION));
+ } else {
+ NSString *patchFile = args[0];
+ xar_t x = xar_open([patchFile fileSystemRepresentation], READ);
+ if (!x) {
+ fprintf(stderr, "Unable to open patch %s\n", [patchFile fileSystemRepresentation]);
+ return 1;
+ }
+
+ SUBinaryDeltaMajorVersion majorDiffVersion = FIRST_DELTA_DIFF_MAJOR_VERSION;
+ SUBinaryDeltaMinorVersion minorDiffVersion = FIRST_DELTA_DIFF_MINOR_VERSION;
+
+ xar_subdoc_t subdoc;
+ for (subdoc = xar_subdoc_first(x); subdoc; subdoc = xar_subdoc_next(subdoc)) {
+ if (!strcmp(xar_subdoc_name(subdoc), BINARY_DELTA_ATTRIBUTES_KEY)) {
+ const char *value = 0;
+
+ // available in version 2.0 or later
+ xar_subdoc_prop_get(subdoc, MAJOR_DIFF_VERSION_KEY, &value);
+ if (value)
+ majorDiffVersion = (SUBinaryDeltaMajorVersion)[@(value) intValue];
+
+ // available in version 2.0 or later
+ xar_subdoc_prop_get(subdoc, MINOR_DIFF_VERSION_KEY, &value);
+ if (value)
+ minorDiffVersion = (SUBinaryDeltaMinorVersion)[@(value) intValue];
+ }
+ }
+
+ fprintf(stdout, "%u.%u\n", majorDiffVersion, minorDiffVersion);
+ }
+
+ return 0;
}
int main(int __unused argc, char __unused *argv[])
{
@autoreleasepool {
NSArray *args = [[NSProcessInfo processInfo] arguments];
- if (args.count != 5) {
- usage:
- fprintf(stderr, "Usage: BinaryDelta [create | apply] before-tree after-tree patch-file\n");
+ NSString *programName = [args[0] lastPathComponent];
+
+ if (args.count < 2) {
+ printUsage(programName);
return 1;
}
NSString *command = args[1];
- NSString *oldPath = args[2];
- NSString *newPath = args[3];
- NSString *patchFile = args[4];
-
- BOOL isDirectory;
- [[NSFileManager defaultManager] fileExistsAtPath:oldPath isDirectory:&isDirectory];
- if (!isDirectory) {
- fprintf(stderr, "Usage: before-tree must be a directory\n");
- return 1;
+ NSArray *commandArguments = [args subarrayWithRange:NSMakeRange(2, args.count - 2)];
+
+ int result;
+ if ([command isEqualToString:CREATE_COMMAND]) {
+ result = runCreateCommand(programName, commandArguments);
+ } else if ([command isEqualToString:APPLY_COMMAND]) {
+ result = runApplyCommand(programName, commandArguments);
+ } else if ([command isEqualToString:VERSION_COMMAND] || [command isEqualToString:VERSION_ALTERNATE_COMMAND]) {
+ result = runVersionCommand(programName, commandArguments);
+ } else {
+ result = 1;
+ printUsage(programName);
}
-
- [[NSFileManager defaultManager] fileExistsAtPath:newPath isDirectory:&isDirectory];
- if (!isDirectory) {
- fprintf(stderr, "Usage: after-tree must be a directory\n");
- return 1;
- }
-
- if ([command isEqualToString:@"apply"]) {
- int result = applyBinaryDelta(oldPath, newPath, patchFile);
- return result;
- }
- if (![command isEqualToString:@"create"]) {
- goto usage;
- }
-
- NSMutableDictionary *originalTreeState = [NSMutableDictionary dictionary];
-
- const char *sourcePaths[] = {[oldPath fileSystemRepresentation], 0};
- FTS *fts = fts_open((char* const*)sourcePaths, FTS_PHYSICAL | FTS_NOCHDIR, compareFiles);
- if (!fts) {
- perror("fts_open");
- return 1;
- }
-
- fprintf(stderr, "Processing %s...", [oldPath UTF8String]);
- FTSENT *ent = 0;
- while ((ent = fts_read(fts))) {
- if (ent->fts_info != FTS_F && ent->fts_info != FTS_SL && ent->fts_info != FTS_D) {
- continue;
- }
-
- NSString *key = pathRelativeToDirectory(oldPath, stringWithFileSystemRepresentation(ent->fts_path));
- if (![key length]) {
- continue;
- }
-
- NSDictionary *info = infoForFile(ent);
- originalTreeState[key] = info;
- }
- fts_close(fts);
-
- NSString *beforeHash = hashOfTree(oldPath);
-
- NSMutableDictionary *newTreeState = [NSMutableDictionary dictionary];
- for (NSString *key in originalTreeState)
- {
- newTreeState[key] = [NSNull null];
- }
-
- fprintf(stderr, "\nProcessing %s... ", [newPath UTF8String]);
- sourcePaths[0] = [newPath fileSystemRepresentation];
- fts = fts_open((char* const*)sourcePaths, FTS_PHYSICAL | FTS_NOCHDIR, compareFiles);
- if (!fts) {
- perror("fts_open");
- return 1;
- }
-
-
- while ((ent = fts_read(fts))) {
- if (ent->fts_info != FTS_F && ent->fts_info != FTS_SL && ent->fts_info != FTS_D) {
- continue;
- }
-
- NSString *key = pathRelativeToDirectory(newPath, stringWithFileSystemRepresentation(ent->fts_path));
- if (![key length]) {
- continue;
- }
-
- NSDictionary *info = infoForFile(ent);
- NSDictionary *oldInfo = originalTreeState[key];
-
- if ([info isEqual:oldInfo])
- [newTreeState removeObjectForKey:key];
- else
- newTreeState[key] = info;
- }
- fts_close(fts);
-
- NSString *afterHash = hashOfTree(newPath);
-
- fprintf(stderr, "\nGenerating delta... ");
-
- NSString *temporaryFile = temporaryPatchFile(patchFile);
- xar_t x = xar_open([temporaryFile fileSystemRepresentation], WRITE);
- xar_opt_set(x, XAR_OPT_COMPRESSION, "bzip2");
- xar_subdoc_t attributes = xar_subdoc_new(x, "binary-delta-attributes");
- xar_subdoc_prop_set(attributes, "before-sha1", [beforeHash UTF8String]);
- xar_subdoc_prop_set(attributes, "after-sha1", [afterHash UTF8String]);
-
- NSOperationQueue *deltaQueue = [[NSOperationQueue alloc] init];
- NSMutableArray *deltaOperations = [NSMutableArray array];
-
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wselector"
- // Xcode 5.1.1: compare: is clearly declared, must warn due to a compiler bug?
- NSArray *keys = [[newTreeState allKeys] sortedArrayUsingSelector:@selector(compare:)];
-#pragma clang diagnostic pop
- for (NSString* key in keys) {
- id value = [newTreeState valueForKey:key];
-
- if ([value isEqual:[NSNull null]]) {
- xar_file_t newFile = xar_add_frombuffer(x, 0, [key fileSystemRepresentation], (char *)"", 1);
- assert(newFile);
- xar_prop_set(newFile, "delete", "true");
- continue;
- }
-
- NSDictionary *originalInfo = originalTreeState[key];
- NSDictionary *newInfo = newTreeState[key];
- if (shouldSkipDeltaCompression(key, originalInfo, newInfo)) {
- NSString *path = [newPath stringByAppendingPathComponent:key];
- xar_file_t newFile = xar_add_frompath(x, 0, [key fileSystemRepresentation], [path fileSystemRepresentation]);
- assert(newFile);
- if (shouldDeleteThenExtract(key, originalInfo, newInfo)) {
- xar_prop_set(newFile, "delete-then-extract", "true");
- }
- } else {
- CreateBinaryDeltaOperation *operation = [[CreateBinaryDeltaOperation alloc] initWithRelativePath:key oldTree:oldPath newTree:newPath];
- [deltaQueue addOperation:operation];
- [deltaOperations addObject:operation];
- }
- }
-
- [deltaQueue waitUntilAllOperationsAreFinished];
-
- for (CreateBinaryDeltaOperation *operation in deltaOperations) {
- NSString *resultPath = [operation resultPath];
- xar_file_t newFile = xar_add_frompath(x, 0, [[operation relativePath] fileSystemRepresentation], [resultPath fileSystemRepresentation]);
- assert(newFile);
- xar_prop_set(newFile, "binary-delta", "true");
- unlink([resultPath fileSystemRepresentation]);
- }
-
- xar_close(x);
-
- unlink([patchFile fileSystemRepresentation]);
- link([temporaryFile fileSystemRepresentation], [patchFile fileSystemRepresentation]);
- unlink([temporaryFile fileSystemRepresentation]);
- fprintf(stderr, "Done!\n");
-
- return 0;
+
+ return result;
}
}
diff --git a/Frameworks/Sparkle/Sparkle/SUBinaryDeltaUnarchiver.m b/Frameworks/Sparkle/Sparkle/SUBinaryDeltaUnarchiver.m
index df8b67ef2..0c238df7f 100644
--- a/Frameworks/Sparkle/Sparkle/SUBinaryDeltaUnarchiver.m
+++ b/Frameworks/Sparkle/Sparkle/SUBinaryDeltaUnarchiver.m
@@ -17,16 +17,16 @@
+ (BOOL)canUnarchivePath:(NSString *)path
{
- return binaryDeltaSupported() && [[path pathExtension] isEqualToString:@"delta"];
+ return [[path pathExtension] isEqualToString:@"delta"];
}
- (void)applyBinaryDelta
{
@autoreleasepool {
- NSString *sourcePath = [[self.updateHost bundle] bundlePath];
+ NSString *sourcePath = self.updateHostBundlePath;
NSString *targetPath = [[self.archivePath stringByDeletingLastPathComponent] stringByAppendingPathComponent:[sourcePath lastPathComponent]];
- int result = applyBinaryDelta(sourcePath, targetPath, self.archivePath);
+ int result = applyBinaryDelta(sourcePath, targetPath, self.archivePath, NO);
if (!result) {
dispatch_async(dispatch_get_main_queue(), ^{
[self notifyDelegateOfSuccess];
diff --git a/Frameworks/Sparkle/Sparkle/SUCodeSigningVerifier.h b/Frameworks/Sparkle/Sparkle/SUCodeSigningVerifier.h
index 1f01d1b55..eddd1b261 100644
--- a/Frameworks/Sparkle/Sparkle/SUCodeSigningVerifier.h
+++ b/Frameworks/Sparkle/Sparkle/SUCodeSigningVerifier.h
@@ -12,8 +12,10 @@
#import
@interface SUCodeSigningVerifier : NSObject
-+ (BOOL)codeSignatureIsValidAtPath:(NSString *)destinationPath error:(NSError **)error;
++ (BOOL)codeSignatureMatchesHostAndIsValidAtPath:(NSString *)applicationPath error:(NSError **)error;
++ (BOOL)codeSignatureIsValidAtPath:(NSString *)applicationPath error:(NSError **)error;
+ (BOOL)hostApplicationIsCodeSigned;
++ (BOOL)applicationAtPathIsCodeSigned:(NSString *)applicationPath;
@end
#endif
diff --git a/Frameworks/Sparkle/Sparkle/SUCodeSigningVerifier.m b/Frameworks/Sparkle/Sparkle/SUCodeSigningVerifier.m
index 692865928..73efc6c11 100644
--- a/Frameworks/Sparkle/Sparkle/SUCodeSigningVerifier.m
+++ b/Frameworks/Sparkle/Sparkle/SUCodeSigningVerifier.m
@@ -13,7 +13,7 @@
@implementation SUCodeSigningVerifier
-+ (BOOL)codeSignatureIsValidAtPath:(NSString *)destinationPath error:(NSError *__autoreleasing *)error
++ (BOOL)codeSignatureMatchesHostAndIsValidAtPath:(NSString *)applicationPath error:(NSError *__autoreleasing *)error
{
OSStatus result;
SecRequirementRef requirement = NULL;
@@ -37,20 +37,23 @@
goto finally;
}
- newBundle = [NSBundle bundleWithPath:destinationPath];
+ newBundle = [NSBundle bundleWithPath:applicationPath];
if (!newBundle) {
SULog(@"Failed to load NSBundle for update");
result = -1;
goto finally;
}
- result = SecStaticCodeCreateWithPath((__bridge CFURLRef)[newBundle executableURL], kSecCSDefaultFlags, &staticCode);
+ result = SecStaticCodeCreateWithPath((__bridge CFURLRef)[newBundle bundleURL], kSecCSDefaultFlags, &staticCode);
if (result != noErr) {
SULog(@"Failed to get static code %d", result);
goto finally;
}
- result = SecStaticCodeCheckValidityWithErrors(staticCode, kSecCSDefaultFlags | kSecCSCheckAllArchitectures, requirement, &cfError);
+ // Note that kSecCSCheckNestedCode may not work with pre-Mavericks code signing.
+ // See https://github.com/sparkle-project/Sparkle/issues/376#issuecomment-48824267 and https://developer.apple.com/library/mac/technotes/tn2206
+ SecCSFlags flags = kSecCSDefaultFlags | kSecCSCheckAllArchitectures;
+ result = SecStaticCodeCheckValidityWithErrors(staticCode, flags, requirement, &cfError);
if (cfError) {
NSError *tmpError = CFBridgingRelease(cfError);
@@ -80,6 +83,53 @@ finally:
return (result == noErr);
}
++ (BOOL)codeSignatureIsValidAtPath:(NSString *)applicationPath error:(NSError *__autoreleasing *)error
+{
+ OSStatus result;
+ SecStaticCodeRef staticCode = NULL;
+ NSBundle *newBundle;
+ CFErrorRef cfError = NULL;
+ if (error) {
+ *error = nil;
+ }
+
+ newBundle = [NSBundle bundleWithPath:applicationPath];
+ if (!newBundle) {
+ SULog(@"Failed to load NSBundle");
+ result = -1;
+ goto finally;
+ }
+
+ result = SecStaticCodeCreateWithPath((__bridge CFURLRef)[newBundle bundleURL], kSecCSDefaultFlags, &staticCode);
+ if (result != noErr) {
+ SULog(@"Failed to get static code %d", result);
+ goto finally;
+ }
+
+ // Note that kSecCSCheckNestedCode may not work with pre-Mavericks code signing.
+ // See https://github.com/sparkle-project/Sparkle/issues/376#issuecomment-48824267 and https://developer.apple.com/library/mac/technotes/tn2206
+ SecCSFlags flags = kSecCSDefaultFlags | kSecCSCheckAllArchitectures;
+ result = SecStaticCodeCheckValidityWithErrors(staticCode, flags, NULL, &cfError);
+
+ if (cfError) {
+ NSError *tmpError = CFBridgingRelease(cfError);
+ if (error) *error = tmpError;
+ }
+
+ if (result != noErr) {
+ if (result == errSecCSUnsigned) {
+ SULog(@"Error: The app is not signed using Apple Code Signing. %@", applicationPath);
+ }
+ if (result == errSecCSReqFailed) {
+ [self logSigningInfoForCode:staticCode label:@"new info"];
+ }
+ }
+
+finally:
+ if (staticCode) CFRelease(staticCode);
+ return (result == noErr);
+}
+
static id valueOrNSNull(id value) {
return value ? value : [NSNull null];
}
@@ -114,4 +164,35 @@ static id valueOrNSNull(id value) {
return (result == 0);
}
++ (BOOL)applicationAtPathIsCodeSigned:(NSString *)applicationPath
+{
+ OSStatus result;
+ SecStaticCodeRef staticCode = NULL;
+ NSBundle *newBundle;
+
+ newBundle = [NSBundle bundleWithPath:applicationPath];
+ if (!newBundle) {
+ SULog(@"Failed to load NSBundle");
+ return NO;
+ }
+
+ result = SecStaticCodeCreateWithPath((__bridge CFURLRef)[newBundle bundleURL], kSecCSDefaultFlags, &staticCode);
+ if (result == errSecCSUnsigned) {
+ return NO;
+ }
+
+ SecRequirementRef requirement = NULL;
+ result = SecCodeCopyDesignatedRequirement(staticCode, kSecCSDefaultFlags, &requirement);
+ if (staticCode) {
+ CFRelease(staticCode);
+ }
+ if (requirement) {
+ CFRelease(requirement);
+ }
+ if (result == errSecCSUnsigned) {
+ return NO;
+ }
+ return (result == 0);
+}
+
@end
diff --git a/Frameworks/Sparkle/Sparkle/SUConstants.h b/Frameworks/Sparkle/Sparkle/SUConstants.h
index d9d7887bf..e6bfcb31c 100644
--- a/Frameworks/Sparkle/Sparkle/SUConstants.h
+++ b/Frameworks/Sparkle/Sparkle/SUConstants.h
@@ -79,33 +79,4 @@ extern NSString *const SURSSElementLink;
extern NSString *const SURSSElementPubDate;
extern NSString *const SURSSElementTitle;
-// -----------------------------------------------------------------------------
-// Errors:
-// -----------------------------------------------------------------------------
-
-extern NSString *const SUSparkleErrorDomain;
-typedef NS_ENUM(OSStatus, SUError) {
- // Appcast phase errors.
- SUAppcastParseError = 1000,
- SUNoUpdateError = 1001,
- SUAppcastError = 1002,
- SURunningFromDiskImageError = 1003,
-
- // Downlaod phase errors.
- SUTemporaryDirectoryError = 2000,
-
- // Extraction phase errors.
- SUUnarchivingError = 3000,
- SUSignatureError = 3001,
-
- // Installation phase errors.
- SUFileCopyFailure = 4000,
- SUAuthenticationFailure = 4001,
- SUMissingUpdateError = 4002,
- SUMissingInstallerToolError = 4003,
- SURelaunchError = 4004,
- SUInstallationError = 4005,
- SUDowngradeError = 4006
-};
-
#endif
diff --git a/Frameworks/Sparkle/Sparkle/SUErrors.h b/Frameworks/Sparkle/Sparkle/SUErrors.h
new file mode 100644
index 000000000..e624bb056
--- /dev/null
+++ b/Frameworks/Sparkle/Sparkle/SUErrors.h
@@ -0,0 +1,44 @@
+//
+// SUErrors.h
+// Sparkle
+//
+// Created by C.W. Betts on 10/13/14.
+// Copyright (c) 2014 Sparkle Project. All rights reserved.
+//
+
+#ifndef SUERRORS_H
+#define SUERRORS_H
+
+#import
+#import "SUExport.h"
+
+/**
+ * Error domain used by Sparkle
+ */
+SU_EXPORT extern NSString *const SUSparkleErrorDomain;
+
+typedef NS_ENUM(OSStatus, SUError) {
+ // Appcast phase errors.
+ SUAppcastParseError = 1000,
+ SUNoUpdateError = 1001,
+ SUAppcastError = 1002,
+ SURunningFromDiskImageError = 1003,
+
+ // Downlaod phase errors.
+ SUTemporaryDirectoryError = 2000,
+
+ // Extraction phase errors.
+ SUUnarchivingError = 3000,
+ SUSignatureError = 3001,
+
+ // Installation phase errors.
+ SUFileCopyFailure = 4000,
+ SUAuthenticationFailure = 4001,
+ SUMissingUpdateError = 4002,
+ SUMissingInstallerToolError = 4003,
+ SURelaunchError = 4004,
+ SUInstallationError = 4005,
+ SUDowngradeError = 4006
+};
+
+#endif
diff --git a/Frameworks/Sparkle/Sparkle/SUGuidedPackageInstaller.h b/Frameworks/Sparkle/Sparkle/SUGuidedPackageInstaller.h
new file mode 100644
index 000000000..92082a340
--- /dev/null
+++ b/Frameworks/Sparkle/Sparkle/SUGuidedPackageInstaller.h
@@ -0,0 +1,35 @@
+//
+// SUGuidedPackageInstaller.h
+// Sparkle
+//
+// Created by Graham Miln on 14/05/2010.
+// Copyright 2010 Dragon Systems Software Limited. All rights reserved.
+//
+
+/*!
+# Sparkle Guided Installations
+
+A guided installation allows Sparkle to download and install a package (pkg) or multi-package (mpkg) without user interaction.
+
+The installer package is installed using Mac OS X's built-in command line installer, `/usr/sbin/installer`. No installation interface is shown to the user.
+
+A guided installation can be started by applications other than the application being replaced. This is particularly useful where helper applications or agents are used.
+
+## To Do
+- Replace the use of `AuthorizationExecuteWithPrivilegesAndWait`. This method remains because it is well supported and tested. Ideally a helper tool or XPC would be used.
+*/
+
+#ifndef SUGUIDEDPACKAGEINSTALLER_H
+#define SUGUIDEDPACKAGEINSTALLER_H
+
+#import "Sparkle.h"
+#import "SUInstaller.h"
+
+@interface SUGuidedPackageInstaller : SUInstaller {
+}
+
+/*! Perform the guided installation */
++ (void)performInstallationToPath:(NSString *)path fromPath:(NSString *)installerGuide host:(SUHost *)host versionComparator:(id)comparator completionHandler:(void (^)(NSError *))completionHandler;
+@end
+
+#endif
diff --git a/Frameworks/Sparkle/Sparkle/SUGuidedPackageInstaller.m b/Frameworks/Sparkle/Sparkle/SUGuidedPackageInstaller.m
new file mode 100644
index 000000000..bc83d42be
--- /dev/null
+++ b/Frameworks/Sparkle/Sparkle/SUGuidedPackageInstaller.m
@@ -0,0 +1,139 @@
+//
+// SUGuidedPackageInstaller.m
+// Sparkle
+//
+// Created by Graham Miln on 14/05/2010.
+// Copyright 2010 Dragon Systems Software Limited. All rights reserved.
+//
+
+#import
+#import
+
+#import "SUGuidedPackageInstaller.h"
+
+static BOOL AuthorizationExecuteWithPrivilegesAndWait(AuthorizationRef authorization, const char* executablePath, AuthorizationFlags options, const char* const* arguments)
+{
+ sig_t oldSigChildHandler = signal(SIGCHLD, SIG_DFL);
+ BOOL returnValue = YES;
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ /* AuthorizationExecuteWithPrivileges used to support 10.4+; should be replaced with XPC or external process */
+ if (AuthorizationExecuteWithPrivileges(authorization, executablePath, options, (char* const*)arguments, NULL) == errAuthorizationSuccess)
+#pragma clang diagnostic pop
+ {
+ int status = 0;
+ pid_t pid = wait(&status);
+ if (pid == -1 || !WIFEXITED(status) || WEXITSTATUS(status) != 0)
+ returnValue = NO;
+ }
+ else
+ returnValue = NO;
+
+ signal(SIGCHLD, oldSigChildHandler);
+ return returnValue;
+}
+
+@implementation SUGuidedPackageInstaller (SUGuidedPackageInstallerAuthentication)
+
++ (AuthorizationRef)authorizationForExecutable:(NSString*)executablePath
+{
+ NSParameterAssert(executablePath);
+
+ // Get authorization using advice in Apple's Technical Q&A1172
+
+ // ...create authorization without specific rights
+ AuthorizationRef auth = NULL;
+ OSStatus validAuth = AuthorizationCreate(NULL,
+ kAuthorizationEmptyEnvironment,
+ kAuthorizationFlagDefaults,
+ &auth);
+ // ...then extend authorization with desired rights
+ if ((validAuth == errAuthorizationSuccess) &&
+ (auth != NULL))
+ {
+ const char* executableFileSystemRepresentation = [executablePath fileSystemRepresentation];
+
+ // Prepare a right allowing script to execute with privileges
+ AuthorizationItem right;
+ memset(&right,0,sizeof(right));
+ right.name = kAuthorizationRightExecute;
+ right.value = (void*) executableFileSystemRepresentation;
+ right.valueLength = strlen(executableFileSystemRepresentation);
+
+ // Package up the single right
+ AuthorizationRights rights;
+ memset(&rights,0,sizeof(rights));
+ rights.count = 1;
+ rights.items = &right;
+
+ // Extend rights to run script
+ validAuth = AuthorizationCopyRights(auth,
+ &rights,
+ kAuthorizationEmptyEnvironment,
+ kAuthorizationFlagPreAuthorize |
+ kAuthorizationFlagExtendRights |
+ kAuthorizationFlagInteractionAllowed,
+ NULL);
+ if (validAuth != errAuthorizationSuccess)
+ {
+ // Error, clean up authorization
+ (void) AuthorizationFree(auth,kAuthorizationFlagDefaults);
+ auth = NULL;
+ }
+ }
+
+ return auth;
+}
+
+@end
+
+@implementation SUGuidedPackageInstaller
+
++ (void)performInstallationToPath:(NSString *)destinationPath fromPath:(NSString *)packagePath host:(SUHost *)__unused host versionComparator:(id)__unused comparator completionHandler:(void (^)(NSError *))completionHandler
+{
+ NSParameterAssert(packagePath);
+
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+
+ // Preflight
+ NSString* installerPath = @"/usr/sbin/installer"; // Mac OS X 10.2+ command line installer tool
+ NSError* error = nil;
+
+ // Create authorization for installer executable
+ BOOL validInstallation = NO;
+ AuthorizationRef auth = [self authorizationForExecutable:installerPath];
+ if (auth != NULL)
+ {
+ // Permission was granted to execute the installer with privileges
+ const char* const arguments[] = {
+ "-pkg",
+ [packagePath fileSystemRepresentation],
+ "-target",
+ "/",
+ NULL
+ };
+ validInstallation = AuthorizationExecuteWithPrivilegesAndWait(auth,
+ [installerPath fileSystemRepresentation],
+ kAuthorizationFlagDefaults,
+ arguments);
+ // TODO: wait for communications pipe to close via fileno & CFSocketCreateWithNative
+ AuthorizationFree(auth,kAuthorizationFlagDefaults);
+ }
+ else
+ {
+ NSString* errorMessage = [NSString stringWithFormat:@"Sparkle Updater: Script authorization denied."];
+ error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUInstallationError userInfo:[NSDictionary dictionaryWithObject:errorMessage forKey:NSLocalizedDescriptionKey]];
+ }
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [self finishInstallationToPath:destinationPath
+ withResult:validInstallation
+ error:error
+ completionHandler:completionHandler];
+
+ });
+ });
+}
+
+@end
diff --git a/Frameworks/Sparkle/Sparkle/SUHost.h b/Frameworks/Sparkle/Sparkle/SUHost.h
index 6e4e49f9a..4ec132867 100644
--- a/Frameworks/Sparkle/Sparkle/SUHost.h
+++ b/Frameworks/Sparkle/Sparkle/SUHost.h
@@ -18,7 +18,7 @@
- (instancetype)initWithBundle:(NSBundle *)aBundle;
@property (readonly, copy) NSString *bundlePath;
-@property (readonly, copy) NSString *appSupportPath;
+@property (readonly, copy) NSString *appCachePath;
@property (readonly, copy) NSString *installationPath;
@property (readonly, copy) NSString *name;
@property (readonly, copy) NSString *version;
diff --git a/Frameworks/Sparkle/Sparkle/SUHost.m b/Frameworks/Sparkle/Sparkle/SUHost.m
index 58c536513..8f559a87e 100644
--- a/Frameworks/Sparkle/Sparkle/SUHost.m
+++ b/Frameworks/Sparkle/Sparkle/SUHost.m
@@ -66,21 +66,26 @@ typedef struct {
return [self.bundle bundlePath];
}
-- (NSString *)appSupportPath
+- (NSString *)appCachePath
{
- NSArray *appSupportPaths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
- NSString *appSupportPath = nil;
- if (!appSupportPaths || [appSupportPaths count] == 0)
- {
- SULog(@"Failed to find app support directory! Using ~/Library/Application Support...");
- appSupportPath = [@"~/Library/Application Support" stringByExpandingTildeInPath];
+ NSArray *cachePaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
+ NSString *cachePath = nil;
+ if ([cachePaths count]) {
+ cachePath = cachePaths[0];
}
- else {
- appSupportPath = appSupportPaths[0];
+ if (!cachePath) {
+ SULog(@"Failed to find user's cache directory! Using system default");
+ cachePath = NSTemporaryDirectory();
}
- appSupportPath = [appSupportPath stringByAppendingPathComponent:[self name]];
- appSupportPath = [appSupportPath stringByAppendingPathComponent:@".Sparkle"];
- return appSupportPath;
+
+ NSString *name = [self.bundle bundleIdentifier];
+ if (!name) {
+ name = [self name];
+ }
+
+ cachePath = [cachePath stringByAppendingPathComponent:name];
+ cachePath = [cachePath stringByAppendingPathComponent:@"Sparkle"];
+ return cachePath;
}
- (NSString *)installationPath
@@ -97,7 +102,13 @@ typedef struct {
- (NSString *)name
{
- NSString *name = [self.bundle objectForInfoDictionaryKey:@"CFBundleDisplayName"];
+ NSString *name;
+
+ // Allow host bundle to provide a custom name
+ name = [self objectForInfoDictionaryKey:@"SUBundleName"];
+ if (name) return name;
+
+ name = [self.bundle objectForInfoDictionaryKey:@"CFBundleDisplayName"];
if (name) return name;
name = [self objectForInfoDictionaryKey:(__bridge NSString *)kCFBundleNameKey];
diff --git a/Frameworks/Sparkle/Sparkle/SUInstaller.h b/Frameworks/Sparkle/Sparkle/SUInstaller.h
index c1b01898e..fe82f885f 100644
--- a/Frameworks/Sparkle/Sparkle/SUInstaller.h
+++ b/Frameworks/Sparkle/Sparkle/SUInstaller.h
@@ -12,21 +12,14 @@
#import
#import "SUVersionComparisonProtocol.h"
-@protocol SUInstallerDelegate;
-
@class SUHost;
@interface SUInstaller : NSObject
+ (NSString *)appPathInUpdateFolder:(NSString *)updateFolder forHost:(SUHost *)host;
-+ (void)installFromUpdateFolder:(NSString *)updateFolder overHost:(SUHost *)host installationPath:(NSString *)installationPath delegate:(id)delegate versionComparator:(id)comparator;
-+ (void)finishInstallationToPath:(NSString *)installationPath withResult:(BOOL)result host:(SUHost *)host error:(NSError *)error delegate:(id)delegate;
++ (void)installFromUpdateFolder:(NSString *)updateFolder overHost:(SUHost *)host installationPath:(NSString *)installationPath versionComparator:(id)comparator completionHandler:(void (^)(NSError *))completionHandler;
++ (void)finishInstallationToPath:(NSString *)installationPath withResult:(BOOL)result error:(NSError *)error completionHandler:(void (^)(NSError *))completionHandler;
+ (NSString *)updateFolder;
@end
-@protocol SUInstallerDelegate
-- (void)installerFinishedForHost:(SUHost *)host;
-- (void)installerForHost:(SUHost *)host failedWithError:(NSError *)error;
-@end
-
#endif
diff --git a/Frameworks/Sparkle/Sparkle/SUInstaller.m b/Frameworks/Sparkle/Sparkle/SUInstaller.m
index 6eff13942..2513973a9 100644
--- a/Frameworks/Sparkle/Sparkle/SUInstaller.m
+++ b/Frameworks/Sparkle/Sparkle/SUInstaller.m
@@ -9,11 +9,11 @@
#import "SUInstaller.h"
#import "SUPlainInstaller.h"
#import "SUPackageInstaller.h"
+#import "SUGuidedPackageInstaller.h"
#import "SUHost.h"
#import "SUConstants.h"
#import "SULog.h"
-
@implementation SUInstaller
static NSString *sUpdateFolder = nil;
@@ -42,50 +42,49 @@ static NSString *sUpdateFolder = nil;
return NO;
}
-+ (NSString *)installSourcePathInUpdateFolder:(NSString *)inUpdateFolder forHost:(SUHost *)host isPackage:(BOOL *)isPackagePtr
++ (NSString *)installSourcePathInUpdateFolder:(NSString *)inUpdateFolder forHost:(SUHost *)host isPackage:(BOOL *)isPackagePtr isGuided:(BOOL *)isGuidedPtr
{
+ NSParameterAssert(inUpdateFolder);
+ NSParameterAssert(host);
+
// Search subdirectories for the application
NSString *currentFile,
*newAppDownloadPath = nil,
*bundleFileName = [[host bundlePath] lastPathComponent],
*alternateBundleFileName = [[host name] stringByAppendingPathExtension:[[host bundlePath] pathExtension]];
BOOL isPackage = NO;
+ BOOL isGuided = NO;
NSString *fallbackPackagePath = nil;
NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:inUpdateFolder];
+ NSString *bundleFileNameNoExtension = [bundleFileName stringByDeletingPathExtension];
sUpdateFolder = inUpdateFolder;
- while ((currentFile = [dirEnum nextObject]))
- {
+ while ((currentFile = [dirEnum nextObject])) {
NSString *currentPath = [inUpdateFolder stringByAppendingPathComponent:currentFile];
- if ([[currentFile lastPathComponent] isEqualToString:bundleFileName] ||
- [[currentFile lastPathComponent] isEqualToString:alternateBundleFileName]) // We found one!
+ NSString *currentFilename = [currentFile lastPathComponent];
+ NSString *currentExtension = [currentFile pathExtension];
+ NSString *currentFilenameNoExtension = [currentFilename stringByDeletingPathExtension];
+ if ([currentFilename isEqualToString:bundleFileName] ||
+ [currentFilename isEqualToString:alternateBundleFileName]) // We found one!
{
isPackage = NO;
newAppDownloadPath = currentPath;
break;
- }
- else if ([[currentFile pathExtension] isEqualToString:@"pkg"] ||
- [[currentFile pathExtension] isEqualToString:@"mpkg"])
- {
- if ([[[currentFile lastPathComponent] stringByDeletingPathExtension] isEqualToString:[bundleFileName stringByDeletingPathExtension]])
- {
+ } else if ([currentExtension isEqualToString:@"pkg"] ||
+ [currentExtension isEqualToString:@"mpkg"]) {
+ if ([currentFilenameNoExtension isEqualToString:bundleFileNameNoExtension]) {
isPackage = YES;
newAppDownloadPath = currentPath;
break;
- }
- else
- {
+ } else {
// Remember any other non-matching packages we have seen should we need to use one of them as a fallback.
fallbackPackagePath = currentPath;
}
- }
- else
- {
+ } else {
// Try matching on bundle identifiers in case the user has changed the name of the host app
NSBundle *incomingBundle = [NSBundle bundleWithPath:currentPath];
- if(incomingBundle && [[incomingBundle bundleIdentifier] isEqualToString:[[host bundle] bundleIdentifier]])
- {
+ if (incomingBundle && [[incomingBundle bundleIdentifier] isEqualToString:[[host bundle] bundleIdentifier]]) {
isPackage = NO;
newAppDownloadPath = currentPath;
break;
@@ -99,35 +98,52 @@ static NSString *sUpdateFolder = nil;
// We don't have a valid path. Try to use the fallback package.
- if (newAppDownloadPath == nil && fallbackPackagePath != nil)
- {
+ if (newAppDownloadPath == nil && fallbackPackagePath != nil) {
isPackage = YES;
newAppDownloadPath = fallbackPackagePath;
}
- if (isPackagePtr) *isPackagePtr = isPackage;
+ if (isPackage) {
+ // foo.app -> foo.sparkle_guided.pkg or foo.sparkle_guided.mpkg
+ if ([[[newAppDownloadPath stringByDeletingPathExtension] pathExtension] isEqualToString:@"sparkle_guided"]) {
+ isGuided = YES;
+ }
+ }
+
+ if (isPackagePtr)
+ *isPackagePtr = isPackage;
+ if (isGuidedPtr)
+ *isGuidedPtr = isGuided;
+
+ if (!newAppDownloadPath) {
+ SULog(@"Searched %@ for %@.(app|pkg)", inUpdateFolder, bundleFileNameNoExtension);
+ }
return newAppDownloadPath;
}
+ (NSString *)appPathInUpdateFolder:(NSString *)updateFolder forHost:(SUHost *)host
{
BOOL isPackage = NO;
- NSString *path = [self installSourcePathInUpdateFolder:updateFolder forHost:host isPackage:&isPackage];
+ NSString *path = [self installSourcePathInUpdateFolder:updateFolder forHost:host isPackage:&isPackage isGuided:nil];
return isPackage ? nil : path;
}
-+ (void)installFromUpdateFolder:(NSString *)inUpdateFolder overHost:(SUHost *)host installationPath:(NSString *)installationPath delegate:(id)delegate versionComparator:(id)comparator
++ (void)installFromUpdateFolder:(NSString *)inUpdateFolder overHost:(SUHost *)host installationPath:(NSString *)installationPath versionComparator:(id)comparator completionHandler:(void (^)(NSError *))completionHandler
{
BOOL isPackage = NO;
- NSString *newAppDownloadPath = [self installSourcePathInUpdateFolder:inUpdateFolder forHost:host isPackage:&isPackage];
+ BOOL isGuided = NO;
+ NSString *newAppDownloadPath = [self installSourcePathInUpdateFolder:inUpdateFolder forHost:host isPackage:&isPackage isGuided:&isGuided];
- if (newAppDownloadPath == nil)
- {
- [self finishInstallationToPath:installationPath withResult:NO host:host error:[NSError errorWithDomain:SUSparkleErrorDomain code:SUMissingUpdateError userInfo:@{ NSLocalizedDescriptionKey: @"Couldn't find an appropriate update in the downloaded package." }] delegate:delegate];
- }
- else
- {
- [(isPackage ? [SUPackageInstaller class] : [SUPlainInstaller class])performInstallationToPath:installationPath fromPath:newAppDownloadPath host:host delegate:delegate versionComparator:comparator];
+ if (newAppDownloadPath == nil) {
+ [self finishInstallationToPath:installationPath withResult:NO error:[NSError errorWithDomain:SUSparkleErrorDomain code:SUMissingUpdateError userInfo:@{ NSLocalizedDescriptionKey: @"Couldn't find an appropriate update in the downloaded package." }] completionHandler:completionHandler];
+ } else {
+ if (isPackage && isGuided) {
+ [SUGuidedPackageInstaller performInstallationToPath:installationPath fromPath:newAppDownloadPath host:host versionComparator:comparator completionHandler:completionHandler];
+ } else if (isPackage) {
+ [SUPackageInstaller performInstallationToPath:installationPath fromPath:newAppDownloadPath host:host versionComparator:comparator completionHandler:completionHandler];
+ } else {
+ [SUPlainInstaller performInstallationToPath:installationPath fromPath:newAppDownloadPath host:host versionComparator:comparator completionHandler:completionHandler];
+ }
}
}
@@ -140,8 +156,7 @@ static NSString *sUpdateFolder = nil;
NSTask *mdimport = [[NSTask alloc] init];
[mdimport setLaunchPath:@"/usr/bin/mdimport"];
[mdimport setArguments:@[installationPath]];
- @try
- {
+ @try {
[mdimport launch];
[mdimport waitUntilExit];
}
@@ -152,24 +167,20 @@ static NSString *sUpdateFolder = nil;
}
}
-+ (void)finishInstallationToPath:(NSString *)installationPath withResult:(BOOL)result host:(SUHost *)host error:(NSError *)error delegate:(id)delegate
++ (void)finishInstallationToPath:(NSString *)installationPath withResult:(BOOL)result error:(NSError *)error completionHandler:(void (^)(NSError *))completionHandler
{
- if (result)
- {
+ if (result) {
[self mdimportInstallationPath:installationPath];
- if ([delegate respondsToSelector:@selector(installerFinishedForHost:)]) {
- dispatch_async(dispatch_get_main_queue(), ^{
- [delegate installerFinishedForHost:host];
- });
- }
- }
- else
- {
- if ([delegate respondsToSelector:@selector(installerForHost:failedWithError:)]) {
- dispatch_async(dispatch_get_main_queue(), ^{
- [delegate installerForHost:host failedWithError:error];
- });
+ dispatch_async(dispatch_get_main_queue(), ^{
+ completionHandler(nil);
+ });
+ } else {
+ if (!error) {
+ error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUInstallationError userInfo:nil];
}
+ dispatch_async(dispatch_get_main_queue(), ^{
+ completionHandler(error);
+ });
}
}
diff --git a/Frameworks/Sparkle/Sparkle/SULog.h b/Frameworks/Sparkle/Sparkle/SULog.h
index e3559b5ea..1693f5355 100644
--- a/Frameworks/Sparkle/Sparkle/SULog.h
+++ b/Frameworks/Sparkle/Sparkle/SULog.h
@@ -12,7 +12,8 @@
Your tech support will hug you if you tell them about this.
*/
-#pragma once
+#ifndef SULOG_H
+#define SULOG_H
// -----------------------------------------------------------------------------
// Headers:
@@ -28,4 +29,4 @@
void SUClearLog(void);
void SULog(NSString *format, ...) NS_FORMAT_FUNCTION(1, 2);
-
+#endif
diff --git a/Frameworks/Sparkle/Sparkle/SULog.m b/Frameworks/Sparkle/Sparkle/SULog.m
index 02c3be10c..5941aac7e 100644
--- a/Frameworks/Sparkle/Sparkle/SULog.m
+++ b/Frameworks/Sparkle/Sparkle/SULog.m
@@ -30,10 +30,6 @@ static NSString *const SULogFilePath = @"~/Library/Logs/SparkleUpdateLog.log";
//
// TAKES:
// sender - Object that sent this message, typically of type X.
-//
-// GIVES:
-// param - who owns the returned value?
-// result - same here.
// -----------------------------------------------------------------------------
void SUClearLog(void)
@@ -41,6 +37,7 @@ void SUClearLog(void)
FILE *logfile = fopen([[SULogFilePath stringByExpandingTildeInPath] fileSystemRepresentation], "w");
if (logfile) {
fclose(logfile);
+ SULog(@"===== %@ =====", [[NSFileManager defaultManager] displayNameAtPath:[[NSBundle mainBundle] bundlePath]]);
}
}
@@ -57,6 +54,12 @@ void SUClearLog(void)
void SULog(NSString *format, ...)
{
+ static BOOL loggedYet = NO;
+ if (!loggedYet) {
+ loggedYet = YES;
+ SUClearLog();
+ }
+
va_list ap;
va_start(ap, format);
NSString *theStr = [[NSString alloc] initWithFormat:format arguments:ap];
diff --git a/Frameworks/Sparkle/Sparkle/SUPackageInstaller.m b/Frameworks/Sparkle/Sparkle/SUPackageInstaller.m
index 23ad954f5..64637433c 100644
--- a/Frameworks/Sparkle/Sparkle/SUPackageInstaller.m
+++ b/Frameworks/Sparkle/Sparkle/SUPackageInstaller.m
@@ -12,7 +12,7 @@
@implementation SUPackageInstaller
-+ (void)performInstallationToPath:(NSString *)installationPath fromPath:(NSString *)path host:(SUHost *)host delegate:(id)delegate versionComparator:(id)__unused comparator
++ (void)performInstallationToPath:(NSString *)installationPath fromPath:(NSString *)path host:(SUHost *)__unused host versionComparator:(id)__unused comparator completionHandler:(void (^)(NSError *))completionHandler
{
// Run installer using the "open" command to ensure it is launched in front of current application.
// -W = wait until the app has quit.
@@ -23,7 +23,7 @@
if (![[NSFileManager defaultManager] fileExistsAtPath:command]) {
NSError *error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUMissingInstallerToolError userInfo:@{ NSLocalizedDescriptionKey: @"Couldn't find Apple's installer tool!" }];
- [self finishInstallationToPath:installationPath withResult:NO host:host error:error delegate:delegate];
+ [self finishInstallationToPath:installationPath withResult:NO error:error completionHandler:completionHandler];
return;
}
@@ -33,7 +33,7 @@
// Known bug: if the installation fails or is canceled, Sparkle goes ahead and restarts, thinking everything is fine.
dispatch_async(dispatch_get_main_queue(), ^{
- [self finishInstallationToPath:installationPath withResult:YES host:host error:nil delegate:delegate];
+ [self finishInstallationToPath:installationPath withResult:YES error:nil completionHandler:completionHandler];
});
});
}
diff --git a/Frameworks/Sparkle/Sparkle/SUPipedUnarchiver.m b/Frameworks/Sparkle/Sparkle/SUPipedUnarchiver.m
index 1f2f68eaa..7a65054c1 100644
--- a/Frameworks/Sparkle/Sparkle/SUPipedUnarchiver.m
+++ b/Frameworks/Sparkle/Sparkle/SUPipedUnarchiver.m
@@ -22,7 +22,10 @@
@".tar.gz": @"extractTGZ",
@".tgz": @"extractTGZ",
@".tar.bz2": @"extractTBZ",
- @".tbz": @"extractTBZ" };
+ @".tbz": @"extractTBZ",
+ @".tar.xz": @"extractTXZ",
+ @".txz": @"extractTXZ",
+ @".tar.lzma": @"extractTXZ"};
NSString *lastPathComponent = [path lastPathComponent];
for (NSString *currentType in typeSelectorDictionary)
@@ -45,73 +48,69 @@
}
// This method abstracts the types that use a command line tool piping data from stdin.
-- (void)extractArchivePipingDataToCommand:(NSString *)command
+- (void)extractArchivePipingDataToCommand:(NSString *)command args:(NSArray*)args
{
// *** GETS CALLED ON NON-MAIN THREAD!!!
@autoreleasepool {
- FILE *fp = NULL, *cmdFP = NULL;
- char *oldDestinationString = NULL;
- // We have to declare these before a goto to prevent an error under ARC.
- // No, we cannot have them in the dispatch_async calls, as the goto "jump enters
- // lifetime of block which strongly captures a variable"
- dispatch_block_t delegateSuccess = ^{
- [self notifyDelegateOfSuccess];
- };
- dispatch_block_t delegateFailure = ^{
- [self notifyDelegateOfFailure];
- };
- SULog(@"Extracting %@ using '%@'", self.archivePath, command);
+ NSString *destination = [self.archivePath stringByDeletingLastPathComponent];
+
+ SULog(@"Extracting using '%@' '%@' < '%@' '%@'", command, [args componentsJoinedByString:@"' '"], self.archivePath, destination);
// Get the file size.
- NSNumber *fs = [[NSFileManager defaultManager] attributesOfItemAtPath:self.archivePath error:nil][NSFileSize];
- if (fs == nil) goto reportError;
+ NSUInteger expectedLength = [[[NSFileManager defaultManager] attributesOfItemAtPath:self.archivePath error:nil][NSFileSize] unsignedIntegerValue];
+ if (expectedLength > 0) {
+ NSFileHandle *archiveInput = [NSFileHandle fileHandleForReadingAtPath:self.archivePath];
- // Thank you, Allan Odgaard!
- // (who wrote the following extraction alg.)
- fp = fopen([self.archivePath fileSystemRepresentation], "r");
- if (!fp) goto reportError;
+ NSPipe *pipe = [NSPipe pipe];
+ NSFileHandle *archiveOutput = [pipe fileHandleForWriting];
- oldDestinationString = getenv("DESTINATION");
- setenv("DESTINATION", [[self.archivePath stringByDeletingLastPathComponent] fileSystemRepresentation], 1);
- cmdFP = popen([command fileSystemRepresentation], "w");
- size_t written;
- if (!cmdFP) goto reportError;
+ NSTask *task = [[NSTask alloc] init];
+ [task setStandardInput:[pipe fileHandleForReading]];
+ [task setStandardError:[NSFileHandle fileHandleWithStandardError]];
+ [task setStandardOutput:[NSFileHandle fileHandleWithStandardOutput]];
+ [task setLaunchPath:command];
+ [task setArguments:[args arrayByAddingObject:destination]];
+ [task launch];
- char buf[32 * 1024];
- size_t len;
- while((len = fread(buf, 1, 32*1024, fp)))
- {
- written = fwrite(buf, 1, len, cmdFP);
- if( written < len )
- {
- pclose(cmdFP);
- goto reportError;
+ NSUInteger bytesRead = 0;
+ do {
+ NSData *data = [archiveInput readDataOfLength:256*1024];
+ NSUInteger len = [data length];
+ if (!len) {
+ break;
+ }
+ bytesRead += len;
+ [archiveOutput writeData:data];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [self notifyDelegateOfProgress:(double)bytesRead / (double)expectedLength];
+ });
}
+ while(bytesRead < expectedLength);
+
+ [archiveOutput closeFile];
- dispatch_async(dispatch_get_main_queue(), ^{
- [self notifyDelegateOfExtractedLength:len];
- });
- }
- pclose(cmdFP);
-
- if (ferror(fp)) {
- goto reportError;
+ [task waitUntilExit];
+
+ if ([task terminationStatus] == 0) {
+ if (bytesRead == expectedLength) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [self notifyDelegateOfSuccess];
+ });
+ return;
+ } else {
+ SULog(@"Extraction failed, command '%@' got only %ld of %ld bytes", command, (long)bytesRead, (long)expectedLength);
+ }
+ } else {
+ SULog(@"Extraction failed, command '%@' returned %d", command, [task terminationStatus]);
+ }
+ } else {
+ SULog(@"Extraction failed, archive '%@' is empty", self.archivePath);
}
- dispatch_async(dispatch_get_main_queue(), delegateSuccess);
- goto finally;
-
- reportError:
- dispatch_async(dispatch_get_main_queue(), delegateFailure);
-
- finally:
- if (fp)
- fclose(fp);
- if (oldDestinationString)
- setenv("DESTINATION", oldDestinationString, 1);
- else
- unsetenv("DESTINATION");
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [self notifyDelegateOfFailure];
+ });
}
}
@@ -119,28 +118,35 @@
{
// *** GETS CALLED ON NON-MAIN THREAD!!!
- [self extractArchivePipingDataToCommand:@"tar -xC \"$DESTINATION\""];
+ [self extractArchivePipingDataToCommand:@"/usr/bin/tar" args:@[@"-xC"]];
}
- (void)extractTGZ
{
// *** GETS CALLED ON NON-MAIN THREAD!!!
- [self extractArchivePipingDataToCommand:@"tar -zxC \"$DESTINATION\""];
+ [self extractArchivePipingDataToCommand:@"/usr/bin/tar" args:@[@"-zxC"]];
}
- (void)extractTBZ
{
// *** GETS CALLED ON NON-MAIN THREAD!!!
- [self extractArchivePipingDataToCommand:@"tar -jxC \"$DESTINATION\""];
+ [self extractArchivePipingDataToCommand:@"/usr/bin/tar" args:@[@"-jxC"]];
}
- (void)extractZIP
{
// *** GETS CALLED ON NON-MAIN THREAD!!!
- [self extractArchivePipingDataToCommand:@"ditto -x -k - \"$DESTINATION\""];
+ [self extractArchivePipingDataToCommand:@"/usr/bin/ditto" args:@[@"-x",@"-k",@"-"]];
+}
+
+- (void)extractTXZ
+{
+ // *** GETS CALLED ON NON-MAIN THREAD!!!
+
+ [self extractArchivePipingDataToCommand:@"/usr/bin/tar" args:@[@"-zxC"]];
}
+ (void)load
diff --git a/Frameworks/Sparkle/Sparkle/SUPlainInstaller.h b/Frameworks/Sparkle/Sparkle/SUPlainInstaller.h
index bb7827a67..9fd78f6ff 100644
--- a/Frameworks/Sparkle/Sparkle/SUPlainInstaller.h
+++ b/Frameworks/Sparkle/Sparkle/SUPlainInstaller.h
@@ -21,7 +21,7 @@
@interface SUPlainInstaller : SUInstaller
-+ (void)performInstallationToPath:(NSString *)installationPath fromPath:(NSString *)path host:(SUHost *)host delegate:(id)delegate versionComparator:(id)comparator;
++ (void)performInstallationToPath:(NSString *)installationPath fromPath:(NSString *)path host:(SUHost *)host versionComparator:(id)comparator completionHandler:(void (^)(NSError *))completionHandler;
@end
diff --git a/Frameworks/Sparkle/Sparkle/SUPlainInstaller.m b/Frameworks/Sparkle/Sparkle/SUPlainInstaller.m
index 96973d465..1aeb63171 100644
--- a/Frameworks/Sparkle/Sparkle/SUPlainInstaller.m
+++ b/Frameworks/Sparkle/Sparkle/SUPlainInstaller.m
@@ -8,20 +8,22 @@
#import "SUPlainInstaller.h"
#import "SUPlainInstallerInternals.h"
+#import "SUCodeSigningVerifier.h"
#import "SUConstants.h"
#import "SUHost.h"
@implementation SUPlainInstaller
-+ (void)performInstallationToPath:(NSString *)installationPath fromPath:(NSString *)path host:(SUHost *)host delegate:(id)delegate versionComparator:(id)comparator
++ (void)performInstallationToPath:(NSString *)installationPath fromPath:(NSString *)path host:(SUHost *)host versionComparator:(id)comparator completionHandler:(void (^)(NSError *))completionHandler
{
+ NSParameterAssert(host);
+
// Prevent malicious downgrades
if (![[[NSBundle bundleWithIdentifier:SUBundleIdentifier] infoDictionary][SUEnableAutomatedDowngradesKey] boolValue]) {
- if ([comparator compareVersion:[host version] toVersion:[[NSBundle bundleWithPath:path] objectForInfoDictionaryKey:(__bridge NSString *)kCFBundleVersionKey]] == NSOrderedDescending)
- {
+ if ([comparator compareVersion:[host version] toVersion:[[NSBundle bundleWithPath:path] objectForInfoDictionaryKey:(__bridge NSString *)kCFBundleVersionKey]] == NSOrderedDescending) {
NSString *errorMessage = [NSString stringWithFormat:@"Sparkle Updater: Possible attack in progress! Attempting to \"upgrade\" from %@ to %@. Aborting update.", [host version], [[NSBundle bundleWithPath:path] objectForInfoDictionaryKey:(__bridge NSString *)kCFBundleVersionKey]];
NSError *error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUDowngradeError userInfo:@{ NSLocalizedDescriptionKey: errorMessage }];
- [self finishInstallationToPath:installationPath withResult:NO host:host error:error delegate:delegate];
+ [self finishInstallationToPath:installationPath withResult:NO error:error completionHandler:completionHandler];
return;
}
}
@@ -30,8 +32,17 @@
NSError *error = nil;
NSString *oldPath = [host bundlePath];
NSString *tempName = [self temporaryNameForPath:[host installationPath]];
+ BOOL hostIsCodeSigned = [SUCodeSigningVerifier applicationAtPathIsCodeSigned:oldPath];
BOOL result = [self copyPathWithAuthentication:path overPath:installationPath temporaryName:tempName error:&error];
+
+ if (result) {
+ // If the host is code signed, then the replacement should be be too (and the signature should be valid).
+ BOOL needToCheckCodeSignature = (hostIsCodeSigned || [SUCodeSigningVerifier applicationAtPathIsCodeSigned:installationPath]);
+ if (needToCheckCodeSignature) {
+ result = [SUCodeSigningVerifier codeSignatureIsValidAtPath:installationPath error:&error];
+ }
+ }
if (result) {
BOOL haveOld = [[NSFileManager defaultManager] fileExistsAtPath:oldPath];
@@ -42,7 +53,7 @@
}
dispatch_async(dispatch_get_main_queue(), ^{
- [self finishInstallationToPath:installationPath withResult:result host:host error:error delegate:delegate];
+ [self finishInstallationToPath:installationPath withResult:result error:error completionHandler:completionHandler];
});
});
}
diff --git a/Frameworks/Sparkle/Sparkle/SUPlainInstallerInternals.m b/Frameworks/Sparkle/Sparkle/SUPlainInstallerInternals.m
index e06e5bdf7..41b297e8b 100644
--- a/Frameworks/Sparkle/Sparkle/SUPlainInstallerInternals.m
+++ b/Frameworks/Sparkle/Sparkle/SUPlainInstallerInternals.m
@@ -599,7 +599,11 @@ static BOOL AuthorizationExecuteWithPrivilegesAndWait(AuthorizationRef authoriza
}
#endif
NSURL *rootURL = [NSURL fileURLWithPath:root];
- [rootURL setResourceValue:[NSNull null] forKey:NSURLQuarantinePropertiesKey error:NULL];
+ id rootResourceValue = nil;
+ [rootURL getResourceValue:&rootResourceValue forKey:NSURLQuarantinePropertiesKey error:NULL];
+ if (rootResourceValue) {
+ [rootURL setResourceValue:[NSNull null] forKey:NSURLQuarantinePropertiesKey error:NULL];
+ }
// Only recurse if it's actually a directory. Don't recurse into a
// root-level symbolic link.
@@ -612,7 +616,11 @@ static BOOL AuthorizationExecuteWithPrivilegesAndWait(AuthorizationRef authoriza
NSDirectoryEnumerator *directoryEnumerator = [manager enumeratorAtURL:rootURL includingPropertiesForKeys:nil options:(NSDirectoryEnumerationOptions)0 errorHandler:nil];
for (NSURL *file in directoryEnumerator) {
- [file setResourceValue:[NSNull null] forKey:NSURLQuarantinePropertiesKey error:NULL];
+ id fileResourceValue = nil;
+ [file getResourceValue:&fileResourceValue forKey:NSURLQuarantinePropertiesKey error:NULL];
+ if (fileResourceValue) {
+ [file setResourceValue:[NSNull null] forKey:NSURLQuarantinePropertiesKey error:NULL];
+ }
}
}
}
diff --git a/Frameworks/Sparkle/Sparkle/SUScheduledUpdateDriver.m b/Frameworks/Sparkle/Sparkle/SUScheduledUpdateDriver.m
index b51fd91dc..9d6bf141b 100644
--- a/Frameworks/Sparkle/Sparkle/SUScheduledUpdateDriver.m
+++ b/Frameworks/Sparkle/Sparkle/SUScheduledUpdateDriver.m
@@ -43,10 +43,18 @@
- (void)abortUpdateWithError:(NSError *)error
{
- if (self.showErrors)
+ if (self.showErrors) {
[super abortUpdateWithError:error];
- else
+ } else {
+ // Call delegate separately here because otherwise it won't know we stopped.
+ // Normally this gets called by the superclass
+ id updaterDelegate = [self.updater delegate];
+ if ([updaterDelegate respondsToSelector:@selector(updater:didAbortWithError:)]) {
+ [updaterDelegate updater:self.updater didAbortWithError:error];
+ }
+
[self abortUpdate];
+ }
}
@end
diff --git a/Frameworks/Sparkle/Sparkle/SUStatusController.m b/Frameworks/Sparkle/Sparkle/SUStatusController.m
index b1ca3b039..c7def5d5d 100644
--- a/Frameworks/Sparkle/Sparkle/SUStatusController.m
+++ b/Frameworks/Sparkle/Sparkle/SUStatusController.m
@@ -31,7 +31,7 @@
- (instancetype)initWithHost:(SUHost *)aHost
{
- self = [super initWithHost:aHost windowNibName:@"SUStatus"];
+ self = [super initWithWindowNibName:@"SUStatus"];
if (self)
{
self.host = aHost;
diff --git a/Frameworks/Sparkle/Sparkle/SUUIBasedUpdateDriver.h b/Frameworks/Sparkle/Sparkle/SUUIBasedUpdateDriver.h
index fb1450724..65f374433 100644
--- a/Frameworks/Sparkle/Sparkle/SUUIBasedUpdateDriver.h
+++ b/Frameworks/Sparkle/Sparkle/SUUIBasedUpdateDriver.h
@@ -15,7 +15,7 @@
@class SUStatusController;
-@interface SUUIBasedUpdateDriver : SUBasicUpdateDriver
+@interface SUUIBasedUpdateDriver : SUBasicUpdateDriver
- (void)showModalAlert:(NSAlert *)alert;
- (IBAction)cancelDownload:(id)sender;
diff --git a/Frameworks/Sparkle/Sparkle/SUUIBasedUpdateDriver.m b/Frameworks/Sparkle/Sparkle/SUUIBasedUpdateDriver.m
index ef631466f..7875dadd9 100644
--- a/Frameworks/Sparkle/Sparkle/SUUIBasedUpdateDriver.m
+++ b/Frameworks/Sparkle/Sparkle/SUUIBasedUpdateDriver.m
@@ -28,8 +28,9 @@
- (void)didFindValidUpdate
{
- self.updateAlert = [[SUUpdateAlert alloc] initWithAppcastItem:self.updateItem host:self.host];
- [self.updateAlert setDelegate:self];
+ self.updateAlert = [[SUUpdateAlert alloc] initWithAppcastItem:self.updateItem host:self.host completionBlock:^(SUUpdateAlertChoice choice) {
+ [self updateAlertFinishedWithChoice:choice];
+ }];
id versDisp = nil;
if ([[self.updater delegate] respondsToSelector:@selector(versionDisplayerForUpdater:)]) {
@@ -44,8 +45,7 @@
// If the app is a menubar app or the like, we need to focus it first and alter the
// update prompt to behave like a normal window. Otherwise if the window were hidden
// there may be no way for the application to be activated to make it visible again.
- if ([self.host isBackgroundApplication])
- {
+ if ([self.host isBackgroundApplication]) {
[[self.updateAlert window] setHidesOnDeactivate:NO];
[NSApp activateIgnoringOtherApps:YES];
}
@@ -78,12 +78,11 @@
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationDidBecomeActiveNotification object:NSApp];
}
-- (void)updateAlert:(SUUpdateAlert *)__unused alert finishedWithChoice:(SUUpdateAlertChoice)choice
+- (void)updateAlertFinishedWithChoice:(SUUpdateAlertChoice)choice
{
self.updateAlert = nil;
[self.host setObject:nil forUserDefaultsKey:SUSkippedVersionKey];
- switch (choice)
- {
+ switch (choice) {
case SUInstallUpdateChoice:
self.statusController = [[SUStatusController alloc] initWithHost:self.host];
[self.statusController beginActionWithTitle:SULocalizedString(@"Downloading update...", @"Take care not to overflow the status window.") maxProgressValue:0.0 statusText:nil];
@@ -154,16 +153,13 @@
[super extractUpdate];
}
-- (void)unarchiver:(SUUnarchiver *)__unused ua extractedLength:(unsigned long)length
+- (void)unarchiver:(SUUnarchiver *)__unused ua extractedProgress:(double)progress
{
// We do this here instead of in extractUpdate so that we only have a determinate progress bar for archives with progress.
- if ([self.statusController maxProgressValue] == 0.0)
- {
- NSDictionary *attributes;
- attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:self.downloadPath error:nil];
- [self.statusController setMaxProgressValue:[attributes[NSFileSize] doubleValue]];
+ if ([self.statusController maxProgressValue] == 0.0) {
+ [self.statusController setMaxProgressValue:1];
}
- [self.statusController setProgressValue:[self.statusController progressValue] + (double)length];
+ [self.statusController setProgressValue:progress];
}
- (void)unarchiverDidFinish:(SUUnarchiver *)__unused ua
diff --git a/Frameworks/Sparkle/Sparkle/SUUnarchiver.h b/Frameworks/Sparkle/Sparkle/SUUnarchiver.h
index 523bdf257..9b7d07807 100644
--- a/Frameworks/Sparkle/Sparkle/SUUnarchiver.h
+++ b/Frameworks/Sparkle/Sparkle/SUUnarchiver.h
@@ -15,10 +15,10 @@
@interface SUUnarchiver : NSObject
@property (copy, readonly) NSString *archivePath;
-@property (weak, readonly) SUHost *updateHost;
+@property (copy, readonly) NSString *updateHostBundlePath;
@property (weak) id delegate;
-+ (SUUnarchiver *)unarchiverForPath:(NSString *)path updatingHost:(SUHost *)host;
++ (SUUnarchiver *)unarchiverForPath:(NSString *)path updatingHostBundlePath:(NSString *)host;
- (void)start;
@end
@@ -27,7 +27,7 @@
- (void)unarchiverDidFinish:(SUUnarchiver *)unarchiver;
- (void)unarchiverDidFail:(SUUnarchiver *)unarchiver;
@optional
-- (void)unarchiver:(SUUnarchiver *)unarchiver extractedLength:(unsigned long)length;
+- (void)unarchiver:(SUUnarchiver *)unarchiver extractedProgress:(double)progress;
@end
#endif
diff --git a/Frameworks/Sparkle/Sparkle/SUUnarchiver.m b/Frameworks/Sparkle/Sparkle/SUUnarchiver.m
index 99f777592..fefa840bf 100644
--- a/Frameworks/Sparkle/Sparkle/SUUnarchiver.m
+++ b/Frameworks/Sparkle/Sparkle/SUUnarchiver.m
@@ -18,15 +18,15 @@
@implementation SUUnarchiver
@synthesize archivePath;
-@synthesize updateHost;
+@synthesize updateHostBundlePath;
@synthesize delegate;
-+ (SUUnarchiver *)unarchiverForPath:(NSString *)path updatingHost:(SUHost *)host
++ (SUUnarchiver *)unarchiverForPath:(NSString *)path updatingHostBundlePath:(NSString *)hostPath
{
for (id current in [self unarchiverImplementations])
{
if ([current canUnarchivePath:path]) {
- return [[current alloc] initWithPath:path host:host];
+ return [[current alloc] initWithPath:path hostBundlePath:hostPath];
}
}
return nil;
@@ -39,12 +39,12 @@
// No-op
}
-- (instancetype)initWithPath:(NSString *)path host:(SUHost *)host
+- (instancetype)initWithPath:(NSString *)path hostBundlePath:(NSString *)hostPath
{
if ((self = [super init]))
{
archivePath = [path copy];
- updateHost = host;
+ updateHostBundlePath = hostPath;
}
return self;
}
@@ -54,10 +54,10 @@
return NO;
}
-- (void)notifyDelegateOfExtractedLength:(size_t)length
+- (void)notifyDelegateOfProgress:(double)progress
{
- if ([self.delegate respondsToSelector:@selector(unarchiver:extractedLength:)]) {
- [self.delegate unarchiver:self extractedLength:length];
+ if ([self.delegate respondsToSelector:@selector(unarchiver:extractedProgress:)]) {
+ [self.delegate unarchiver:self extractedProgress:progress];
}
}
diff --git a/Frameworks/Sparkle/Sparkle/SUUnarchiver_Private.h b/Frameworks/Sparkle/Sparkle/SUUnarchiver_Private.h
index e2d66bd08..c29d68cff 100644
--- a/Frameworks/Sparkle/Sparkle/SUUnarchiver_Private.h
+++ b/Frameworks/Sparkle/Sparkle/SUUnarchiver_Private.h
@@ -16,9 +16,9 @@
+ (void)registerImplementation:(Class)implementation;
+ (NSArray *)unarchiverImplementations;
+ (BOOL)canUnarchivePath:(NSString *)path;
-- (instancetype)initWithPath:(NSString *)path host:(SUHost *)host;
+- (instancetype)initWithPath:(NSString *)archive hostBundlePath:(NSString *)host;
-- (void)notifyDelegateOfExtractedLength:(size_t)length;
+- (void)notifyDelegateOfProgress:(double)progress;
- (void)notifyDelegateOfSuccess;
- (void)notifyDelegateOfFailure;
@end
diff --git a/Frameworks/Sparkle/Sparkle/SUUpdateAlert.h b/Frameworks/Sparkle/Sparkle/SUUpdateAlert.h
index caf9ccba9..2db57bf49 100644
--- a/Frameworks/Sparkle/Sparkle/SUUpdateAlert.h
+++ b/Frameworks/Sparkle/Sparkle/SUUpdateAlert.h
@@ -24,10 +24,9 @@ typedef NS_ENUM(NSInteger, SUUpdateAlertChoice) {
@class WebView, SUAppcastItem, SUHost;
@interface SUUpdateAlert : SUWindowController
-@property (weak) id delegate;
@property (weak) id versionDisplayer;
-- (instancetype)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)host;
+- (instancetype)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)host completionBlock:(void(^)(SUUpdateAlertChoice))c;
- (IBAction)installUpdate:sender;
- (IBAction)skipThisVersion:sender;
@@ -35,10 +34,4 @@ typedef NS_ENUM(NSInteger, SUUpdateAlertChoice) {
@end
-@protocol SUUpdateAlertDelegate
-- (void)updateAlert:(SUUpdateAlert *)updateAlert finishedWithChoice:(SUUpdateAlertChoice)updateChoice;
-@optional
-- (void)updateAlert:(SUUpdateAlert *)updateAlert shouldAllowAutoUpdate:(BOOL *)shouldAllowAutoUpdate;
-@end
-
#endif
diff --git a/Frameworks/Sparkle/Sparkle/SUUpdateAlert.m b/Frameworks/Sparkle/Sparkle/SUUpdateAlert.m
index 2ca4093be..0b3610381 100644
--- a/Frameworks/Sparkle/Sparkle/SUUpdateAlert.m
+++ b/Frameworks/Sparkle/Sparkle/SUUpdateAlert.m
@@ -21,6 +21,7 @@
@property (strong) SUAppcastItem *updateItem;
@property (strong) SUHost *host;
+@property (strong) void(^completionBlock)(SUUpdateAlertChoice);
@property (strong) NSProgressIndicator *releaseNotesSpinner;
@property (assign) BOOL webViewFinishedLoading;
@@ -36,7 +37,7 @@
@implementation SUUpdateAlert
-@synthesize delegate;
+@synthesize completionBlock;
@synthesize versionDisplayer;
@synthesize updateItem;
@@ -52,11 +53,12 @@
@synthesize skipButton;
@synthesize laterButton;
-- (instancetype)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)aHost
+- (instancetype)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)aHost completionBlock:(void (^)(SUUpdateAlertChoice))block
{
- self = [super initWithHost:host windowNibName:@"SUUpdateAlert"];
+ self = [super initWithWindowNibName:@"SUUpdateAlert"];
if (self)
{
+ self.completionBlock = block;
host = aHost;
updateItem = item;
[self setShouldCascadeWindows:NO];
@@ -79,8 +81,8 @@
[self.releaseNotesView setPolicyDelegate:nil];
[self.releaseNotesView removeFromSuperview]; // Otherwise it gets sent Esc presses (why?!) and gets very confused.
[self close];
- if ([self.delegate respondsToSelector:@selector(updateAlert:finishedWithChoice:)])
- [self.delegate updateAlert:self finishedWithChoice:choice];
+ self.completionBlock(choice);
+ self.completionBlock = nil;
}
- (IBAction)installUpdate:(id)__unused sender
@@ -164,10 +166,6 @@
if ([self.host objectForInfoDictionaryKey:SUAllowsAutomaticUpdatesKey])
allowAutoUpdates = [self.host boolForInfoDictionaryKey:SUAllowsAutomaticUpdatesKey];
- // Give delegate a chance to modify this choice:
- if (self.delegate && [self.delegate respondsToSelector:@selector(updateAlert:shouldAllowAutoUpdate:)])
- [self.delegate updateAlert:self shouldAllowAutoUpdate:&allowAutoUpdates];
-
return allowAutoUpdates;
}
diff --git a/Frameworks/Sparkle/Sparkle/SUUpdatePermissionPrompt.m b/Frameworks/Sparkle/Sparkle/SUUpdatePermissionPrompt.m
index 6d31cdf69..6aef7bd2d 100644
--- a/Frameworks/Sparkle/Sparkle/SUUpdatePermissionPrompt.m
+++ b/Frameworks/Sparkle/Sparkle/SUUpdatePermissionPrompt.m
@@ -45,7 +45,7 @@
- (instancetype)initWithHost:(SUHost *)aHost systemProfile:(NSArray *)profile delegate:(id)d
{
- self = [super initWithHost:aHost windowNibName:@"SUUpdatePermissionPrompt"];
+ self = [super initWithWindowNibName:@"SUUpdatePermissionPrompt"];
if (self)
{
host = aHost;
@@ -65,8 +65,11 @@
// the user would not know why the application was paused.
if ([aHost isBackgroundApplication]) { [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; }
- SUUpdatePermissionPrompt *prompt = [[[self class] alloc] initWithHost:aHost systemProfile:profile delegate:d];
- [NSApp runModalForWindow:[prompt window]];
+ if( ![NSApp modalWindow]) // do not prompt if there is is another modal window on screen
+ {
+ SUUpdatePermissionPrompt *prompt = [[[self class] alloc] initWithHost:aHost systemProfile:profile delegate:d];
+ [NSApp runModalForWindow:[prompt window]];
+ }
}
- (NSString *)description { return [NSString stringWithFormat:@"%@ <%@>", [self class], [self.host bundlePath]]; }
diff --git a/Frameworks/Sparkle/Sparkle/SUUpdater.h b/Frameworks/Sparkle/Sparkle/SUUpdater.h
index f83fed5bf..90d606fa4 100644
--- a/Frameworks/Sparkle/Sparkle/SUUpdater.h
+++ b/Frameworks/Sparkle/Sparkle/SUUpdater.h
@@ -25,7 +25,7 @@
*/
SU_EXPORT @interface SUUpdater : NSObject
-@property (weak) IBOutlet id delegate;
+@property (unsafe_unretained) IBOutlet id delegate;
+ (SUUpdater *)sharedUpdater;
+ (SUUpdater *)updaterForBundle:(NSBundle *)bundle;
@@ -46,6 +46,8 @@ SU_EXPORT @interface SUUpdater : NSObject
@property (nonatomic, copy) NSString *userAgentString;
+@property (copy) NSDictionary *httpHeaders;
+
@property BOOL sendsSystemProfile;
@property BOOL automaticallyDownloadsUpdates;
@@ -312,6 +314,14 @@ SU_EXPORT extern NSString *const SUUpdaterAppcastNotificationKey;
*/
- (void)updater:(SUUpdater *)updater didCancelInstallUpdateOnQuit:(SUAppcastItem *)item;
+/*!
+ Called after an update is aborted due to an error.
+
+ \param updater The SUUpdater instance.
+ \param error The error that caused the abort
+ */
+- (void)updater:(SUUpdater *)updater didAbortWithError:(NSError *)error;
+
@end
#endif
diff --git a/Frameworks/Sparkle/Sparkle/SUUpdater.m b/Frameworks/Sparkle/Sparkle/SUUpdater.m
index 7d0e25d52..ee26656ce 100644
--- a/Frameworks/Sparkle/Sparkle/SUUpdater.m
+++ b/Frameworks/Sparkle/Sparkle/SUUpdater.m
@@ -51,12 +51,10 @@ NSString *const SUUpdaterAppcastNotificationKey = @"SUUpdaterAppCastNotification
@synthesize delegate;
@synthesize checkTimer;
@synthesize userAgentString = customUserAgentString;
-
+@synthesize httpHeaders;
@synthesize driver;
@synthesize host;
-#pragma mark Initialization
-
static NSMutableDictionary *sharedUpdaters = nil;
static NSString *const SUUpdaterDefaultsObservationContext = @"SUUpdaterDefaultsObservationContext";
@@ -147,38 +145,28 @@ static NSString *const SUUpdaterDefaultsObservationContext = @"SUUpdaterDefaults
- (void)startUpdateCycle
{
BOOL shouldPrompt = NO;
+ BOOL hasLaunchedBefore = [self.host boolForUserDefaultsKey:SUHasLaunchedBeforeKey];
// If the user has been asked about automatic checks, don't bother prompting
- if ([self.host objectForUserDefaultsKey:SUEnableAutomaticChecksKey])
- {
+ if ([self.host objectForUserDefaultsKey:SUEnableAutomaticChecksKey]) {
shouldPrompt = NO;
}
// Does the delegate want to take care of the logic for when we should ask permission to update?
- else if ([self.delegate respondsToSelector:@selector(updaterShouldPromptForPermissionToCheckForUpdates:)])
- {
+ else if ([self.delegate respondsToSelector:@selector(updaterShouldPromptForPermissionToCheckForUpdates:)]) {
shouldPrompt = [self.delegate updaterShouldPromptForPermissionToCheckForUpdates:self];
}
// Has he been asked already? And don't ask if the host has a default value set in its Info.plist.
- else if ([self.host objectForKey:SUEnableAutomaticChecksKey] == nil)
- {
- if ([self.host objectForUserDefaultsKey:SUEnableAutomaticChecksKeyOld]) {
- [self setAutomaticallyChecksForUpdates:[self.host boolForUserDefaultsKey:SUEnableAutomaticChecksKeyOld]];
- }
+ else if ([self.host objectForKey:SUEnableAutomaticChecksKey] == nil) {
// Now, we don't want to ask the user for permission to do a weird thing on the first launch.
// We wait until the second launch, unless explicitly overridden via SUPromptUserOnFirstLaunchKey.
- else if (![self.host objectForKey:SUPromptUserOnFirstLaunchKey])
- {
- if ([self.host boolForUserDefaultsKey:SUHasLaunchedBeforeKey] == NO)
- [self.host setBool:YES forUserDefaultsKey:SUHasLaunchedBeforeKey];
- else
- shouldPrompt = YES;
- }
- else
- shouldPrompt = YES;
+ shouldPrompt = [self.host objectForKey:SUPromptUserOnFirstLaunchKey] || hasLaunchedBefore;
}
- if (shouldPrompt)
- {
+ if (!hasLaunchedBefore) {
+ [self.host setBool:YES forUserDefaultsKey:SUHasLaunchedBeforeKey];
+ }
+
+ if (shouldPrompt) {
NSArray *profileInfo = [self.host systemProfile];
// Always say we're sending the system profile here so that the delegate displays the parameters it would send.
if ([self.delegate respondsToSelector:@selector(feedParametersForUpdater:sendingSystemProfile:)]) {
@@ -186,9 +174,7 @@ static NSString *const SUUpdaterDefaultsObservationContext = @"SUUpdaterDefaults
}
[SUUpdatePermissionPrompt promptWithHost:self.host systemProfile:profileInfo delegate:self];
// We start the update checks and register as observer for changes after the prompt finishes
- }
- else
- {
+ } else {
// We check if the user's said they want updates, or they haven't said anything, and the default is set to checking.
[self scheduleNextUpdateCheck];
}
@@ -334,9 +320,6 @@ static NSString *const SUUpdaterDefaultsObservationContext = @"SUUpdaterDefaults
if ([self updateInProgress]) { return; }
if (self.checkTimer) { [self.checkTimer invalidate]; self.checkTimer = nil; } // Timer is non-repeating, may have invalidated itself, so we had to retain it.
- SUClearLog();
- SULog(@"===== %@ =====", [[NSFileManager defaultManager] displayNameAtPath:[[NSBundle mainBundle] bundlePath]]);
-
[self willChangeValueForKey:@"lastUpdateCheckDate"];
[self.host setObject:[NSDate date] forUserDefaultsKey:SULastCheckTimeKey];
[self didChangeValueForKey:@"lastUpdateCheckDate"];
diff --git a/Frameworks/Sparkle/Sparkle/SUUserInitiatedUpdateDriver.m b/Frameworks/Sparkle/Sparkle/SUUserInitiatedUpdateDriver.m
index 5dd66f298..d1e0c9a65 100644
--- a/Frameworks/Sparkle/Sparkle/SUUserInitiatedUpdateDriver.m
+++ b/Frameworks/Sparkle/Sparkle/SUUserInitiatedUpdateDriver.m
@@ -78,16 +78,6 @@
[super abortUpdate];
}
-- (void)appcast:(SUAppcast *)ac failedToLoadWithError:(NSError *)error
-{
- if (self.isCanceled)
- {
- [self abortUpdate];
- return;
- }
- [super appcast:ac failedToLoadWithError:error];
-}
-
- (BOOL)itemContainsValidUpdate:(SUAppcastItem *)ui
{
// We don't check to see if this update's been skipped, because the user explicitly *asked* if he had the latest version.
diff --git a/Frameworks/Sparkle/Sparkle/SUWindowController.h b/Frameworks/Sparkle/Sparkle/SUWindowController.h
index 0cd60191b..f6af6fae3 100644
--- a/Frameworks/Sparkle/Sparkle/SUWindowController.h
+++ b/Frameworks/Sparkle/Sparkle/SUWindowController.h
@@ -14,7 +14,7 @@
@class SUHost;
@interface SUWindowController : NSWindowController
// We use this instead of plain old NSWindowController initWithWindowNibName so that we'll be able to find the right path when running in a bundle loaded from another app.
-- (instancetype)initWithHost:(SUHost *)host windowNibName:(NSString *)nibName;
+- (instancetype)initWithWindowNibName:(NSString *)nibName;
@end
#endif
diff --git a/Frameworks/Sparkle/Sparkle/SUWindowController.m b/Frameworks/Sparkle/Sparkle/SUWindowController.m
index 874f76535..553231130 100644
--- a/Frameworks/Sparkle/Sparkle/SUWindowController.m
+++ b/Frameworks/Sparkle/Sparkle/SUWindowController.m
@@ -11,16 +11,9 @@
@implementation SUWindowController
-- (instancetype)initWithHost:(SUHost *)host windowNibName:(NSString *)nibName
+- (instancetype)initWithWindowNibName:(NSString *)nibName
{
- NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:nibName ofType:@"nib"];
- if (path == nil) // Slight hack to resolve issues with running Sparkle in debug configurations.
- {
- NSString *frameworkPath = [[[host bundle] sharedFrameworksPath] stringByAppendingPathComponent:@"Sparkle.framework"];
- NSBundle *framework = [NSBundle bundleWithPath:frameworkPath];
- path = [framework pathForResource:nibName ofType:@"nib"];
- }
- self = [super initWithWindowNibPath:path owner:self];
+ self = [super initWithWindowNibName:nibName owner:self];
return self;
}
diff --git a/Frameworks/Sparkle/Sparkle/Sparkle.h b/Frameworks/Sparkle/Sparkle/Sparkle.h
index 555cb6f64..fe97bae17 100644
--- a/Frameworks/Sparkle/Sparkle/Sparkle.h
+++ b/Frameworks/Sparkle/Sparkle/Sparkle.h
@@ -18,5 +18,6 @@
#import
#import
#import
+#import
#endif
diff --git a/Frameworks/Sparkle/Sparkle/Sparkle.pch b/Frameworks/Sparkle/Sparkle/Sparkle.pch
index e30bf990c..ccd8c7591 100644
--- a/Frameworks/Sparkle/Sparkle/Sparkle.pch
+++ b/Frameworks/Sparkle/Sparkle/Sparkle.pch
@@ -10,6 +10,7 @@
#import
#import "SUConstants.h"
+#import "SUErrors.h"
#define SULocalizedString(key, comment) NSLocalizedStringFromTableInBundle(key, @"Sparkle", [NSBundle bundleWithIdentifier:SUBundleIdentifier] ? [NSBundle bundleWithIdentifier:SUBundleIdentifier] : [NSBundle mainBundle], comment)
diff --git a/Frameworks/Sparkle/Sparkle/en.lproj/SUUpdatePermissionPrompt.xib b/Frameworks/Sparkle/Sparkle/en.lproj/SUUpdatePermissionPrompt.xib
index 149753b1e..c110598fb 100644
--- a/Frameworks/Sparkle/Sparkle/en.lproj/SUUpdatePermissionPrompt.xib
+++ b/Frameworks/Sparkle/Sparkle/en.lproj/SUUpdatePermissionPrompt.xib
@@ -44,7 +44,7 @@ DQ