Sandboxie/SandboxiePlus/SandMan/SandManRecovery.cpp

353 lines
12 KiB
C++

void CSandMan::OnFileToRecover(const QString& BoxName, const QString& FilePath, const QString& BoxPath, quint32 ProcessId)
{
CSandBoxPtr pBox = theAPI->GetBoxByName(BoxName);
if ((!pBox.isNull() && pBox.objectCast<CSandBoxPlus>()->IsRecoverySuspended()) || IsDisableRecovery())
return;
if (theConf->GetBool("Options/InstantRecovery", true))
{
CRecoveryWindow* pWnd = ShowRecovery(pBox, false);
//if (!theConf->GetBool("Options/AlwaysOnTop", false)) {
// SetWindowPos((HWND)pWnd->winId(), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
// QTimer::singleShot(100, this, [pWnd]() {
// SetWindowPos((HWND)pWnd->winId(), HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
// });
//}
if (pWnd)
pWnd->AddFile(FilePath, BoxPath);
}
else
m_pPopUpWindow->AddFileToRecover(FilePath, BoxPath, pBox, ProcessId);
}
bool CSandMan::OpenRecovery(const CSandBoxPtr& pBox, bool& DeleteShapshots, bool bCloseEmpty)
{
auto pBoxEx = pBox.objectCast<CSandBoxPlus>();
if (!pBoxEx) return false;
if (pBoxEx->m_pRecoveryWnd != NULL) {
if (pBoxEx->m_pRecoveryWnd->IsDeleteDialog())
return false;
pBoxEx->m_pRecoveryWnd->close();
}
CRecoveryWindow* pRecoveryWnd = pBoxEx->m_pRecoveryWnd = new CRecoveryWindow(pBox, false, this);
if (pBoxEx->m_pRecoveryWnd->FindFiles() == 0 && bCloseEmpty) {
delete pBoxEx->m_pRecoveryWnd;
pBoxEx->m_pRecoveryWnd = NULL;
return true;
}
else {
connect(pBoxEx->m_pRecoveryWnd, &CRecoveryWindow::Closed, [pBoxEx]() {
pBoxEx->m_pRecoveryWnd = NULL;
});
if (pBoxEx->m_pRecoveryWnd->exec() != 1)
return false;
}
DeleteShapshots = pRecoveryWnd->IsDeleteShapshots();
return true;
}
CRecoveryWindow* CSandMan::ShowRecovery(const CSandBoxPtr& pBox, bool bFind)
{
auto pBoxEx = pBox.objectCast<CSandBoxPlus>();
if (!pBoxEx) return NULL;
if (pBoxEx->m_pRecoveryWnd == NULL) {
pBoxEx->m_pRecoveryWnd = new CRecoveryWindow(pBox, bFind == false);
connect(pBoxEx->m_pRecoveryWnd, &CRecoveryWindow::Closed, [pBoxEx]() {
pBoxEx->m_pRecoveryWnd = NULL;
});
pBoxEx->m_pRecoveryWnd->show();
}
else if(bFind) { // We don't want to force window in front on instant recovery
pBoxEx->m_pRecoveryWnd->setWindowState((pBoxEx->m_pRecoveryWnd->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
SetForegroundWindow((HWND)pBoxEx->m_pRecoveryWnd->winId());
}
if(bFind)
pBoxEx->m_pRecoveryWnd->FindFiles();
return pBoxEx->m_pRecoveryWnd;
}
SB_PROGRESS CSandMan::CheckFiles(const QString& BoxName, const QStringList& Files)
{
CSbieProgressPtr pProgress = CSbieProgressPtr(new CSbieProgress());
CSandBoxPtr pBox = theAPI->GetBoxByName(BoxName);
QStringList Checkers;
if (!pBox.isNull()) {
foreach(const QString & Value, pBox->GetTextList("OnFileRecovery", true, false, true)) {
Checkers.append(pBox->Expand(Value));
}
}
QtConcurrent::run(CSandMan::CheckFilesAsync, pProgress, BoxName, Files, Checkers);
return SB_PROGRESS(OP_ASYNC, pProgress);
}
void CSandMan::CheckFilesAsync(const CSbieProgressPtr& pProgress, const QString& BoxName, const QStringList& Files, const QStringList& Checkers)
{
int FailCount = 0;
for (QStringList::const_iterator I = Files.begin(); I != Files.end(); ++I)
{
if (pProgress->IsCanceled()) break;
QString BoxPath = *I;
QString FileName = BoxPath.mid(BoxPath.lastIndexOf("\\") + 1);
pProgress->ShowMessage(tr("Checking file %1").arg(FileName));
foreach(const QString & Value, Checkers) {
QString Output;
int ret = CSbieUtils::ExecCommandEx(Value + " \"" + BoxPath + "\"", &Output, 15000); // 15 sec timeout
if (ret != 0) {
FailCount++;
QMetaObject::invokeMethod(theGUI, "ShowMessage", Qt::BlockingQueuedConnection, // show this message using the GUI thread
Q_ARG(QString, tr("The file %1 failed a security check!\r\n\r\n%2").arg(BoxPath).arg(Output)),
Q_ARG(int, QMessageBox::Warning)
);
}
}
}
if (FailCount == 0) {
QMetaObject::invokeMethod(theGUI, "ShowMessage", Qt::BlockingQueuedConnection, // show this message using the GUI thread
Q_ARG(QString, tr("All files passed the checks")),
Q_ARG(int, QMessageBox::Information)
);
}
pProgress->Finish(SB_OK);
}
SB_PROGRESS CSandMan::RecoverFiles(const QString& BoxName, const QList<QPair<QString, QString>>& FileList, int Action)
{
CSbieProgressPtr pProgress = CSbieProgressPtr(new CSbieProgress());
CSandBoxPtr pBox = theAPI->GetBoxByName(BoxName);
QStringList Checkers;
if (!pBox.isNull()) {
foreach(const QString & Value, pBox->GetTextList("OnFileRecovery", true, false, true)) {
Checkers.append(pBox->Expand(Value));
}
}
QtConcurrent::run(CSandMan::RecoverFilesAsync, pProgress, BoxName, FileList, Checkers, Action);
return SB_PROGRESS(OP_ASYNC, pProgress);
}
void CSandMan::RecoverFilesAsync(const CSbieProgressPtr& pProgress, const QString& BoxName, const QList<QPair<QString, QString>>& FileList, const QStringList& Checkers, int Action)
{
SB_STATUS Status = SB_OK;
int OverwriteOnExist = -1;
int RecoverCheckFailed = -1;
QStringList Unrecovered;
for (QList<QPair<QString, QString>>::const_iterator I = FileList.begin(); I != FileList.end(); ++I)
{
if (pProgress->IsCanceled()) break;
QString BoxPath = I->first;
QString RecoveryPath = I->second;
QString FileName = BoxPath.mid(BoxPath.lastIndexOf("\\") + 1);
QString RecoveryFolder = RecoveryPath.left(RecoveryPath.lastIndexOf("\\") + 1);
if (!Checkers.isEmpty()) {
pProgress->ShowMessage(tr("Checking file %1").arg(FileName));
//bool bNoGui = true;
//if (GetKeyState(VK_CONTROL) & 0x8000)
// bNoGui = false;
int ret = 0;
foreach(const QString & Value, Checkers) {
QString Output;
ret = CSbieUtils::ExecCommandEx(Value + " \"" + BoxPath + "\"", &Output, 15000); // 15 sec timeout
if (ret != 0) {
int Recover = RecoverCheckFailed;
if (Recover == -1)
{
bool forAll = false;
int retVal = 0;
QMetaObject::invokeMethod(theGUI, "ShowQuestion", Qt::BlockingQueuedConnection, // show this question using the GUI thread
Q_RETURN_ARG(int, retVal),
Q_ARG(QString, tr("The file %1 failed a security check, do you want to recover it anyways?\r\n\r\n%2").arg(BoxPath).arg(Output)),
Q_ARG(QString, tr("Do this for all files!")),
Q_ARG(bool*, &forAll),
Q_ARG(int, QDialogButtonBox::Yes | QDialogButtonBox::No),
Q_ARG(int, QDialogButtonBox::No),
Q_ARG(int, QMessageBox::Warning)
);
Recover = retVal == QDialogButtonBox::Yes ? 1 : 0;
if (forAll)
RecoverCheckFailed = Recover;
}
if (Recover == 1)
ret = 0;
else
break;
}
}
if (ret != 0)
continue; // Do not recover this file
}
pProgress->ShowMessage(tr("Recovering file %1 to %2").arg(FileName).arg(RecoveryFolder));
QDir().mkpath(RecoveryFolder);
if (QFile::exists(RecoveryPath))
{
int Overwrite = OverwriteOnExist;
if (Overwrite == -1)
{
bool forAll = false;
int retVal = 0;
QMetaObject::invokeMethod(theGUI, "ShowQuestion", Qt::BlockingQueuedConnection, // show this question using the GUI thread
Q_RETURN_ARG(int, retVal),
Q_ARG(QString, tr("The file %1 already exists, do you want to overwrite it?").arg(RecoveryPath)),
Q_ARG(QString, tr("Do this for all files!")),
Q_ARG(bool*, &forAll),
Q_ARG(int, QDialogButtonBox::Yes | QDialogButtonBox::No),
Q_ARG(int, QDialogButtonBox::No),
Q_ARG(int, QMessageBox::Question)
);
Overwrite = retVal == QDialogButtonBox::Yes ? 1 : 0;
if (forAll)
OverwriteOnExist = Overwrite;
}
if (Overwrite == 1)
QFile::remove(RecoveryPath);
}
if (!QFile::rename(BoxPath, RecoveryPath))
Unrecovered.append(BoxPath);
else {
QMetaObject::invokeMethod(theGUI, "OnFileRecovered", Qt::BlockingQueuedConnection, // show this question using the GUI thread
Q_ARG(QString, BoxName),
Q_ARG(QString, RecoveryPath),
Q_ARG(QString, BoxPath)
);
}
}
if (!Unrecovered.isEmpty())
Status = SB_ERR(SB_Message, QVariantList () << (tr("Failed to recover some files: \n") + Unrecovered.join("\n")));
else if(FileList.count() == 1 && Action != 0)
{
std::wstring path = FileList.first().second.toStdWString();
switch (Action)
{
case 1: // open
ShellExecute(NULL, NULL, path.c_str(), NULL, NULL, SW_SHOWNORMAL);
break;
case 2: // explore
ShellExecute(NULL, NULL, L"explorer.exe", (L"/select,\"" + path + L"\"").c_str(), NULL, SW_SHOWNORMAL);
break;
}
}
pProgress->Finish(Status);
}
void CSandMan::AddFileRecovered(const QString& BoxName, const QString& FilePath)
{
CPanelWidgetEx* pRecoveryLog = m_pRecoveryLog;
if (pRecoveryLog == NULL) {
pRecoveryLog = m_pRecoveryLogWnd->m_pRecoveryLog;
if (!pRecoveryLog) return;
}
QTreeWidgetItem* pItem = new QTreeWidgetItem(); // Time|Box|FilePath
pItem->setText(0, QDateTime::currentDateTime().toString("hh:mm:ss.zzz"));
pItem->setText(1, BoxName);
pItem->setText(2, FilePath);
pRecoveryLog->GetTree()->addTopLevelItem(pItem);
pRecoveryLog->GetView()->verticalScrollBar()->setValue(pRecoveryLog->GetView()->verticalScrollBar()->maximum());
}
void CSandMan::OnFileRecovered(const QString& BoxName, const QString& FilePath, const QString& BoxPath)
{
AddFileRecovered(BoxName, FilePath);
CSandBoxPtr pBox = theAPI->GetBoxByName(BoxName);
if (pBox)
pBox.objectCast<CSandBoxPlus>()->UpdateSize();
}
////////////////////////////////////////////////////////////////////////////////////////
// CRecoveryLogWnd
CRecoveryLogWnd::CRecoveryLogWnd(QWidget *parent)
: QDialog(parent)
{
Qt::WindowFlags flags = windowFlags();
flags |= Qt::CustomizeWindowHint;
//flags &= ~Qt::WindowContextHelpButtonHint;
//flags &= ~Qt::WindowSystemMenuHint;
//flags &= ~Qt::WindowMinMaxButtonsHint;
//flags |= Qt::WindowMinimizeButtonHint;
//flags &= ~Qt::WindowCloseButtonHint;
flags &= ~Qt::WindowContextHelpButtonHint;
//flags &= ~Qt::WindowSystemMenuHint;
setWindowFlags(flags);
this->setWindowTitle(tr("Sandboxie-Plus - Recovery Log"));
QGridLayout* pLayout = new QGridLayout();
//pLayout->setContentsMargins(3,3,3,3);
m_pRecoveryLog = new CPanelWidgetEx();
m_pRecoveryLog->GetTree()->setItemDelegate(new CTreeItemDelegate());
m_pRecoveryLog->GetTree()->setAlternatingRowColors(theConf->GetBool("Options/AltRowColors", false));
//m_pRecoveryLog->GetView()->setItemDelegate(theGUI->GetItemDelegate());
((QTreeWidgetEx*)m_pRecoveryLog->GetView())->setHeaderLabels(tr("Time|Box Name|File Path").split("|"));
QAction* pAction = new QAction(tr("Cleanup Recovery Log"));
connect(pAction, SIGNAL(triggered()), m_pRecoveryLog->GetTree(), SLOT(clear()));
m_pRecoveryLog->GetMenu()->insertAction(m_pRecoveryLog->GetMenu()->actions()[0], pAction);
m_pRecoveryLog->GetMenu()->insertSeparator(m_pRecoveryLog->GetMenu()->actions()[0]);
m_pRecoveryLog->GetView()->setSelectionMode(QAbstractItemView::ExtendedSelection);
m_pRecoveryLog->GetView()->setSortingEnabled(false);
connect(m_pRecoveryLog->GetTree(), SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(OnDblClick(QTreeWidgetItem*)));
pLayout->addWidget(new QLabel(tr("The following files were recently recovered and moved out of a sandbox.")), 0, 0);
pLayout->addWidget(m_pRecoveryLog, 1, 0);
this->setLayout(pLayout);
restoreGeometry(theConf->GetBlob("RecoveryLogWindow/Window_Geometry"));
}
CRecoveryLogWnd::~CRecoveryLogWnd()
{
theConf->SetBlob("RecoveryLogWindow/Window_Geometry", saveGeometry());
}
void CRecoveryLogWnd::closeEvent(QCloseEvent *e)
{
emit Closed();
//this->deleteLater();
}
void CRecoveryLogWnd::OnDblClick(QTreeWidgetItem* pItem)
{
ShellExecute(NULL, NULL, L"explorer.exe", (L"/select,\"" + pItem->text(2).toStdWString() + L"\"").c_str(), NULL, SW_SHOWNORMAL);
}
void CSandMan::OnRecoveryLog()
{
if (!m_pRecoveryLogWnd->isVisible()) {
bool bAlwaysOnTop = theConf->GetBool("Options/AlwaysOnTop", false);
m_pRecoveryLogWnd->setWindowFlag(Qt::WindowStaysOnTopHint, bAlwaysOnTop);
SafeShow(m_pRecoveryLogWnd);
}
}