diff options
-rw-r--r-- | SwifTools/AutoUpdater/AutoUpdater.h | 1 | ||||
-rw-r--r-- | SwifTools/AutoUpdater/SparkleAutoUpdater.h | 9 | ||||
-rw-r--r-- | SwifTools/AutoUpdater/SparkleAutoUpdater.mm | 90 |
3 files changed, 95 insertions, 5 deletions
diff --git a/SwifTools/AutoUpdater/AutoUpdater.h b/SwifTools/AutoUpdater/AutoUpdater.h index a125229..fdd3a91 100644 --- a/SwifTools/AutoUpdater/AutoUpdater.h +++ b/SwifTools/AutoUpdater/AutoUpdater.h @@ -1,40 +1,41 @@ /* * Copyright (c) 2010-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #pragma once #include <string> #include <boost/signals2.hpp> namespace Swift { class AutoUpdater { public: enum class State { NotCheckedForUpdatesYet, NoUpdateAvailable, CheckingForUpdate, ErrorCheckingForUpdate, DownloadingUpdate, RestartToInstallUpdate }; public: virtual ~AutoUpdater(); virtual void setAppcastFeed(const std::string& appcastFeed) = 0; virtual void checkForUpdates() = 0; virtual State getCurrentState() = 0; + virtual bool applicationInstallationLocationWritable() = 0; public: /** * Emit this signal if a new version of the software has been downloaded * and the user needs to be notified so they can quit the app and start * the newer version. */ boost::signals2::signal<void(State)> onUpdateStateChanged; }; } diff --git a/SwifTools/AutoUpdater/SparkleAutoUpdater.h b/SwifTools/AutoUpdater/SparkleAutoUpdater.h index 48b75e5..26e08da 100644 --- a/SwifTools/AutoUpdater/SparkleAutoUpdater.h +++ b/SwifTools/AutoUpdater/SparkleAutoUpdater.h @@ -1,37 +1,38 @@ /* * Copyright (c) 2010-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #pragma once #include <memory> #include <string> #include <SwifTools/AutoUpdater/AutoUpdater.h> namespace Swift { /** * @brief The SparkleAutoUpdater class provides integration with Sparkle. * This enables automatic silent background updates. If using this in Qt you * need to emit a NSApplicationWillTerminateNotification before you quit * the application. */ class SparkleAutoUpdater : public AutoUpdater { public: SparkleAutoUpdater(const std::string& appcastFeed); - ~SparkleAutoUpdater(); + ~SparkleAutoUpdater() override; - void setAppcastFeed(const std::string& appcastFeed); - void checkForUpdates(); - State getCurrentState(); + void setAppcastFeed(const std::string& appcastFeed) override; + void checkForUpdates() override; + State getCurrentState() override; + bool applicationInstallationLocationWritable() override; private: void setCurrentState(State updatedState); private: class Private; const std::unique_ptr<Private> d; }; } diff --git a/SwifTools/AutoUpdater/SparkleAutoUpdater.mm b/SwifTools/AutoUpdater/SparkleAutoUpdater.mm index 4cf5837..b4a4c05 100644 --- a/SwifTools/AutoUpdater/SparkleAutoUpdater.mm +++ b/SwifTools/AutoUpdater/SparkleAutoUpdater.mm @@ -1,70 +1,158 @@ /* * Copyright (c) 2016-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #include <SwifTools/AutoUpdater/SparkleAutoUpdater.h> + +#include <boost/filesystem.hpp> +#include <boost/filesystem/fstream.hpp> + #include <Cocoa/Cocoa.h> #include <Sparkle/Sparkle.h> +#include <Swiften/Base/Log.h> + #include <SwifTools/AutoUpdater/SparkleAutoUpdaterDelegate.h> #include <SwifTools/Cocoa/CocoaUtil.h> namespace Swift { class SparkleAutoUpdater::Private { public: SUUpdater* updater; boost::intrusive_ptr<SparkleAutoUpdaterDelegate> delegate; State currentState = State::NotCheckedForUpdatesYet; }; SparkleAutoUpdater::SparkleAutoUpdater(const std::string& appcastFeed) : d(new Private()) { d->updater = [SUUpdater sharedUpdater]; [d->updater retain]; d->delegate = boost::intrusive_ptr<SparkleAutoUpdaterDelegate>([[SparkleAutoUpdaterDelegate alloc] init], false); [d->delegate.get() setOnNewUpdateState: [&](AutoUpdater::State updatedState){ setCurrentState(updatedState); }]; [d->updater setDelegate: d->delegate.get()]; [d->updater setAutomaticallyChecksForUpdates: true]; // Automatically check for an update after a day. [d->updater setUpdateCheckInterval: 86400]; - [d->updater setAutomaticallyDownloadsUpdates: true]; + + auto canDoSilentUpdates = applicationInstallationLocationWritable(); + [d->updater setAutomaticallyDownloadsUpdates: canDoSilentUpdates]; + + SWIFT_LOG(debug) << (canDoSilentUpdates ? + "The current running user has enough permissions to do a silent update." : + "The current running user has insufficient permissions to do a silent update.") << std::endl; setAppcastFeed(appcastFeed); } SparkleAutoUpdater::~SparkleAutoUpdater() { [d->updater release]; } void SparkleAutoUpdater::setAppcastFeed(const std::string& appcastFeed) { NSURL* nsurl = [NSURL URLWithString: std2NSString(appcastFeed)]; [d->updater setFeedURL: nsurl]; } void SparkleAutoUpdater::checkForUpdates() { if (!(getCurrentState() == State::CheckingForUpdate || getCurrentState() == State::DownloadingUpdate || getCurrentState() == State::RestartToInstallUpdate)) { setCurrentState(State::CheckingForUpdate); [d->updater resetUpdateCycle]; [d->updater checkForUpdatesInBackground]; } } void SparkleAutoUpdater::setCurrentState(AutoUpdater::State updatedState) { d->currentState = updatedState; onUpdateStateChanged(d->currentState); } AutoUpdater::State SparkleAutoUpdater::getCurrentState() { return d->currentState; } +bool Swift::SparkleAutoUpdater::applicationInstallationLocationWritable() { + auto bundlePath = ns2StdString([[NSBundle mainBundle] bundlePath]); + + auto createTemporaryFile = [](const boost::filesystem::path& parentFolder) { + boost::optional<boost::filesystem::path> tempFilePath; + boost::system::error_code error; + + if (!boost::filesystem::is_directory(parentFolder, error) || error) { + return tempFilePath; + } + auto uniquePath = boost::filesystem::unique_path("%%%%-%%%%-%%%%-%%%%", error); + if (error) { + return tempFilePath; + } + auto testFilePath = parentFolder / uniquePath; + + boost::filesystem::ofstream testFile(testFilePath, std::ios::in | std::ios::out | std::ios::binary | std::ios::trunc); + if (testFile) { + testFile.close(); + tempFilePath = testFilePath; + } + + return tempFilePath; + }; + + auto isDirectoryWritable = [&](const boost::filesystem::path& path) { + auto bundleTestFilePath = createTemporaryFile(path); + if (!bundleTestFilePath) { + return false; + } + + boost::system::error_code error; + if (!boost::filesystem::remove(bundleTestFilePath.get(), error) || error) { + // File did not exist in the first place or error while removing it. + return false; + } + + return true; + }; + + auto applyMatchingPermissions = [](const boost::filesystem::path& permissionsFrom, const boost::filesystem::path& applyTo) { + auto permissions = boost::filesystem::status(permissionsFrom).permissions(); + + boost::system::error_code error; + boost::filesystem::permissions(applyTo, permissions, error); + + return !error; + }; + + auto canChangePermissionsOnTemporaryFile = [&](const boost::filesystem::path& pathToCreateTemporaryFileUnder, const boost::filesystem::path& pathToTakePermissionsFrom) { + auto temporaryFilePath = createTemporaryFile(pathToCreateTemporaryFileUnder); + if (!temporaryFilePath) { + return false; + } + + boost::system::error_code error; + auto fileExists = boost::filesystem::exists(temporaryFilePath.get(), error); + if (!fileExists || error) { + return false; + } + + auto successfullPermissionCopy = applyMatchingPermissions(pathToTakePermissionsFrom, temporaryFilePath.get()); + + boost::filesystem::remove(temporaryFilePath.get(), error); + + return successfullPermissionCopy; + }; + + auto bundleBoostPath = boost::filesystem::path(bundlePath); + if (!isDirectoryWritable(bundleBoostPath.parent_path()) || !isDirectoryWritable(bundleBoostPath)) { + return false; + } + + return canChangePermissionsOnTemporaryFile(bundleBoostPath.parent_path(), bundleBoostPath); +} + } |