462 lines
14 KiB
C++
462 lines
14 KiB
C++
#include "stdafx.h"
|
|
#include "AddonManager.h"
|
|
#include "SandMan.h"
|
|
#include "OnlineUpdater.h"
|
|
#include "../MiscHelpers/Common/Common.h"
|
|
#include "../MiscHelpers/Common/OtherFunctions.h"
|
|
#include <QUrlQuery>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include "../QSbieAPI/Sandboxie/SbieTemplates.h"
|
|
#include <QtConcurrent>
|
|
#include "../MiscHelpers/Archive/Archive.h"
|
|
|
|
|
|
#include <Windows.h>
|
|
|
|
CAddonManager::CAddonManager(QObject* parent)
|
|
: QObject(parent)
|
|
{
|
|
|
|
}
|
|
|
|
void CAddonManager::UpdateAddonsWhenNotCached()
|
|
{
|
|
QVariantMap Data = theGUI->m_pUpdater->GetUpdateData();
|
|
if (!Data.isEmpty() && Data.contains("addons") && theGUI->m_pUpdater->GetLastUpdateTime() > QDateTime::currentDateTime().addDays(-1)) {
|
|
OnUpdateData(Data, QVariantMap());
|
|
return;
|
|
}
|
|
else if (!m_Addons.isEmpty()) {
|
|
QFileInfo info(theConf->GetConfigDir() + "/addons.json");
|
|
if (info.birthTime() > QDateTime::currentDateTime().addDays(-1))
|
|
return;
|
|
}
|
|
|
|
UpdateAddons();
|
|
}
|
|
|
|
void CAddonManager::UpdateAddons()
|
|
{
|
|
theGUI->m_pUpdater->GetUpdates(this, SLOT(OnUpdateData(const QVariantMap&, const QVariantMap&)));
|
|
}
|
|
|
|
void CAddonManager::OnUpdateData(const QVariantMap& Data, const QVariantMap& Params)
|
|
{
|
|
if (Data.isEmpty() || Data["error"].toBool())
|
|
return;
|
|
|
|
QVariantMap Addons = Data["addons"].toMap();
|
|
|
|
QJsonDocument doc(QJsonValue::fromVariant(Addons).toObject());
|
|
QFile::remove(theConf->GetConfigDir() + "/addons.json");
|
|
WriteStringToFile(theConf->GetConfigDir() + "/addons.json", doc.toJson());
|
|
|
|
LoadAddons();
|
|
emit DataUpdated();
|
|
}
|
|
|
|
QList<CAddonPtr> CAddonManager::GetAddons()
|
|
{
|
|
if (m_Addons.isEmpty()) {
|
|
if (!LoadAddons())
|
|
UpdateAddons();
|
|
}
|
|
else {
|
|
foreach(const CAddonPtr& pAddon, m_Addons)
|
|
pAddon->Installed = CheckAddon(pAddon);
|
|
}
|
|
return m_Addons;
|
|
}
|
|
|
|
bool CAddonManager::LoadAddons()
|
|
{
|
|
m_Addons.clear();
|
|
|
|
QString AddonPath = theConf->GetConfigDir() + "/addons.json";
|
|
QVariantMap Data = QJsonDocument::fromJson(ReadFileAsString(AddonPath).toUtf8()).toVariant().toMap();
|
|
foreach(const QVariant vAddon, Data["list"].toList()) {
|
|
CAddonPtr pAddon = CAddonPtr(new CAddon(vAddon.toMap()));
|
|
pAddon->Installed = CheckAddon(pAddon);
|
|
m_Addons.append(pAddon);
|
|
}
|
|
|
|
return !m_Addons.isEmpty();
|
|
}
|
|
|
|
CAddonPtr CAddonManager::GetAddon(const QString& Id)
|
|
{
|
|
if (m_Addons.isEmpty())
|
|
LoadAddons();
|
|
|
|
foreach(const CAddonPtr& pAddon, m_Addons) {
|
|
if (pAddon->Id.compare(Id, Qt::CaseInsensitive) == 0) {
|
|
pAddon->Installed = CheckAddon(pAddon);
|
|
return pAddon;
|
|
}
|
|
}
|
|
return CAddonPtr();
|
|
}
|
|
|
|
bool CAddonManager::HasAddon(const QString& Id)
|
|
{
|
|
CAddonPtr pAddon = GetAddon("FileChecker");
|
|
return pAddon && pAddon->Installed;
|
|
}
|
|
|
|
bool CAddonManager::CheckAddon(const CAddonPtr& pAddon)
|
|
{
|
|
QString Key = pAddon->GetSpecificEntry("uninstall_key").toString();
|
|
if (!Key.isEmpty()) {
|
|
QSettings settings(Key, QSettings::NativeFormat);
|
|
QString Uninstall = settings.value("UninstallString").toString();
|
|
return !Uninstall.isEmpty();
|
|
}
|
|
|
|
QStringList Files = pAddon->GetSpecificEntry("files").toStringList();
|
|
foreach(const QString & File, Files) {
|
|
if (theGUI->GetCompat()->CheckFile(ExpandPath(File)))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
SB_PROGRESS CAddonManager::TryInstallAddon(const QString& Id, QWidget* pParent, const QString& Prompt)
|
|
{
|
|
if (QMessageBox("Sandboxie-Plus", Prompt.isEmpty() ? tr("Do you want to download and install %1?").arg(Id) : Prompt,
|
|
QMessageBox::Question, QMessageBox::Yes | QMessageBox::Default, QMessageBox::No | QMessageBox::Escape, QMessageBox::NoButton, pParent ? pParent : theGUI).exec() != QMessageBox::Yes)
|
|
return SB_ERR(SB_OtherError);
|
|
|
|
SB_PROGRESS Status = InstallAddon(Id);
|
|
if (Status.GetStatus() == OP_ASYNC)
|
|
theGUI->AddAsyncOp(Status.GetValue(), false, tr("Installing: %1").arg(Id), pParent);
|
|
else
|
|
theGUI->CheckResults(QList<SB_STATUS>() << Status, pParent);
|
|
return Status;
|
|
}
|
|
|
|
SB_PROGRESS CAddonManager::InstallAddon(const QString& Id)
|
|
{
|
|
CAddonPtr pAddon = GetAddon(Id);
|
|
if (!pAddon)
|
|
return SB_ERR(SB_OtherError, QVariantList() << tr("Addon not found, please try updating the addon list in the global settings!"));
|
|
if (pAddon->Installed)
|
|
return SB_ERR(SB_OtherError, QVariantList() << tr("Addon already installed!"));
|
|
|
|
QString Entry;
|
|
QString Url = pAddon->GetSpecificEntry("download", &Entry).toString();
|
|
if (Url.isEmpty())
|
|
return SB_ERR(SB_OtherError, QVariantList() << tr("Addon has no download url, addon may not be available for your platform."));
|
|
|
|
QVariantMap Params;
|
|
Params["name"] = Id;
|
|
Params["path"] = theGUI->m_pUpdater->GetUpdateDir(true) + "/" + QUrl(Url).fileName();
|
|
Params["signature"] = pAddon->Data.value(Entry + "_sig");
|
|
theGUI->m_pUpdater->DownloadFile(Url, this, SLOT(OnAddonDownloaded(const QString&, const QVariantMap&)), Params);
|
|
|
|
pAddon->pProgress = CSbieProgressPtr(new CSbieProgress());
|
|
connect(pAddon->pProgress.data(), SIGNAL(Finished()), this, SIGNAL(AddonInstalled()));
|
|
pAddon->pProgress->ShowMessage(tr("Downloading Addon %1").arg(pAddon->Id));
|
|
return SB_PROGRESS(OP_ASYNC, pAddon->pProgress);
|
|
}
|
|
|
|
extern "C" NTSTATUS VerifyFileSignatureImpl(const wchar_t* FilePath, PVOID Signature, ULONG SignatureSize);
|
|
|
|
void CAddonManager::OnAddonDownloaded(const QString& Path, const QVariantMap& Params)
|
|
{
|
|
CAddonPtr pAddon = GetAddon(Params["name"].toString());
|
|
|
|
QByteArray Signature = QByteArray::fromBase64(Params["signature"].toByteArray());
|
|
|
|
if (VerifyFileSignatureImpl(QString(Path).replace("/","\\").toStdWString().c_str(), Signature.data(), Signature.size()) < 0) { // !NT_SUCCESS
|
|
pAddon->pProgress->Finish(SB_ERR(SB_OtherError, QVariantList() << tr("Download signature is not valid!")));
|
|
pAddon->pProgress.create();
|
|
return;
|
|
}
|
|
|
|
pAddon->pProgress->ShowMessage(tr("Installing Addon %1").arg(pAddon->Id));
|
|
|
|
QtConcurrent::run(CAddonManager::InstallAddonAsync, Path, pAddon);
|
|
}
|
|
|
|
void CAddonManager::InstallAddonAsync(const QString& FilePath, CAddonPtr pAddon)
|
|
{
|
|
SB_STATUS Status = SB_OK;
|
|
|
|
CArchive Archive(FilePath);
|
|
|
|
if (Archive.Open() == 1)
|
|
{
|
|
QString FileDir = Split2(FilePath, ".", true).first.replace("/", "\\");
|
|
if (Archive.Extract(FileDir)) {
|
|
|
|
QString Cmd = pAddon->GetSpecificEntry("installer").toString();
|
|
QString Path = ExpandPath(pAddon->GetSpecificEntry("install_path").toString());
|
|
if (!Cmd.isEmpty() && QFile::exists(FileDir + Cmd))
|
|
{
|
|
pAddon->pProgress->ShowMessage(tr("Running Installer for %1").arg(pAddon->Id));
|
|
|
|
std::wstring sbiehome = theAPI->GetSbiePath().toStdWString();
|
|
std::wstring plusdata = theConf->GetConfigDir().toStdWString();
|
|
|
|
LPWCH environmentStrings = GetEnvironmentStrings();
|
|
|
|
DWORD environmentLen = 0;
|
|
for (LPWCH current = environmentStrings; *current; current += wcslen(current) + 1)
|
|
environmentLen += wcslen(current) + 1;
|
|
|
|
LPWCH modifiedEnvironment = (LPWCH)LocalAlloc(0, (environmentLen + sbiehome.length() + 1 + plusdata.length() + 1 + 1) * sizeof(wchar_t));
|
|
memcpy(modifiedEnvironment, environmentStrings, (environmentLen + 1) * sizeof(wchar_t));
|
|
|
|
FreeEnvironmentStrings(environmentStrings);
|
|
|
|
LPWCH modifiedEnvironmentEnd = modifiedEnvironment + environmentLen;
|
|
|
|
wcscpy(modifiedEnvironmentEnd, L"SBIEHOME=");
|
|
wcscat(modifiedEnvironmentEnd, sbiehome.c_str());
|
|
modifiedEnvironmentEnd += wcslen(modifiedEnvironmentEnd) + 1;
|
|
|
|
wcscpy(modifiedEnvironmentEnd, L"PLUSDATA=");
|
|
wcscat(modifiedEnvironmentEnd, plusdata.c_str());
|
|
modifiedEnvironmentEnd += wcslen(modifiedEnvironmentEnd) + 1;
|
|
|
|
*modifiedEnvironmentEnd = 0;
|
|
|
|
STARTUPINFO si = { sizeof(si), 0 };
|
|
PROCESS_INFORMATION pi = { 0 };
|
|
if (CreateProcessW(NULL, (wchar_t*)(FileDir + Cmd).toStdWString().c_str(), NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT, modifiedEnvironment, NULL, &si, &pi))
|
|
{
|
|
while (WaitForSingleObject(pi.hProcess, 1000) == WAIT_TIMEOUT && !pAddon->pProgress->IsCanceled());
|
|
CloseHandle(pi.hProcess);
|
|
CloseHandle(pi.hThread);
|
|
}
|
|
else
|
|
Status = SB_ERR(SB_OtherError, QVariantList() << tr("Failed to start installer (%1)!").arg(GetLastError()));
|
|
|
|
LocalFree(modifiedEnvironment);
|
|
}
|
|
else if (!Path.isEmpty())
|
|
{
|
|
pAddon->pProgress->ShowMessage(tr("Copying Files for %1").arg(pAddon->Id));
|
|
|
|
std::wstring from;
|
|
foreach(const QString & file, ListDir(FileDir)) {
|
|
QString File = QString(file).replace("/", "\\");
|
|
from.append((FileDir + "\\" + File).toStdWString());
|
|
from.append(L"\0", 1);
|
|
}
|
|
from.append(L"\0", 1);
|
|
|
|
std::wstring to;
|
|
to.append(Path.toStdWString());
|
|
to.append(L"\0", 1);
|
|
|
|
SHFILEOPSTRUCT SHFileOp;
|
|
memset(&SHFileOp, 0, sizeof(SHFILEOPSTRUCT));
|
|
SHFileOp.hwnd = NULL;
|
|
SHFileOp.wFunc = FO_MOVE;
|
|
SHFileOp.pFrom = from.c_str();
|
|
SHFileOp.pTo = to.c_str();
|
|
SHFileOp.fFlags = FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR;
|
|
|
|
SHFileOperation(&SHFileOp);
|
|
}
|
|
|
|
QDir(FileDir).removeRecursively();
|
|
}
|
|
else
|
|
Status = SB_ERR(SB_OtherError, QVariantList() << tr("Failed to unpack addon!"));
|
|
Archive.Close();
|
|
}
|
|
|
|
QFile::remove(FilePath);
|
|
|
|
if (!Status.IsError()) {
|
|
pAddon->Installed = CheckAddon(pAddon);
|
|
if (!pAddon->Installed)
|
|
Status = SB_ERR(SB_OtherError, QVariantList() << tr("Addon Installation Failed!"));
|
|
}
|
|
pAddon->pProgress->Finish(Status);
|
|
pAddon->pProgress.create();
|
|
}
|
|
|
|
SB_PROGRESS CAddonManager::TryRemoveAddon(const QString& Id, QWidget* pParent)
|
|
{
|
|
if (QMessageBox("Sandboxie-Plus", tr("Do you want to remove %1?").arg(Id),
|
|
QMessageBox::Question, QMessageBox::Yes | QMessageBox::Default, QMessageBox::No | QMessageBox::Escape, QMessageBox::NoButton, pParent ? pParent : theGUI).exec() != QMessageBox::Yes)
|
|
return SB_ERR(SB_OtherError);
|
|
|
|
SB_PROGRESS Status = RemoveAddon(Id);
|
|
if (Status.GetStatus() == OP_ASYNC)
|
|
theGUI->AddAsyncOp(Status.GetValue(), false, tr("Removing: %1").arg(Id), pParent);
|
|
else
|
|
theGUI->CheckResults(QList<SB_STATUS>() << Status, pParent);
|
|
return Status;
|
|
}
|
|
|
|
SB_PROGRESS CAddonManager::RemoveAddon(const QString& Id)
|
|
{
|
|
CAddonPtr pAddon = GetAddon(Id);
|
|
if (!pAddon)
|
|
return SB_ERR(SB_OtherError, QVariantList() << tr("Addon not found!"));
|
|
|
|
pAddon->pProgress = CSbieProgressPtr(new CSbieProgress());
|
|
QtConcurrent::run(CAddonManager::RemoveAddonAsync, pAddon);
|
|
return SB_PROGRESS(OP_ASYNC, pAddon->pProgress);
|
|
}
|
|
|
|
void CAddonManager::CleanupPath(const QString& Path)
|
|
{
|
|
StrPair PathName = Split2(Path, "\\", true);
|
|
if (ListDir(PathName.first).isEmpty())
|
|
{
|
|
QDir().rmdir(PathName.first);
|
|
//qDebug() << "delete dir" << PathName.first;
|
|
CleanupPath(PathName.first);
|
|
}
|
|
}
|
|
|
|
void CAddonManager::RemoveAddonAsync(CAddonPtr pAddon)
|
|
{
|
|
SB_STATUS Status = SB_OK;
|
|
|
|
QString Key = pAddon->GetSpecificEntry("uninstall_key").toString();
|
|
if (!Key.isEmpty())
|
|
{
|
|
QSettings settings(Key, QSettings::NativeFormat);
|
|
QString Cmd = settings.value("UninstallString").toString();
|
|
|
|
pAddon->pProgress->ShowMessage(tr("Running Uninstaller for %1").arg(pAddon->Id));
|
|
|
|
STARTUPINFO si = { sizeof(si), 0 };
|
|
PROCESS_INFORMATION pi = { 0 };
|
|
if (CreateProcessW(NULL, (wchar_t*)Cmd.toStdWString().c_str(), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
|
|
{
|
|
while (WaitForSingleObject(pi.hProcess, 1000) == WAIT_TIMEOUT && !pAddon->pProgress->IsCanceled());
|
|
CloseHandle(pi.hProcess);
|
|
CloseHandle(pi.hThread);
|
|
}
|
|
else
|
|
Status = SB_ERR(SB_OtherError, QVariantList() << tr("Failed to start uninstaller!"));
|
|
}
|
|
else
|
|
{
|
|
QStringList Files = pAddon->GetSpecificEntry("files").toStringList();
|
|
//foreach(const QString & File, Files) {
|
|
// pAddon->pProgress->ShowMessage(tr("Removing %1").arg(File));
|
|
// QString FilePath = ExpandPath(File);
|
|
// QFile::remove(FilePath);
|
|
// CleanupPath(FilePath);
|
|
//}
|
|
|
|
std::wstring from;
|
|
foreach(const QString & File, Files) {
|
|
QString FilePath = ExpandPath(File);
|
|
if (QFile::exists(FilePath)) {
|
|
from.append(FilePath.toStdWString());
|
|
from.append(L"\0", 1);
|
|
}
|
|
}
|
|
from.append(L"\0", 1);
|
|
|
|
SHFILEOPSTRUCT SHFileOp;
|
|
memset(&SHFileOp, 0, sizeof(SHFILEOPSTRUCT));
|
|
SHFileOp.hwnd = NULL;
|
|
SHFileOp.wFunc = FO_DELETE;
|
|
SHFileOp.pFrom = from.c_str();
|
|
SHFileOp.pTo = NULL;
|
|
SHFileOp.fFlags = FOF_NOCONFIRMATION;
|
|
|
|
SHFileOperation(&SHFileOp);
|
|
}
|
|
|
|
if (!Status.IsError()) {
|
|
pAddon->Installed = CheckAddon(pAddon);
|
|
if (pAddon->Installed)
|
|
Status = SB_ERR(SB_OtherError, QVariantList() << tr("Addon Removal Failed!"));
|
|
}
|
|
pAddon->pProgress->Finish(Status);
|
|
pAddon->pProgress.create();
|
|
}
|
|
|
|
QString CAddonManager::ExpandPath(QString Path)
|
|
{
|
|
Path.replace("%SbieHome%", theAPI->GetSbiePath(), Qt::CaseInsensitive);
|
|
Path.replace("%PlusData%", theConf->GetConfigDir(), Qt::CaseInsensitive);
|
|
|
|
return theGUI->GetCompat()->ExpandPath(Path);
|
|
}
|
|
|
|
QString GetArch()
|
|
{
|
|
SYSTEM_INFO systemInfo;
|
|
GetSystemInfo(&systemInfo);
|
|
switch (systemInfo.wProcessorArchitecture)
|
|
{
|
|
case PROCESSOR_ARCHITECTURE_INTEL: return "x86";
|
|
case PROCESSOR_ARCHITECTURE_AMD64: return "x64";
|
|
case PROCESSOR_ARCHITECTURE_ARM64: return "a64";
|
|
}
|
|
return "???";
|
|
}
|
|
|
|
QVariant CAddon::GetSpecificEntry(const QString& Name, QString* pName)
|
|
{
|
|
#ifdef _M_ARM64
|
|
QString arch = "a64";
|
|
#elif _WIN64
|
|
QString arch = "x64";
|
|
#else
|
|
QString arch = "x86";
|
|
#endif
|
|
|
|
//
|
|
// First we check the qt cpecific entry for our version of qt and platform
|
|
//
|
|
|
|
QString qt = QString("qt%1_%2_%3_%4").arg(QT_VERSION_MAJOR).arg(QT_VERSION_MINOR).arg(QT_VERSION_PATCH).arg(arch);
|
|
#ifdef _DEBUG
|
|
qt.append("d");
|
|
#endif // _DEBUG
|
|
if (Data.contains(Name + "_" + qt)) {
|
|
if (pName) *pName = Name + "_" + qt;
|
|
return Data[Name + "_" + qt];
|
|
}
|
|
|
|
//
|
|
// Second we check the actual architecture
|
|
//
|
|
|
|
QString match = Data["match_arch"].toString();
|
|
if (match != "agent")
|
|
arch = GetArch();
|
|
if (Data.contains(Name + "_" + arch)) {
|
|
if (pName) *pName = Name + "_" + arch;
|
|
return Data[Name + "_" + arch];
|
|
}
|
|
|
|
//
|
|
// last we try the unsoecific entry
|
|
//
|
|
|
|
if (Data.contains(Name)) {
|
|
if (pName) *pName = Name;
|
|
return Data[Name];
|
|
}
|
|
|
|
return QString();
|
|
}
|
|
|
|
QString CAddon::GetLocalizedEntry(const QString& Name)
|
|
{
|
|
if (Data.contains(Name + "_" + theGUI->m_Language))
|
|
return Data[Name + "_" + theGUI->m_Language].toString();
|
|
|
|
QString LangAux = theGUI->m_Language; // Short version as fallback
|
|
LangAux.truncate(LangAux.lastIndexOf('_'));
|
|
if (Data.contains(Name + "_" + LangAux))
|
|
return Data[Name + "_" + LangAux].toString();
|
|
|
|
return Data[Name].toString();
|
|
}
|