#include "stdafx.h" #include "BoxAssistant.h" #include "../MiscHelpers/Common/Common.h" #include "../MiscHelpers/Common/OtherFunctions.h" #include "../Windows/SettingsWindow.h" #include "../Windows/SelectBoxWindow.h" #include "../SandMan.h" #include "Helpers/WinAdmin.h" #include #include #include "../QSbieAPI/SbieUtils.h" #include "../Engine/BoxEngine.h" #include "../Engine/SysObject.h" #include "OnlineUpdater.h" #include "../MiscHelpers/Archive/Archive.h" #include "../MiscHelpers/Archive/ArchiveFS.h" #include #include #include "../MiscHelpers/Common/CheckableMessageBox.h" #include #include "../Views/TraceView.h" #include "../AddonManager.h" QList ReadCommentHeader(const QString& Header) { QList HeaderFields; QStringList HeaderLines = Header.split("\n"); foreach(QString Line, HeaderLines) { Line = Line.trimmed(); if(Line.left(1) == "*") { Line.remove(0,1); Line = Line.trimmed(); } StrPair Field = Split2(Line, ":"); if(Field.first.isEmpty() || Field.first.contains(" ")) continue; HeaderFields.append(Field); } return HeaderFields; } CBoxAssistant::CBoxAssistant(QWidget *parent) : QWizard(parent) { setWindowTitle(tr("Troubleshooting Wizard")); m_pEngine = NULL; m_bUseDebugger = false; m_pDebugger = NULL; QAction* pDbgAction = new QAction(tr("Toggle Debugger")); pDbgAction->setShortcut(QKeySequence("Ctrl+Shift+D")); connect(pDbgAction, SIGNAL(triggered()), this, SLOT(OnToggleDebugger())); addAction(pDbgAction); m_NextCounter = 0; setPage(Page_Begin, new CBeginPage); setPage(Page_Group, new CGroupPage); setPage(Page_List, new CListPage); setPage(Page_Run, new CRunPage); setPage(Page_Submit, new CSubmitPage); setPage(Page_Complete, new CCompletePage); setWizardStyle(ModernStyle); setPixmap(QWizard::LogoPixmap, QPixmap(":/SandMan.png").scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation)); // // Load Issues, when the user has an own troubleshooting folder, don't load the 7z or online // C7zFileEngineHandler IssueFS("issue"); LoadIssues(GetIssueDir(IssueFS, &m_IssueDate)); if (m_IssueDate.isValid()) { if (theConf->GetInt("Options/CheckForIssues", 2) == 1) theGUI->m_pUpdater->GetUpdates(this, SLOT(OnUpdateData(const QVariantMap&, const QVariantMap&))); } } CBoxAssistant::~CBoxAssistant() { } void CBoxAssistant::OnToggleDebugger() { m_bUseDebugger = !m_bUseDebugger; if (m_bUseDebugger && !theGUI->GetAddonManager()->HasAddon("V4dbg")) theGUI->GetAddonManager()->TryInstallAddon("V4dbg", this, tr("To debug troubleshooting scripts you need the V4 Script Debugger addon, do you want to download and install it?")); QString title = windowTitle(); if (m_bUseDebugger) setWindowTitle(title + " - " + tr("Debugger Enabled")); else setWindowTitle(title.mid(0, title.indexOf(" - "))); } QString CBoxAssistant::GetIssueDir(C7zFileEngineHandler& IssueFS, QDateTime* pDate) { QString IssueDir = theConf->GetConfigDir() + "/troubleshooting/"; if (!QFile::exists(IssueDir)) { QFileInfo Installed(QApplication::applicationDirPath() + "/troubleshooting.7z"); QFileInfo Latest(theConf->GetConfigDir() + "/troubleshooting.7z"); quint64 latest = Latest.lastModified().toSecsSinceEpoch(); quint64 installed = Installed.lastModified().toSecsSinceEpoch(); if (latest >= installed && IssueFS.Open(theConf->GetConfigDir() + "/troubleshooting.7z")) { IssueDir = IssueFS.Prefix() + "/"; if(pDate) *pDate = Latest.lastModified(); } else if (IssueFS.Open(QApplication::applicationDirPath() + "/troubleshooting.7z")) { IssueDir = IssueFS.Prefix() + "/"; if(pDate) *pDate = Installed.lastModified(); } } return IssueDir; } void CBoxAssistant::OnUpdateData(const QVariantMap& Data, const QVariantMap& Params) { if (Data.isEmpty() || Data["error"].toBool()) return; QVariantMap Issues = Data["issues"].toMap(); quint64 Date = Issues["date"].toULongLong(); if (Date >= m_IssueDate.toSecsSinceEpoch()) { QString Download = Issues["download"].toString(); QVariantMap Params; Params["path"] = theConf->GetConfigDir() + "/troubleshooting.tmp"; Params["setDate"] = QDateTime::fromSecsSinceEpoch(Date); Params["signature"] = Issues["signature"]; theGUI->m_pUpdater->DownloadFile(Download, this, SLOT(OnDownload(const QString&, const QVariantMap&)), Params); } } extern "C" long VerifyFileSignatureImpl(const wchar_t* FilePath, void* Signature, unsigned long SignatureSize); void CBoxAssistant::OnDownload(const QString& Path, const QVariantMap& Params) { QByteArray Signature = QByteArray::fromBase64(Params["signature"].toByteArray()); if (VerifyFileSignatureImpl(QString(Path).replace("/","\\").toStdWString().c_str(), Signature.data(), Signature.size()) < 0) { // !NT_SUCCESS QFile::remove(Path); return; } QString FinalPath = theConf->GetConfigDir() + "/troubleshooting.7z"; QFile::remove(FinalPath); QFile::rename(Path, FinalPath); QString IssueDir; C7zFileEngineHandler IssueFS("issue"); if (!IssueFS.Open(FinalPath)) { QMessageBox::critical(this, "Sandboxie-Plus", tr("Downloaded troubleshooting instructions are currupted!")); QFile::remove(Path); return; } LoadIssues(IssueFS.Prefix() + "/"); CBeginPage* pBegin = qobject_cast(currentPage()); if (pBegin) pBegin->initializePage(); } void CBoxAssistant::LoadIssues(const QString& IssueDir) { QVariantMap Issues = QJsonDocument::fromJson(ReadFileAsString(IssueDir + "layout.json").toUtf8()).toVariant().toMap(); QVariantList Entries = Issues.value("entries").toList(); if (Entries.isEmpty()) { QMessageBox::critical(this, "Sandboxie-Plus", tr("Fatal error, failed to load troubleshooting instructions!")); return; } m_GroupedIssues.clear(); quint32 OsBuild = JSysObject::GetOSVersion()["build"].toUInt(); //QDir Dir(IssueDir); //foreach(const QFileInfo & Info, Dir.entryInfoList(QStringList() << "*.js", QDir::Files)) { foreach(const QString & FileName, ListDir(IssueDir, QStringList() << "*.js")) { QFileInfo Info(IssueDir + FileName); QString Script = ReadFileAsString(Info.filePath()); int HeaderBegin = Script.indexOf("/*"); int HeaderEnd = Script.indexOf("*/"); if(HeaderBegin == -1 || HeaderEnd == -1) continue; // Header is mandatory if(HeaderBegin != 0) { qDebug() << "Bad Header of" << Info.fileName(); continue; } QVariantMap Issue; Issue["id"] = Info.fileName().left(Info.fileName().length() - 3); Issue["type"] = "issue"; foreach(const StrPair& KeyValue, ReadCommentHeader(Script.mid(HeaderBegin + 2, HeaderEnd - (HeaderBegin + 2)))) Issue[KeyValue.first] = KeyValue.second; Issue["script"] = Script; bool NotApplicable = false; if (Issue.contains("versions")) { NotApplicable = true; foreach(const QString & V, SplitStr(Issue["versions"].toString(), ",")) { StrPair VV = Split2(V, "-"); if ((VV.second.isEmpty() && COnlineUpdater::VersionToInt(VV.first) == COnlineUpdater::CurrentVersion()) || // exact version match (!VV.second.isEmpty() && (COnlineUpdater::VersionToInt(VV.first) <= COnlineUpdater::CurrentVersion() && COnlineUpdater::VersionToInt(VV.second) >= COnlineUpdater::CurrentVersion()))) { // inside version range NotApplicable = false; break; } } } if (!NotApplicable && Issue.contains("os_builds")) { NotApplicable = true; foreach(const QString & V, SplitStr(Issue["os_builds"].toString(), ",")) { StrPair VV = Split2(V, "-"); if ((VV.second.isEmpty() && VV.first.toUInt() == OsBuild) || // exact version match (!VV.second.isEmpty() && (VV.first.toUInt() <= OsBuild && VV.second.toUInt() >= OsBuild))) { // inside version range NotApplicable = false; break; } } } if (NotApplicable) continue; Entries.append(Issue); } foreach(const QVariant & vIssue, Entries) { QVariantMap Issue = vIssue.toMap(); QList& Group = m_GroupedIssues[Issue["group"].toString()]; // Note: This way we can define order in the layout json and have the issue scripts loaded at the right place QString ID = Issue["id"].toString(); auto I = std::find_if(Group.begin(), Group.end(), [ID](const QVariantMap& cur)->int { return cur["id"] == ID; }); if (I == Group.end()) Group.append(Issue); else { if (I->contains("script")) { QMessageBox::warning(this, "Sandboxie-Plus", tr("Error, troubleshooting instructions duplicated %1 (%2 <-> %3)!") .arg(ID).arg(I->value("id").toString()).arg(Issue.value("id").toString())); } for(auto J = Issue.begin(); J != Issue.end(); ++J) I->insert(J.key(), J.value()); } } // // Load Translations // QString Translation = ReadFileAsString(IssueDir + "lang_" + theGUI->m_Language + ".json"); if (Translation.isEmpty()) { QString LangAux = theGUI->m_Language; // Short version as fallback LangAux.truncate(LangAux.lastIndexOf('_')); Translation = ReadFileAsString(IssueDir + "lang_" + LangAux + ".json"); } if(!Translation.isEmpty()) m_Translation = QJsonDocument::fromJson(Translation.toUtf8()).toVariant().toMap(); } QList CBoxAssistant::GetIssues(const QVariantMap& Root) const { if (Root.contains("id")) return m_GroupedIssues.value(Root["id"].toString()); QString Class = Root["class"].toString(); QList AllIssues; for (auto I = m_GroupedIssues.begin(); I != m_GroupedIssues.end(); ++I) { for(auto J = I->begin(); J != I->end(); ++J) { if (J->value("type") == "issue" && (Class.isEmpty() || J->value("class").toString().compare(Class, Qt::CaseInsensitive) == 0)) AllIssues.append(*J); } } return AllIssues; } void CBoxAssistant::ShowAssistant() { CBoxAssistant* pWizard = new CBoxAssistant(theGUI); pWizard->setAttribute(Qt::WA_DeleteOnClose); SafeShow(pWizard); } bool CBoxAssistant::StartEngine() { QVariantMap Issue = CurrentIssue(); QString Script = Issue["script"].toString(); QString Name = Issue["id"].toString(); if (!Script.isEmpty()) { m_pEngine = new CWizardEngine(this); connect(m_pEngine, SIGNAL(LogMessage(const QString&)), theGUI, SLOT(AddLogMessage(const QString&))); connect(m_pEngine, SIGNAL(BoxUsed(const CSandBoxPtr&)), this, SLOT(OnBoxUsed(const CSandBoxPtr&))); m_pEngine->AppendLog(QString("Starting troubleshooting script: %1").arg(Issue["id"].toString())); // no tr if (m_bUseDebugger) { QObject* pDebuggerBackend = m_pEngine->GetDebuggerBackend(); if (pDebuggerBackend != NULL) { QObject* pDebuggerFrontend = newJSScriptDebuggerFrontendDynamic(); QObject::connect(pDebuggerBackend, SIGNAL(sendResponse(QVariant)), pDebuggerFrontend, SLOT(processResponse(QVariant)), Qt::QueuedConnection); QObject::connect(pDebuggerFrontend, SIGNAL(sendRequest(QVariant)), pDebuggerBackend, SLOT(processRequest(QVariant)), Qt::QueuedConnection); m_pDebugger = newJSScriptDebuggerDynamic(pDebuggerFrontend); //connect(pDebugger, SIGNAL(detach()), this, ...); m_pDebugger->resize(1024, 640); m_pDebugger->restoreGeometry(theConf->GetBlob("DebuggerWindow/Window_Geometry")); m_pDebugger->show(); } else { QMessageBox::critical(this, "Sandboxie-Plus", tr("V4ScriptDebuggerBackend could not be instantiated, probably V4ScriptDebugger.dll and or its dependencies are missing, script debuger could not be opened.")); } } return m_pEngine->RunScript(Script, Name); } return true; } void CBoxAssistant::KillEngine() { m_pEngine->AppendLog(QString("Troubleshooting script terminated")); // no tr if (m_pDebugger) { QObject* pDebuggerBackend = m_pEngine->GetDebuggerBackend(); QMetaObject::invokeMethod(pDebuggerBackend, "detach", Qt::DirectConnection); m_pDebugger->close(); theConf->SetBlob("DebuggerWindow/Window_Geometry", m_pDebugger->saveGeometry()); m_pDebugger->deleteLater(); m_pDebugger = NULL; } delete m_pEngine; m_pEngine = NULL; } void CBoxAssistant::OnBoxUsed(const CSandBoxPtr& pBox) { SUsedBox UsedBox; UsedBox.pBox = pBox; QDir Dir(pBox->GetFileRoot()); foreach(const QFileInfo & Info, Dir.entryInfoList(QStringList() << "*.dmp", QDir::Files)) UsedBox.OldDumps.append(Info.fileName()); m_UsedBoxes.append(UsedBox); } void CBoxAssistant::accept() { if (m_pEngine && currentId() != Page_Submit) m_pEngine->ApplyShadowChanges(); QWizard::accept(); } void CBoxAssistant::reject() { if (m_pEngine && currentId() != Page_Submit) { if (theConf->GetInt("Options/WarnWizardOnClose", -1) == -1) { bool State = false; if (CCheckableMessageBox::question(this, "Sandboxie-Plus", tr("A troubleshooting procedure is in progress, canceling the wizard will abort it, this may leave the sandbox in an incosistent state.") , tr("Don't ask in future"), &State, QDialogButtonBox::Ok | QDialogButtonBox::Cancel, QDialogButtonBox::Cancel) == QDialogButtonBox::Cancel) return; if (State) theConf->SetValue("Options/WarnWizardOnClose", 1); } } QWizard::reject(); } ////////////////////////////////////////////////////////////////////////////////////////// // CBeginPage // CBeginPage::CBeginPage(QWidget *parent) : QWizardPage(parent) { setTitle(tr("Troubleshooting Wizard")); setPixmap(QWizard::WatermarkPixmap, QPixmap(":/SideLogo.png")); int row = 0; m_pLayout = new QGridLayout; QLabel* pTopLabel = new QLabel(tr("Welcome to the Troubleshooting Wizard for Sandboxie-Plus. " "This interactive assistant is designed to help you in resolving sandboxing issues.")); pTopLabel->setWordWrap(true); m_pLayout->addWidget(pTopLabel, row++, 0, 1, 3); m_pLayout->addItem(new QSpacerItem(40, 10, QSizePolicy::Fixed, QSizePolicy::Fixed), row, 0); m_pLayout->addItem(new QSpacerItem(40, 10, QSizePolicy::Expanding, QSizePolicy::Fixed), row, 2); setLayout(m_pLayout); } void CBeginPage::initializePage() { foreach(QWidget * pWidget, m_pWidgets) delete pWidget; m_pWidgets.clear(); int row = 2; auto AddIssue = [&](QVariantMap Issue) { QPushButton* pIssue = new QPushButton(((CBoxAssistant*)wizard())->Tr(Issue["name"].toString())); pIssue->setProperty("issue", Issue); connect(pIssue, SIGNAL(pressed()), this, SLOT(OnCategory())); pIssue->setIcon(CSandMan::GetIcon(Issue["icon"].toString())); pIssue->setIconSize(QSize(32, 32)); pIssue->setProperty("leftButton", true); pIssue->setStyle(new MyButtonStyle(pIssue->style())); m_pLayout->addWidget(pIssue, row++, 1); m_pWidgets.append(pIssue); return pIssue; }; QVariantMap Root; Root["id"] = "root"; foreach(auto Issue, ((CBoxAssistant*)wizard())->GetIssues(Root)) { if (((CBoxAssistant*)wizard())->GetIssues(Issue).isEmpty() && Issue["type"] != "issue") continue; AddIssue(Issue); } m_pLayout->addItem(new QSpacerItem(10, 10, QSizePolicy::Fixed, QSizePolicy::Expanding), row++, 0); if (!g_CertInfo.valid || g_CertInfo.expired) { QLabel* pBottomLabel = new QLabel(tr("With a valid supporter certificate the wizard would be even more powerfull. " "It could access the online solution database to retriev the latest troubleshooting instructions.")); connect(pBottomLabel, SIGNAL(linkActivated(const QString&)), theGUI, SLOT(OpenUrl(const QString&))); pBottomLabel->setWordWrap(true); m_pLayout->addWidget(pBottomLabel, row++, 0, 1, 3); m_pWidgets.append(pBottomLabel); } } void CBeginPage::OnCategory() { QVariantMap Issue = sender()->property("issue").toMap(); ((CBoxAssistant*)wizard())->PushIssue(Issue); wizard()->next(); } int CBeginPage::nextId() const { QVariantMap Issue = ((CBoxAssistant*)wizard())->CurrentIssue(); QString type = Issue["type"].toString(); if (type == "issue") return CBoxAssistant::Page_Run; if (type == "list") return CBoxAssistant::Page_List; return CBoxAssistant::Page_Group; } bool CBeginPage::isComplete() const { //return false; return true; } bool CBeginPage::validatePage() { if (((CBoxAssistant*)wizard())->CurrentIssue().isEmpty()) { QVariantMap Issue; Issue["type"] = "list"; Issue["name"] = tr("Another issue"); ((CBoxAssistant*)wizard())->PushIssue(Issue); } return true; } ////////////////////////////////////////////////////////////////////////////////////////// // CGroupPage // CGroupPage::CGroupPage(QWidget *parent) : QWizardPage(parent) { setTitle(tr("Select issue from group")); setPixmap(QWizard::WatermarkPixmap, QPixmap(":/SideLogo.png")); int row = 0; m_pLayout = new QGridLayout; m_pLayout->setSpacing(2); m_pTopLabel = new QLabel(tr("Please specify the exact issue:")); m_pTopLabel->setWordWrap(true); m_pLayout->addWidget(m_pTopLabel, row++, 0, 1, 2); m_pGroup = new QButtonGroup(); connect(m_pGroup, SIGNAL(idToggled(int, bool)), this, SIGNAL(completeChanged())); setLayout(m_pLayout); } void CGroupPage::initializePage() { int row = 2; foreach(QWidget * pWidget, m_pWidgets) delete pWidget; m_pWidgets.clear(); m_pLayout->addItem(new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Preferred), row++, 1); QVariantMap Group = ((CBoxAssistant*)wizard())->CurrentIssue(); m_pTopLabel->setText(((CBoxAssistant*)wizard())->Tr(Group["description"].toString())); //QLabel* pCommon = new QLabel(tr("Common Issues:")); //m_pLayout->addWidget(pCommon, row++, 0); //m_pWidgets.append(pCommon); auto AddIssue = [&](QVariantMap Issue) { QRadioButton* pIssue = new QRadioButton(((CBoxAssistant*)wizard())->Tr(Issue["name"].toString())); pIssue->setToolTip(((CBoxAssistant*)wizard())->Tr(Issue["description"].toString())); pIssue->setProperty("issue", Issue); m_pGroup->addButton(pIssue); m_pLayout->addWidget(pIssue, row++, 1); m_pWidgets.append(pIssue); }; foreach(auto Issue, ((CBoxAssistant*)wizard())->GetIssues(Group)) AddIssue(Issue); if (!Group["class"].toString().isEmpty()) { QWidget* pSpacer = new QWidget(); pSpacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); m_pLayout->addWidget(pSpacer, row++, 0); m_pWidgets.append(pSpacer); //QLabel* pOther = new QLabel(tr("More Issues:")); //m_pLayout->addWidget(pOther, row++, 0); //m_pWidgets.append(pOther); QVariantMap Issue; Issue["type"] = "list"; Issue["class"] = Group["class"]; Issue["name"] = tr("Another issue"); AddIssue(Issue); pSpacer = new QWidget(); pSpacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); m_pLayout->addWidget(pSpacer, row++, 0); m_pWidgets.append(pSpacer); } } void CGroupPage::cleanupPage() { ((CBoxAssistant*)wizard())->PopIssue(); QPointer w = wizard(); QTimer::singleShot(10, [w]() { if (w && ((CBoxAssistant*)w.data())->m_NextCounter > 0) ((CBoxAssistant*)w.data())->removePage(CBoxAssistant::Page_Next + ((CBoxAssistant*)w.data())->m_NextCounter--); }); } int CGroupPage::nextId() const { if (QAbstractButton* pButton = m_pGroup->checkedButton()) { QVariantMap Issue = pButton->property("issue").toMap(); QString type = Issue["type"].toString(); if (type == "issue") return CBoxAssistant::Page_Run; if (type == "group") return CBoxAssistant::Page_Next + ((CBoxAssistant*)wizard())->m_NextCounter; if (type == "list") return CBoxAssistant::Page_List; } return CBoxAssistant::Page_Submit; } bool CGroupPage::isComplete() const { return m_pGroup->checkedId() != -1; } bool CGroupPage::validatePage() { if (QAbstractButton* pButton = m_pGroup->checkedButton()) { QVariantMap Issue = pButton->property("issue").toMap(); QString type = Issue["type"].toString(); if (type == "group" || type == "list") ((CBoxAssistant*)wizard())->setPage(CBoxAssistant::Page_Next + ++((CBoxAssistant*)wizard())->m_NextCounter, new CGroupPage()); ((CBoxAssistant*)wizard())->PushIssue(Issue); return true; } return false; } ////////////////////////////////////////////////////////////////////////////////////////// // CListPage // CListPage::CListPage(QWidget *parent) : QWizardPage(parent) { setTitle(tr("Select issue from full list")); setPixmap(QWizard::WatermarkPixmap, QPixmap(":/SideLogo.png")); int row = 0; m_pLayout = new QGridLayout; m_pLayout->setSpacing(2); //QLabel* pTopLabel = new QLabel(tr("Please select an isue from the list")); //pTopLabel->setWordWrap(true); //m_pLayout->addWidget(pTopLabel, row++, 0, 1, 2); m_pFilter = new QLineEdit(); m_pFilter->setPlaceholderText(tr("Search filter")); m_pLayout->addWidget(m_pFilter, row++, 0, 1, 2); connect(m_pFilter, SIGNAL(textChanged(const QString &)), this, SLOT(ApplyFilter())); m_pList = new QListWidget(); m_pLayout->addWidget(m_pList, row++, 0, 1, 2); connect(m_pList, SIGNAL(itemClicked(QListWidgetItem *)), this, SIGNAL(completeChanged())); setLayout(m_pLayout); } void CListPage::ApplyFilter() { static bool UpdatePending = false; if (!UpdatePending) { UpdatePending = true; QTimer::singleShot(100, [=]() { UpdatePending = false; LoadIssues(); }); } } void CListPage::LoadIssues() { m_pList->clear(); QVariantMap List = ((CBoxAssistant*)wizard())->CurrentIssue(); int iAny = List.contains("id") ? 0 : 1; auto AddIssue = [&](QVariantMap Issue) { QListWidgetItem* pItem = new QListWidgetItem(); pItem->setText(((CBoxAssistant*)wizard())->Tr(Issue["name"].toString())); pItem->setToolTip(((CBoxAssistant*)wizard())->Tr(Issue["description"].toString())); pItem->setData(Qt::UserRole, Issue); if (iAny != 1 && Issue["bold"].toBool()) { QFont font = pItem->font(); font.setBold(true); pItem->setFont(font); } m_pList->addItem(pItem); }; QString Filter = m_pFilter->text(); foreach(auto Issue, ((CBoxAssistant*)wizard())->GetIssues(List)) { if (Filter.isEmpty() || ((CBoxAssistant*)wizard())->Tr(Issue["name"].toString()).contains(Filter, Qt::CaseInsensitive) || ((CBoxAssistant*)wizard())->Tr(Issue["description"].toString()).contains(Filter, Qt::CaseInsensitive)) AddIssue(Issue); } if (iAny) { QVariantMap Issue; Issue["type"] = "submit"; Issue["name"] = tr("None of the above"); Issue["bold"] = true; iAny = 2; AddIssue(Issue); } else setTitle(((CBoxAssistant*)wizard())->Tr(List["name"].toString())); } void CListPage::initializePage() { m_pFilter->clear(); LoadIssues(); } void CListPage::cleanupPage() { ((CBoxAssistant*)wizard())->PopIssue(); QPointer w = wizard(); QTimer::singleShot(10, [w]() { if (w && ((CBoxAssistant*)w.data())->m_NextCounter > 0) ((CBoxAssistant*)w.data())->removePage(CBoxAssistant::Page_Next + ((CBoxAssistant*)w.data())->m_NextCounter--); }); } int CListPage::nextId() const { if (QListWidgetItem* pItem = m_pList->currentItem()) { QVariantMap Issue = pItem->data(Qt::UserRole).toMap(); QString type = Issue["type"].toString(); if (type == "issue") return CBoxAssistant::Page_Run; if (type == "group") return CBoxAssistant::Page_Next + ((CBoxAssistant*)wizard())->m_NextCounter; if (type == "list") return CBoxAssistant::Page_List; } return CBoxAssistant::Page_Submit; } bool CListPage::isComplete() const { return !m_pList->selectedItems().isEmpty(); } bool CListPage::validatePage() { if (QListWidgetItem* pItem = m_pList->currentItem()) { QVariantMap Issue = pItem->data(Qt::UserRole).toMap(); QString type = Issue["type"].toString(); if (type == "group" || type == "list") ((CBoxAssistant*)wizard())->setPage(CBoxAssistant::Page_Next + ++((CBoxAssistant*)wizard())->m_NextCounter, new CGroupPage()); ((CBoxAssistant*)wizard())->PushIssue(Issue); return true; } return false; } ////////////////////////////////////////////////////////////////////////////////////////// // CRunPage // CRunPage::CRunPage(QWidget *parent) : QWizardPage(parent) { setTitle(tr("Troubleshooting ...")); setSubTitle(tr("")); m_pLayout = new QGridLayout; m_pTopLabel = new QLabel(); m_pTopLabel->setWordWrap(true); m_pLayout->addWidget(m_pTopLabel, 0, 0, 1, 2); m_pForm = NULL; setLayout(m_pLayout); } void CRunPage::initializePage() { QVariantMap Issue = ((CBoxAssistant*)wizard())->CurrentIssue(); setTitle(((CBoxAssistant*)wizard())->Tr(Issue["name"].toString())); setSubTitle(((CBoxAssistant*)wizard())->Tr(Issue["description"].toString())); if(((CBoxAssistant*)wizard())->StartEngine()) { CWizardEngine* pEngine = ((CBoxAssistant*)wizard())->GetEngine(); connect(pEngine, SIGNAL(StateChanged(int, const QString&)), this, SLOT(OnStateChanged(int, const QString&))); } else m_pTopLabel->setText(tr("This troubleshooting procedure could not be initialized. " "You can click on next to submit an issue report.")); } void CRunPage::cleanupPage() { ((CBoxAssistant*)wizard())->KillEngine(); } void CRunPage::OnStateChanged(int state, const QString& Text) { CWizardEngine* pEngine = ((CBoxAssistant*)wizard())->GetEngine(); if (!pEngine || pEngine != sender()) return; if (m_pForm) { delete m_pForm; m_pForm = NULL; m_pWidgets.clear(); } bool bEnableNext = true; //qDebug() << "OnStateChanged" << state; switch (state) { case CBoxEngine::eRunning: case CBoxEngine::eRunningAsync: bEnableNext = false; case CBoxEngine::eReady: m_pTopLabel->setText(Text); break; case CBoxEngine::eQuery: { QVariantMap Query = pEngine->GetQuery(); m_pTopLabel->setText(Query["text"].toString()); if (Query["type"].toString().compare("form", Qt::CaseInsensitive) == 0) { m_pForm = new QWidget(); QFormLayout* pForm = new QFormLayout(m_pForm); m_pLayout->addWidget(m_pForm, 1, 0, 1, 2); QVariantList Form = Query["form"].toList(); foreach(const QVariant& vEntry, Form) { QVariantMap Entry = vEntry.toMap(); QString type = Entry["type"].toString(); QString name = ((CBoxAssistant*)wizard())->Tr(Entry["name"].toString()); QVariant value = Entry["value"].toString(); QWidget* pWidget; if (type.compare("label", Qt::CaseInsensitive) == 0) { pWidget = new QLabel(name); pForm->addRow(pWidget); } else if (type.compare("file", Qt::CaseInsensitive) == 0 || type.compare("folder", Qt::CaseInsensitive) == 0) { CPathEdit* pPath = new CPathEdit(type.compare("folder", Qt::CaseInsensitive) == 0); pWidget = pPath; pPath->SetText(value.toString()); pForm->addRow(name, pPath); } else if (type.compare("edit", Qt::CaseInsensitive) == 0) { QLineEdit* pEdit = new QLineEdit(); pWidget = pEdit; pEdit->setText(value.toString()); pForm->addRow(name, pEdit); } else if (type.compare("check", Qt::CaseInsensitive) == 0) { QCheckBox* pCheck = new QCheckBox(name); pWidget = pCheck; pCheck->setChecked(value.toBool()); pForm->addRow("", pCheck); } else if (type.compare("radio", Qt::CaseInsensitive) == 0) { QRadioButton* pRadio = new QRadioButton(name); pWidget = pRadio; pRadio->setChecked(value.toBool()); pForm->addRow("", pRadio); // todo: add mandatory flag for other fields bEnableNext = false; // user must make a choice connect(pRadio, SIGNAL(toggled(bool)), this, SLOT(CheckUserInput())); } else if (type.compare("box", Qt::CaseInsensitive) == 0) { QString Name = name; //if(!Name.isEmpty()) pForm->addRow(new QLabel(Name)); CBoxPicker* pPicker = new CBoxPicker(value.toString()); pWidget = pPicker; pForm->addRow(Name, pPicker); } else if (type.compare("combo", Qt::CaseInsensitive) == 0) { QComboBox* pCombo = new QComboBox(); pWidget = pCombo; foreach(const QVariant & vItem, Entry["items"].toList()) { if (vItem.type() == QVariant::Map) { QVariantMap Item = vItem.toMap(); pCombo->addItem(((CBoxAssistant*)wizard())->Tr(Item["name"].toString()), Item["value"]); } else pCombo->addItem(vItem.toString()); } pForm->addRow(name, pCombo); } pWidget->setToolTip(((CBoxAssistant*)wizard())->Tr(Entry["description"].toString())); if (Entry["disabled"].toBool()) pWidget->setDisabled(true); QString id = Entry["id"].toString(); if (!id.isEmpty()) m_pWidgets.insert(id, pWidget); } } break; } case CBoxEngine::eError: m_pTopLabel->setText(tr("Somethign failed internally this troubleshooting procedure can not continue. " "You can click on next to submit an issue report.") + (pEngine ? tr("\n\nError: ") + Text : "")); case CBoxEngine::eCanceled: break; case CBoxEngine::eCompleted: case CBoxEngine::eSuccess: case CBoxEngine::eFailed: wizard()->next(); break; } if(bEnableNext) wizard()->button(QWizard::NextButton)->setEnabled(true); } void CRunPage::CheckUserInput() { wizard()->button(QWizard::NextButton)->setEnabled(true); } bool CRunPage::isComplete() const { return true; } int CRunPage::nextId() const { CWizardEngine* pEngine = ((CBoxAssistant*)wizard())->GetEngine(); if(!pEngine || pEngine->HasFailed() || pEngine->HasError()) return CBoxAssistant::Page_Submit; return CBoxAssistant::Page_Complete; } bool CRunPage::validatePage() { CWizardEngine* pEngine = ((CBoxAssistant*)wizard())->GetEngine(); if (!pEngine || !(pEngine->HasQuery() || pEngine->IsReady())) { // disables back button on next page setCommitPage(true); return true; } // dissable back button on the current page wizard()->button(QWizard::BackButton)->setEnabled(false); // disable next button, OnStateChanged wi re enable it wizard()->button(QWizard::NextButton)->setEnabled(false); if (pEngine->HasQuery()) { QVariantMap Reply; for (auto I = m_pWidgets.begin(); I != m_pWidgets.end(); ++I) { QVariant Value; if (CPathEdit* pPath = qobject_cast(I.value())) Value = pPath->GetText(); else if (QLineEdit* pEdit = qobject_cast(I.value())) Value = pEdit->text(); else if (QCheckBox* pCheck = qobject_cast(I.value())) Value = pCheck->isChecked(); else if (QRadioButton* pRadio = qobject_cast(I.value())) Value = pRadio->isChecked(); else if (CBoxPicker* pPicker = qobject_cast(I.value())) Value = pPicker->GetBoxName(); else if (QComboBox* pCombo = qobject_cast(I.value())) { Value = pCombo->currentData(); if (!Value.isValid()) Value = pCombo->currentIndex(); } Reply[I.key()] = Value; } pEngine->SetResult(Reply); } else if (pEngine->IsReady()) { pEngine->Continue(); } return false; } ////////////////////////////////////////////////////////////////////////////////////////// // CSubmitPage // CSubmitPage::CSubmitPage(QWidget *parent) : QWizardPage(parent) { setTitle(tr("Submit Issue Report")); setPixmap(QWizard::WatermarkPixmap, QPixmap(":/SideLogo.png")); int row = 0; QGridLayout* pLayout = new QGridLayout; pLayout->setSpacing(2); m_pTopLabel = new QLabel(); m_pTopLabel->setWordWrap(true); pLayout->addWidget(m_pTopLabel, row++, 0, 1, 3); m_pReport = new QTextEdit(); m_pReport->setPlaceholderText(tr("Detailed issue description")); //m_pReport->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); pLayout->addWidget(m_pReport, row++, 0, 1, 3); m_pAttachIni = new QCheckBox(tr("Attach Sandboxie.ini")); m_pAttachIni->setToolTip(tr("Sandboxing compatybility is relyent on the configuration hence attaching the sandboxie.ini helps a lot with finding the issue.")); pLayout->addWidget(m_pAttachIni, row, 0); m_pAttachLog = new QCheckBox(tr("Attach Logs")); m_pAttachLog->setTristate(true); m_pAttachLog->setToolTip(tr("Select partially checked state to sends only message log but no trace log.\nBefore sending you can review the logs in the main window.")); pLayout->addWidget(m_pAttachLog, row, 1); m_pAttachDmp = new QCheckBox(tr("Attach Crash Dumps")); m_pAttachDmp->setToolTip(tr("An applicatin crashed during the troubleshooting procedure, attaching a crash dump can help with the debugging.")); pLayout->addWidget(m_pAttachDmp, row, 2); m_pMail = new QLineEdit(); m_pMail->setPlaceholderText(tr("Email address")); m_pMail->setToolTip(tr("You have the option to provide an email address to receive a notification once a solution for your issue has been identified.")); pLayout->addWidget(m_pMail, ++row, 0, 1, 3); setLayout(pLayout); } void CSubmitPage::initializePage() { QString Info = tr("We apologize for the inconvenience you are currently facing with Sandboxie-Plus. "); QString Report; // DO NOT TRANSLATE - Reports must be in English! CWizardEngine* pEngine = ((CBoxAssistant*)wizard())->GetEngine(); if (pEngine) { if (pEngine->HasFailed() || pEngine->HasError()) { Info += tr("Unfortunately, the automated troubleshooting procedure failed. "); QVariantMap Issue = ((CBoxAssistant*)wizard())->CurrentIssue(); Report = QString("Troubleshooting procedure '%1' failed").arg(Issue["id"].toString()); } } else { Info += tr("Regrettably, there is no automated troubleshooting procedure available for the specific issue you have described. "); Report = "[PLEASE ENTER YOUR ISSUE DESCRIPTION HERE]"; } Info += tr("If you wish to submit an issue report, please review the report below and click 'Finish'."); m_pTopLabel->setText(Info); Report += "\n\nFurther information:\n"; Report += "Sandboxie-Plus Version: " + theGUI->GetVersion() + "\n"; Report += "Operating System Version: " + QSysInfo::kernelVersion() + "-" + QSysInfo::currentCpuArchitecture() + "\n"; bool bNewDumps = false; foreach(auto & UsedBox, ((CBoxAssistant*)wizard())->m_UsedBoxes) { QDir Dir(UsedBox.pBox->GetFileRoot()); foreach(const QFileInfo & Info, Dir.entryInfoList(QStringList() << "*.dmp", QDir::Files)) { if (!UsedBox.OldDumps.contains(Info.fileName())) { bNewDumps = true; break; } } Report += "Used Sandbox: " + UsedBox.pBox->GetName() + "\n"; } if (pEngine) { QVariantMap Values = pEngine->GetReport(); for (auto I = Values.begin(); I != Values.end(); ++I) Report += I.key() + ": " + I->toString() + "\n"; } m_pReport->setText(Report); m_pAttachIni->setChecked(true); m_pAttachLog->setChecked(true); //bool bHasLog = !theAPI->GetTrace().isEmpty(); //m_pAttachLog->setEnabled(bHasLog); //m_pAttachLog->setChecked(bHasLog); m_pAttachDmp->setEnabled(bNewDumps); m_pAttachDmp->setChecked(bNewDumps); } bool CSubmitPage::validatePage() { QBuffer* pTraceLog = NULL; if (m_pAttachLog->checkState() == Qt::Checked) { pTraceLog = new QBuffer(); pTraceLog->open(QIODevice::ReadWrite); if (!CTraceView::SaveToFile(pTraceLog)) { delete pTraceLog; return false; } } m_pUploadProgress = CSbieProgressPtr(new CSbieProgress()); theGUI->AddAsyncOp(m_pUploadProgress, false, QString(), this); CNetworkAccessManager* pRequestManager = new CNetworkAccessManager(30 * 1000, this); QHttpMultiPart* pMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); QHttpPart Report; Report.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"report\"")); Report.setBody(m_pReport->toPlainText().toUtf8()); pMultiPart->append(Report); if (m_pAttachIni->isChecked()) { QFile* pSbieIni = new QFile(theAPI->GetIniPath(), pMultiPart); if (pSbieIni->open(QIODevice::ReadOnly)) { QHttpPart SbieIni; SbieIni.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain")); SbieIni.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"sbieIni\"; filename=\"Sandboxie.ini\"")); SbieIni.setBodyDevice(pSbieIni); pMultiPart->append(SbieIni); } } if (m_pAttachLog->isChecked()) { QBuffer* pSbieLogs = new QBuffer(pMultiPart); CArchive Archive("SbieTrace.7z", pSbieLogs); QMap Files; QBuffer* pMessageLog = new QBuffer(); pMessageLog->open(QIODevice::ReadWrite); theGUI->SaveMessageLog(pMessageLog); int ArcIndex = Archive.AddFile("SbieMsg.log"); pMessageLog->seek(0); Files.insert(ArcIndex, pMessageLog); if (pTraceLog) { ArcIndex = Archive.AddFile("SbieTrace.log"); pTraceLog->seek(0); Files.insert(ArcIndex, pTraceLog); } m_pUploadProgress->ShowMessage(tr("Compressing Logs")); Archive.Update(&Files, true, 9); if (pSbieLogs->open(QIODevice::ReadOnly)) { QHttpPart SbieLogs; SbieLogs.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-strea")); SbieLogs.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"sbieLogs\"; filename=\"SbieLogs.7z\"")); SbieLogs.setBodyDevice(pSbieLogs); pMultiPart->append(SbieLogs); } } if (m_pAttachDmp->isChecked()) { QBuffer* pSbieDumps = new QBuffer(pMultiPart); CArchive Archive("SbieDumps.7z", pSbieDumps); QMap Files; foreach(auto & UsedBox, ((CBoxAssistant*)wizard())->m_UsedBoxes) { QDir Dir(UsedBox.pBox->GetFileRoot()); foreach(const QFileInfo & Info, Dir.entryInfoList(QStringList() << "*.dmp", QDir::Files)) { if (!UsedBox.OldDumps.contains(Info.fileName())) { QFile* pCrashDump = new QFile(Info.filePath()); int ArcIndex = Archive.AddFile(Info.fileName()); Files.insert(ArcIndex, pCrashDump); } } } m_pUploadProgress->ShowMessage(tr("Compressing Dumps")); Archive.Update(&Files, true, 9); if (pSbieDumps->open(QIODevice::ReadOnly)) { QHttpPart SbieDumps; SbieDumps.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-strea")); SbieDumps.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"sbieDumps\"; filename=\"SbieDumps.7z\"")); SbieDumps.setBodyDevice(pSbieDumps); pMultiPart->append(SbieDumps); } } if (!m_pMail->text().isEmpty()) { QHttpPart eMail; eMail.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"email\"")); eMail.setBody(m_pMail->text().toUtf8()); pMultiPart->append(eMail); } QUrl Url("https://sandboxie-plus.com/issues/submit.php"); QNetworkRequest Request(Url); //Request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); Request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); //Request.setRawHeader("Accept-Encoding", "gzip"); QNetworkReply* pReply = pRequestManager->post(Request, pMultiPart); pMultiPart->setParent(pReply); m_pUploadProgress->ShowMessage(tr("Submitting issue report...")); connect(pReply, &QNetworkReply::finished, this, [pReply, this]() { QByteArray Reply = pReply->readAll(); pReply->deleteLater(); m_pUploadProgress->Finish(SB_OK); m_pUploadProgress.clear(); QVariantMap Result = QJsonDocument::fromJson(Reply).toVariant().toMap(); if (!Result["success"].toBool()) { QMessageBox::critical(this, "Sandboxie-Plus", tr("Failed to submit issue report, error %1\nTry submitting without the log attached.").arg(Result["error"].toInt())); return; } QMessageBox::information(this, "Sandboxie-Plus", tr("Your issue report have been successfully submitted, thank you.")); wizard()->close(); }); connect(pReply, &QNetworkReply::uploadProgress, this, [this](qint64 bytes, qint64 bytesTotal) { if (bytesTotal != 0 && !m_pUploadProgress.isNull()) m_pUploadProgress->Progress(100 * bytes / bytesTotal); }); return false; } ////////////////////////////////////////////////////////////////////////////////////////// // CCompletePage // CCompletePage::CCompletePage(QWidget *parent) : QWizardPage(parent) { setTitle(tr("Troubleshooting Completed")); setPixmap(QWizard::WatermarkPixmap, QPixmap(":/SideLogo.png")); QVBoxLayout *pLayout = new QVBoxLayout; m_pLabel = new QLabel; m_pLabel->setWordWrap(true); m_pLabel->setText(tr("Thank you for using the Troubleshooting Wizard for Sandboxie-Plus. We apologize for any inconvenience you experienced during the process." "If you have any additional questions or need further assistance, please don't hesitate to reach out. We're here to help. " "Thank you for your understanding and cooperation. \n\nYou can click Finish to close this wizard.")); pLayout->addWidget(m_pLabel); // todo: report success optionally setLayout(pLayout); } void CCompletePage::initializePage() { //wizard()->button(QWizard::CancelButton)->setEnabled(false); }