From 4663599ffe2cb8a012bfafee6de5cff31176d7c4 Mon Sep 17 00:00:00 2001 From: DavidXanatos Date: Mon, 7 Dec 2020 17:34:20 +0100 Subject: [PATCH] Build 0.5.0 --- CHANGELOG.md | 8 +- Sandboxie/apps/control/Control.vcxproj | 28 +- .../apps/control/Control.vcxproj.filters | 210 ++++ Sandboxie/apps/control/MyFrame.cpp | 68 +- Sandboxie/apps/control/MyFrame.h | 3 + Sandboxie/apps/control/SbieControl.rc | Bin 70414 -> 71876 bytes Sandboxie/apps/control/UpdateDialog.cpp | 171 ++++ Sandboxie/apps/control/UpdateDialog.h | 71 ++ Sandboxie/apps/control/Updater.cpp | 487 +++++++++ Sandboxie/apps/control/Updater.h | 74 ++ Sandboxie/apps/control/resource.h | 2 + Sandboxie/common/json/JSON.cpp | 280 ++++++ Sandboxie/common/json/JSON.h | 112 +++ Sandboxie/common/json/JSONValue.cpp | 944 ++++++++++++++++++ Sandboxie/common/json/JSONValue.h | 95 ++ Sandboxie/core/svc/DriverAssistStart.cpp | 169 +++- Sandboxie/core/svc/GuiServer.cpp | 23 +- Sandboxie/install/SandboxieVS.nsi | 16 +- Sandboxie/install/kmdutil/kmdutil.c | 72 +- Sandboxie/msgs/Sbie-English-1033.txt | Bin 160784 -> 160860 bytes .../Common/CheckableMessageBox.cpp | 12 + .../MiscHelpers/Common/CheckableMessageBox.h | 4 + .../Common/NetworkAccessManager.cpp | 76 ++ .../MiscHelpers/Common/NetworkAccessManager.h | 34 + .../MiscHelpers/Common/ProgressDialog.h | 4 +- SandboxiePlus/MiscHelpers/MiscHelpers.vcxproj | 2 + .../MiscHelpers/MiscHelpers.vcxproj.filters | 6 + SandboxiePlus/QSbieAPI/Sandboxie/SandBox.cpp | 4 +- SandboxiePlus/QSbieAPI/SbieUtils.cpp | 49 +- SandboxiePlus/QSbieAPI/SbieUtils.h | 3 +- SandboxiePlus/SandMan/Forms/OptionsWindow.ui | 99 +- SandboxiePlus/SandMan/Forms/SettingsWindow.ui | 129 +-- SandboxiePlus/SandMan/Models/SbieModel.cpp | 93 +- SandboxiePlus/SandMan/Models/SbieModel.h | 16 +- SandboxiePlus/SandMan/SandMan.cpp | 457 +++++++-- SandboxiePlus/SandMan/SandMan.h | 21 +- SandboxiePlus/SandMan/Views/SbieView.cpp | 198 +++- SandboxiePlus/SandMan/Views/SbieView.h | 10 +- .../SandMan/Windows/OptionsWindow.cpp | 17 +- SandboxiePlus/SandMan/Windows/OptionsWindow.h | 2 +- .../SandMan/Windows/SettingsWindow.cpp | 13 + SandboxiePlus/SandMan/main.cpp | 125 --- 42 files changed, 3720 insertions(+), 487 deletions(-) create mode 100644 Sandboxie/apps/control/Control.vcxproj.filters create mode 100644 Sandboxie/apps/control/UpdateDialog.cpp create mode 100644 Sandboxie/apps/control/UpdateDialog.h create mode 100644 Sandboxie/apps/control/Updater.cpp create mode 100644 Sandboxie/apps/control/Updater.h create mode 100644 Sandboxie/common/json/JSON.cpp create mode 100644 Sandboxie/common/json/JSON.h create mode 100644 Sandboxie/common/json/JSONValue.cpp create mode 100644 Sandboxie/common/json/JSONValue.h create mode 100644 SandboxiePlus/MiscHelpers/Common/NetworkAccessManager.cpp create mode 100644 SandboxiePlus/MiscHelpers/Common/NetworkAccessManager.h diff --git a/CHANGELOG.md b/CHANGELOG.md index c5b0e2784a..0ed6f7ef5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). -## [0.5.0 / 5.45.0] - 2020-12-01 +## [0.5.0 / 5.45.0] - 2020-12-06 ### Added - added new notification window @@ -27,6 +27,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). - added icons to box option tabs - added box grouping - added new debug option "DebugTrace=y" to log debug output to the trace log +- added check for updates to the new SandMan UI +- added check for updates to the legacy SbieCtrl UI ### Changed - File migration limit can now be disabled by specifying "CopyLimitKb=-1" @@ -35,6 +37,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - reworked sandbox dletion mechaism ofthe new UI - restructured sandbox options window - SbieDLL.dll can now be compiled with an up to date nitll.lib +- improved automated driver self repair ### Fixed - fixed issues migrating files > 4GB @@ -47,6 +50,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - fixed issues when a snapshot operation failed - fixed some special cases of IpcPath and WinClass in the new UI - fixed driver issues with WHQL passing compatybility testing +- fixed issues with classical installer @@ -118,7 +122,6 @@ This project adheres to [Semantic Versioning](http://semver.org/). - fixed issue deleting sandbox when located on a drive directly - ## [0.4.2 / 5.43.6] - 2020-10-10 ### Added @@ -130,7 +133,6 @@ This project adheres to [Semantic Versioning](http://semver.org/). - fixed chrome 86+ compatybility bug with chroms own sandbox - ## [0.4.1 / 5.43.5] - 2020-09-12 ### Added diff --git a/Sandboxie/apps/control/Control.vcxproj b/Sandboxie/apps/control/Control.vcxproj index 01f8a27ddc..1ce920f079 100644 --- a/Sandboxie/apps/control/Control.vcxproj +++ b/Sandboxie/apps/control/Control.vcxproj @@ -108,7 +108,7 @@ Use - uafxcw.lib;common.lib;SbieDll.lib;ntdll.lib;psapi.lib;wininet.lib + uafxcw.lib;common.lib;SbieDll.lib;ntdll.lib;psapi.lib;wininet.lib;winhttp.lib uafxcw.lib @@ -126,7 +126,7 @@ Use - uafxcw.lib;common.lib;SbieDll.lib;ntdll.lib;psapi.lib;wininet.lib + uafxcw.lib;common.lib;SbieDll.lib;ntdll.lib;psapi.lib;wininet.lib;winhttp.lib uafxcw.lib @@ -143,7 +143,7 @@ Use - uafxcw.lib;common.lib;SbieDll.lib;ntdll.lib;psapi.lib;wininet.lib + uafxcw.lib;common.lib;SbieDll.lib;ntdll.lib;psapi.lib;wininet.lib;winhttp.lib uafxcw.lib @@ -158,7 +158,7 @@ Use - uafxcw.lib;common.lib;SbieDll.lib;ntdll.lib;psapi.lib;wininet.lib + uafxcw.lib;common.lib;SbieDll.lib;ntdll.lib;psapi.lib;wininet.lib;winhttp.lib uafxcw.lib @@ -167,6 +167,18 @@ + + NotUsing + NotUsing + NotUsing + NotUsing + + + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing NotUsing @@ -271,11 +283,16 @@ + + + + + @@ -331,6 +348,8 @@ + + @@ -339,7 +358,6 @@ - diff --git a/Sandboxie/apps/control/Control.vcxproj.filters b/Sandboxie/apps/control/Control.vcxproj.filters new file mode 100644 index 0000000000..75ad0d534d --- /dev/null +++ b/Sandboxie/apps/control/Control.vcxproj.filters @@ -0,0 +1,210 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + common\json + + + common\json + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + common\json + + + common\json + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {be66dba5-2f3c-4325-abc0-4283750262a2} + + + {1eb5d258-3c21-4ed6-9384-57dd80c51c36} + + + \ No newline at end of file diff --git a/Sandboxie/apps/control/MyFrame.cpp b/Sandboxie/apps/control/MyFrame.cpp index 9c11990291..22a5c8054a 100644 --- a/Sandboxie/apps/control/MyFrame.cpp +++ b/Sandboxie/apps/control/MyFrame.cpp @@ -45,7 +45,8 @@ #include "apps/common/RunBrowser.h" #include "apps/common/BoxOrder.h" #include "common/my_version.h" - +#include "Updater.h" +#include "UpdateDialog.h" //--------------------------------------------------------------------------- // Defines @@ -87,6 +88,8 @@ static const WCHAR *_HideWindowNotify = L"HideWindowNotify"; const WCHAR *_UpdateCheckNotify = L"UpdateCheckNotify"; static const WCHAR *_ShouldDeleteNotify = L"ShouldDeleteNotify"; + const WCHAR *_NextUpdateCheck = L"NextUpdateCheck"; + BOOL CMyFrame::m_inTimer = FALSE; BOOL CMyFrame::m_destroyed = FALSE; @@ -135,11 +138,15 @@ BEGIN_MESSAGE_MAP(CMyFrame, CFrameWnd) ON_COMMAND(ID_CONF_EDIT, OnCmdConfEdit) ON_COMMAND(ID_CONF_RELOAD, OnCmdConfReload) + ON_COMMAND(ID_HELP_SUPPORT, OnCmdHelpSupport) ON_COMMAND(ID_HELP_TOPICS, OnCmdHelpTopics) ON_COMMAND(ID_HELP_TUTORIAL, OnCmdHelpTutorial) ON_COMMAND(ID_HELP_FORUM, OnCmdHelpForum) + ON_COMMAND(ID_HELP_UPDATE, OnCmdHelpUpdate) ON_COMMAND(ID_HELP_ABOUT, OnCmdHelpAbout) + //ON_MESSAGE(WM_UPDATERESULT, OnUpdateResult) + ON_COMMAND(ID_PROCESS_TERMINATE, OnCmdTerminateProcess) ON_UPDATE_COMMAND_UI(ID_DISABLE_FORCE, OnUpdDisableForce) @@ -962,6 +969,17 @@ void CMyFrame::OnCmdConfReload() } +//--------------------------------------------------------------------------- +// OnCmdHelpSupport +//--------------------------------------------------------------------------- + + +void CMyFrame::OnCmdHelpSupport() +{ + CRunBrowser x(this, L"https://xanasoft.com/go.php?to=donate"); +} + + //--------------------------------------------------------------------------- // OnCmdHelpTopics //--------------------------------------------------------------------------- @@ -997,8 +1015,17 @@ void CMyFrame::OnCmdHelpForum() CRunBrowser::OpenForum(this); } +//--------------------------------------------------------------------------- +// OnCmdHelpUpdate +//--------------------------------------------------------------------------- +void CMyFrame::OnCmdHelpUpdate() +{ + CUpdateDialog dlg(this); + dlg.DoModal(); +} + //--------------------------------------------------------------------------- // OnCmdHelpAbout //--------------------------------------------------------------------------- @@ -2007,6 +2034,39 @@ void CMyFrame::OnTimer(UINT_PTR nIDEvent) if ((_counter % 600) == 0) SaveSettings(); + // + // update check + // + + if (! m_hidden) + { + __int64 NextUpdateCheck; + CUserSettings::GetInstance().GetNum64(_NextUpdateCheck, NextUpdateCheck, 0); + if(NextUpdateCheck == 0) + CUserSettings::GetInstance().SetNum64(_NextUpdateCheck, time(NULL) + 7 * 24 * 60 * 60); + else if(NextUpdateCheck != -1 && time(NULL) >= NextUpdateCheck) + { + BOOL UpdateCheckNotify; + CUserSettings::GetInstance().GetBool(_UpdateCheckNotify, UpdateCheckNotify, TRUE); + if (UpdateCheckNotify) + { + static BOOLEAN update_dlg_open = FALSE; + if (!update_dlg_open) { + update_dlg_open = TRUE; + CUpdateDialog dlg(this); + if(dlg.DoModal() == 0) + CUserSettings::GetInstance().SetNum64(_NextUpdateCheck, time(NULL) + 1 * 24 * 60 * 60); + update_dlg_open = FALSE; + } + } + else + { + CUserSettings::GetInstance().SetNum64(_NextUpdateCheck, time(NULL) + 1 * 24 * 60 * 60); + CUpdater::GetInstance().CheckUpdates(this, false); + } + } + } + // // refresh processes // @@ -2331,3 +2391,9 @@ void CMyFrame::CheckShouldDelete(CBox &box) } } } + +/*LRESULT CMyFrame::OnUpdateResult(WPARAM wParam, LPARAM lParam) +{ + + return 0; +}*/ \ No newline at end of file diff --git a/Sandboxie/apps/control/MyFrame.h b/Sandboxie/apps/control/MyFrame.h index 7dc5618c33..c5d5c1bb93 100644 --- a/Sandboxie/apps/control/MyFrame.h +++ b/Sandboxie/apps/control/MyFrame.h @@ -119,10 +119,13 @@ class CMyFrame : public CFrameWnd afx_msg void OnCmdConfLock(); afx_msg void OnCmdConfEdit(); afx_msg void OnCmdConfReload(); + afx_msg void OnCmdHelpSupport(); afx_msg void OnCmdHelpTopics(); afx_msg void OnCmdHelpTutorial(); afx_msg void OnCmdHelpForum(); + afx_msg void OnCmdHelpUpdate(); afx_msg void OnCmdHelpAbout(); + //afx_msg LRESULT OnUpdateResult(WPARAM wParam, LPARAM lParam); afx_msg void OnCmdTerminateProcess(); afx_msg void OnUpdViewMenu(CCmdUI *pCmdUI); diff --git a/Sandboxie/apps/control/SbieControl.rc b/Sandboxie/apps/control/SbieControl.rc index 56163a592e4e75252bbf1b9b94436768d3008a7a..ae12fcfa1bb24a43f58f84796e074496a12d1124 100644 GIT binary patch delta 699 zcmeBM#&TpM%Z4sRW=;mq$rFXenN1lCCOaD1PX3_CB^=BU$`AmA{tQ73Aq>0>TtGD& zjUzX2QjB5*Yd9#O3Dqd6Ysz54pv0iV05k{=JsDgW;u$;`Tp4_T#>9h-b762~2${Ul zFnx25a?4#-kPR-A|A!iLn=zOJwJR_fG8j%?m?Am(jHATlyysGr^IX{`pX!t1b^^P@ z1!!2%WJ7;-Ru`b5?vn-mbtmt89>B;sdA_qczXAiqO0cO6j+6ISiBF1QQB+_sg&J)D zrj3Dag^7Ua$%;P0tO^V@3<^M&BG52K4|PqDQ$P+fVu0u}fzsws+7M_8Bv2CPsJo+C z4Gb_>ppSh(z6CiMZoe6rMUGj1Ky$*7%(4W!4CFq%W@&@m5AlULg9Rb=S<;~jKob>! zF$s!G?0yOc2C@&(9ezNcfz-g_8$1h9%BlkgiTvsxlr0h0z4V6%`Ugx!. + */ + +//--------------------------------------------------------------------------- +// Run Browser Dialog +//--------------------------------------------------------------------------- + +#include "stdafx.h" + +#include "UpdateDialog.h" +#include "Updater.h" +#include "common/CommonUtils.h" +#include "common/MyMsg.h" +#include "core/dll/sbiedll.h" +#include "UserSettings.h" + +//--------------------------------------------------------------------------- +// Message Map +//--------------------------------------------------------------------------- + + +BEGIN_MESSAGE_MAP(CUpdateDialog, CDialog) + + ON_COMMAND(IDOK, OnNow) + ON_COMMAND(ID_UPDATE_LATER, OnLater) + ON_COMMAND(ID_UPDATE_NEVER, OnNever) + ON_COMMAND(IDCANCEL, OnCancel) + +END_MESSAGE_MAP() + + +//--------------------------------------------------------------------------- +// Constructor +//--------------------------------------------------------------------------- + + +CUpdateDialog::CUpdateDialog(CWnd *pParentWnd) + : CDialog((UINT)0, pParentWnd) +{ + m_lpszTemplateName = L"UPDATE_DIALOG"; + + BOOLEAN LayoutRTL; + SbieDll_GetLanguage(&LayoutRTL); + if (LayoutRTL) { + + m_DlgTmplCopy = + Common_DlgTmplRtl(AfxGetInstanceHandle(), m_lpszTemplateName); + if (m_DlgTmplCopy) { + m_lpszTemplateName = NULL; + InitModalIndirect((LPCDLGTEMPLATE)m_DlgTmplCopy, m_pParentWnd); + } + + } else + m_DlgTmplCopy = NULL; +} + + +//--------------------------------------------------------------------------- +// Destructor +//--------------------------------------------------------------------------- + + +CUpdateDialog::~CUpdateDialog() +{ + if (m_DlgTmplCopy) { + HeapFree(GetProcessHeap(), 0, m_DlgTmplCopy); + m_DlgTmplCopy = NULL; + } +} + + +//--------------------------------------------------------------------------- +// OnInitDialog +//--------------------------------------------------------------------------- + + +BOOL CUpdateDialog::OnInitDialog() +{ + SetWindowText(CMyMsg(MSG_3621)); + + GetDlgItem(ID_UPDATE_EXPLAIN_1)->SetWindowText(CMyMsg(MSG_3622)); + GetDlgItem(IDOK)->SetWindowText(CMyMsg(MSG_3623)); + GetDlgItem(ID_UPDATE_LATER)->SetWindowText(CMyMsg(MSG_3624)); + GetDlgItem(ID_UPDATE_NEVER)->SetWindowText(CMyMsg(MSG_3625)); + GetDlgItem(IDCANCEL)->SetWindowText(CMyMsg(MSG_3002)); + GetDlgItem(ID_UPDATE_SILENT)->SetWindowText(CMyMsg(MSG_3626)); + GetDlgItem(ID_UPDATE_EXPLAIN_2)->SetWindowText(CMyMsg(MSG_3627)); + + BOOL UpdateCheckNotify; + CUserSettings::GetInstance().GetBool(_UpdateCheckNotify, UpdateCheckNotify, FALSE); + CButton *pCheckBox1 = (CButton *)GetDlgItem(ID_UPDATE_SILENT); + pCheckBox1->SetCheck(!UpdateCheckNotify ? BST_CHECKED : BST_UNCHECKED); + + return TRUE; +} + + +//--------------------------------------------------------------------------- +// OnNow +//--------------------------------------------------------------------------- + + +void CUpdateDialog::OnNow() +{ + if(!CUpdater::GetInstance().CheckUpdates(GetParent())) + CMyApp::MsgBox(NULL, MSG_3628, MB_OK); + CloseDialog(); +} + + +//--------------------------------------------------------------------------- +// OnLater +//--------------------------------------------------------------------------- + + +void CUpdateDialog::OnLater() +{ + CUserSettings::GetInstance().SetNum64(_NextUpdateCheck, time(NULL) + 7 * 24 * 60 * 60); + CloseDialog(); +} + + +//--------------------------------------------------------------------------- +// OnNever +//--------------------------------------------------------------------------- + + +void CUpdateDialog::OnNever() +{ + CUserSettings::GetInstance().SetNum64(_NextUpdateCheck, -1); + CloseDialog(); +} + + +//--------------------------------------------------------------------------- +// OnCancel +//--------------------------------------------------------------------------- + + +void CUpdateDialog::OnCancel() +{ + EndDialog(0); +} + + +//--------------------------------------------------------------------------- +// EndDialog +//--------------------------------------------------------------------------- + + +void CUpdateDialog::CloseDialog() +{ + CButton *pCheckBox1 = (CButton *)GetDlgItem(ID_UPDATE_SILENT); + CUserSettings::GetInstance().SetBool(_UpdateCheckNotify, !pCheckBox1->GetCheck() == BST_CHECKED); + + EndDialog(1); +} \ No newline at end of file diff --git a/Sandboxie/apps/control/UpdateDialog.h b/Sandboxie/apps/control/UpdateDialog.h new file mode 100644 index 0000000000..240b358f8c --- /dev/null +++ b/Sandboxie/apps/control/UpdateDialog.h @@ -0,0 +1,71 @@ +/* + * Copyright 2020 David Xanatos, xanasoft.com + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +//--------------------------------------------------------------------------- +// Run Browser Dialog Box +//--------------------------------------------------------------------------- + + +#ifndef _MY_UPDATEDIALOG_H +#define _MY_UPDATEDIALOG_H + + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- + + +#define ID_RUN_BROWSER_EXPLAIN 10111 +#define ID_RUN_BROWSER_URL 10222 + + +//--------------------------------------------------------------------------- +// CUpdateDialog Class +//--------------------------------------------------------------------------- + + +#ifndef RC_INVOKED + + +#include + + +class CUpdateDialog : public CDialog +{ + DECLARE_MESSAGE_MAP() + + void *m_DlgTmplCopy; + + virtual BOOL OnInitDialog(); + + afx_msg void OnNow(); + afx_msg void OnLater(); + afx_msg void OnNever(); + afx_msg void OnCancel(); + + void CloseDialog(); + +public: + + CUpdateDialog(CWnd *pParentWnd); + ~CUpdateDialog(); + +}; + + +#endif // ! RC_INVOKED +#endif // _MY_UPDATEDIALOG_H diff --git a/Sandboxie/apps/control/Updater.cpp b/Sandboxie/apps/control/Updater.cpp new file mode 100644 index 0000000000..a4571a5ede --- /dev/null +++ b/Sandboxie/apps/control/Updater.cpp @@ -0,0 +1,487 @@ +/* + * Copyright 2020 David Xanatos, xanasoft.com + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + //--------------------------------------------------------------------------- + // Updater + //--------------------------------------------------------------------------- + +#include "stdafx.h" +#include +#include "Updater.h" +#include "UpdateDialog.h" +#include "common/RunBrowser.h" +#include "SbieIni.h" +#include "UserSettings.h" +#define CRC_WITH_ADLER32 +#include "common/crc.c" +#include "common/my_version.h" +#include "common/json/JSON.h" +#include "common/win32_ntddk.h" + + +//--------------------------------------------------------------------------- +// Variables +//--------------------------------------------------------------------------- + + +CUpdater *CUpdater::m_instance = NULL; + + +//--------------------------------------------------------------------------- +// Constructor +//--------------------------------------------------------------------------- + + +CUpdater::CUpdater() +{ + m_update_pending = false; + + //OSVERSIONINFOW m_osvi = { 0 }; + m_osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOW); + NTSTATUS(WINAPI *RtlGetVersion)(LPOSVERSIONINFOW); + *(FARPROC*)&RtlGetVersion = GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlGetVersion"); + if (RtlGetVersion == NULL || !NT_SUCCESS(RtlGetVersion(&m_osvi))) + GetVersionExW(&m_osvi); +} + + +//--------------------------------------------------------------------------- +// Destructor +//--------------------------------------------------------------------------- + + +CUpdater::~CUpdater() +{ +} + + +//--------------------------------------------------------------------------- +// GetInstance +//--------------------------------------------------------------------------- + + +CUpdater &CUpdater::GetInstance() +{ + if (!m_instance) + m_instance = new CUpdater(); + return *m_instance; +} + + +//--------------------------------------------------------------------------- +// CheckUpdates +//--------------------------------------------------------------------------- + + +bool CUpdater::CheckUpdates(CWnd *pParentWnd, bool bManual) +{ + if (m_update_pending) + return false; + + ULONG ThreadId; + ULONG_PTR *ThreadArgs = new ULONG_PTR[2]; + ThreadArgs[0] = (ULONG_PTR)pParentWnd->m_hWnd; + ThreadArgs[1] = bManual ? 1 : 0; + CreateThread(NULL, 0, UpdaterServiceThread, ThreadArgs, 0, &ThreadId); + return true; +} + + +//--------------------------------------------------------------------------- +// DownloadUpdateData +//--------------------------------------------------------------------------- + + +BOOLEAN CUpdater::DownloadUpdateData(const WCHAR* Host, const WCHAR* Path, PSTR* pData, ULONG* pDataLength) +{ + BOOLEAN success = FALSE; + + PVOID SessionHandle = NULL; + PVOID ConnectionHandle = NULL; + PVOID RequestHandle = NULL; + + { + SessionHandle = WinHttpOpen(NULL, + m_osvi.dwMajorVersion >= 8 ? WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY : WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); + if (!SessionHandle) + goto CleanupExit; + + if (m_osvi.dwMajorVersion >= 8) { + ULONG Options = WINHTTP_DECOMPRESSION_FLAG_GZIP | WINHTTP_DECOMPRESSION_FLAG_DEFLATE; + WinHttpSetOption(SessionHandle, WINHTTP_OPTION_DECOMPRESSION, &Options, sizeof(Options)); + } + } + + { + ConnectionHandle = WinHttpConnect(SessionHandle, Host, 443, 0); // ssl port + if (!ConnectionHandle) + goto CleanupExit; + } + + { + ULONG httpFlags = WINHTTP_FLAG_SECURE | WINHTTP_FLAG_REFRESH; + RequestHandle = WinHttpOpenRequest(ConnectionHandle, + NULL, Path, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, httpFlags); + + if (!RequestHandle) + goto CleanupExit; + + ULONG Options = WINHTTP_DISABLE_KEEP_ALIVE; + WinHttpSetOption(RequestHandle, WINHTTP_OPTION_DISABLE_FEATURE, &Options, sizeof(Options)); + } + + if (!WinHttpSendRequest(RequestHandle, WINHTTP_NO_ADDITIONAL_HEADERS, 0, NULL, 0, 0, 0)) + goto CleanupExit; + + if (!WinHttpReceiveResponse(RequestHandle, NULL)) + goto CleanupExit; + + { + PVOID result = NULL; + ULONG allocatedLength; + ULONG dataLength; + ULONG returnLength; + BYTE buffer[PAGE_SIZE]; + + if (pData == NULL) + goto CleanupExit; + + allocatedLength = sizeof(buffer); + *pData = (PSTR)malloc(allocatedLength); + dataLength = 0; + + while (WinHttpReadData(RequestHandle, buffer, PAGE_SIZE, &returnLength)) + { + if (returnLength == 0) + break; + + if (allocatedLength < dataLength + returnLength) + { + allocatedLength *= 2; + *pData = (PSTR)realloc(*pData, allocatedLength); + } + + memcpy(*pData + dataLength, buffer, returnLength); + + dataLength += returnLength; + } + + if (allocatedLength < dataLength + 1) + { + allocatedLength++; + *pData = (PSTR)realloc(*pData, allocatedLength); + } + + // Ensure that the buffer is null-terminated. + (*pData)[dataLength] = 0; + + if (pDataLength != NULL) + *pDataLength = dataLength; + } + + success = TRUE; + +CleanupExit: + if (RequestHandle) + WinHttpCloseHandle(RequestHandle); + if (ConnectionHandle) + WinHttpCloseHandle(ConnectionHandle); + if (SessionHandle) + WinHttpCloseHandle(SessionHandle); + + return success; +} + + +//--------------------------------------------------------------------------- +// GetJSONStringSafe +//--------------------------------------------------------------------------- + + +std::wstring GetJSONStringSafe(const JSONObject& root, const std::wstring& key, const std::wstring& default = L"") +{ + auto I = root.find(key); + if (I == root.end() || !I->second->IsString()) + return default; + return I->second->AsString(); +} + + +//--------------------------------------------------------------------------- +// QueryUpdateData +//--------------------------------------------------------------------------- + + +BOOLEAN CUpdater::QueryUpdateData(UPDATER_DATA* Context) +{ + BOOLEAN success = FALSE; + + CString Path; + + char* jsonString = NULL; + JSONValue* jsonObject = NULL; + JSONObject jsonRoot; + + Path.Format(L"/update.php?software=sandboxie&version=%S&system=windows-%d.%d.%d-%s&language=%d&auto=%s", MY_VERSION_STRING, +#ifdef _WIN64 + m_osvi.dwMajorVersion, m_osvi.dwMinorVersion, m_osvi.dwBuildNumber, L"x86_64", +#else + m_osvi.dwMajorVersion, m_osvi.dwMinorVersion, m_osvi.dwBuildNumber, L"i386", +#endif + SbieDll_GetLanguage(NULL), Context->Manual ? L"0" : L"1"); + + CString update_key; + CSbieIni::GetInstance().GetText(_GlobalSettings, L"UpdateKey", update_key); + if (!update_key.IsEmpty()) + Path += L"&update_key=" + update_key; + + if (!DownloadUpdateData(L"xanasoft.com", Path, &jsonString, NULL)) { + Context->ErrorCode = GetLastError(); + goto CleanupExit; + } + + jsonObject = JSON::Parse((const char*)jsonString); + if (jsonObject == NULL || !jsonObject->IsObject()) { + Context->ErrorCode = WEB_E_INVALID_JSON_STRING; + goto CleanupExit; + } + + jsonRoot = jsonObject->AsObject(); + + Context->userMsg = GetJSONStringSafe(jsonRoot, L"userMsg").c_str(); + Context->infoUrl = GetJSONStringSafe(jsonRoot, L"infoUrl").c_str(); + + Context->version = GetJSONStringSafe(jsonRoot, L"version").c_str(); + //Context->updated = (uint64_t)jsonRoot[L"updated"]->AsNumber(); + Context->updateMsg = GetJSONStringSafe(jsonRoot, L"updateMsg").c_str(); + Context->updateUrl = GetJSONStringSafe(jsonRoot, L"updateUrl").c_str(); + Context->downloadUrl = GetJSONStringSafe(jsonRoot, L"downloadUrl").c_str(); + + success = TRUE; + +CleanupExit: + + if (jsonString) + free(jsonString); + + if (jsonObject) + delete jsonObject; + + return success; +} + + +//--------------------------------------------------------------------------- +// DownloadUpdate +//--------------------------------------------------------------------------- + + +CString CUpdater::DownloadUpdate(const CString& downloadUrl) +{ + WCHAR TempDir[MAX_PATH + 1]; + if (GetTempPath(MAX_PATH, (LPWSTR)&TempDir) == 0) + return L""; + + int hostPos = downloadUrl.Find(L"://") + 3; + if(hostPos < 3) + return L""; + + int pathPos = downloadUrl.Find(L"/", hostPos); + if (pathPos < 0) + return L""; + + CString Host = downloadUrl.Mid(hostPos, pathPos - hostPos); + CString Path = downloadUrl.Mid(pathPos); + + int queryPos = downloadUrl.Find(L"?", pathPos); + if (queryPos < 0) + queryPos = downloadUrl.GetLength(); + + CString Name = downloadUrl.Left(queryPos); + Name = Name.Mid(Name.ReverseFind(L'/') + 1); + if (Name.IsEmpty() || Name.Right(4).CompareNoCase(L".exe") != 0) + Name = L"SandboxieInstall.exe"; + + char* data = NULL; + ULONG size = 0; + + if (!DownloadUpdateData(Host, Path, &data, &size)) + return L""; + + CString FilePath = TempDir + Name; + + ULONG bytesWriten = 0; + HANDLE hFile = CreateFile(FilePath, FILE_GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile != INVALID_HANDLE_VALUE) + { + //SetFilePointer(hFile, 0, NULL, FILE_BEGIN); + + WriteFile(hFile, data, size, &bytesWriten, NULL); + + CloseHandle(hFile); + } + + free(data); + + if(bytesWriten != size) + return L""; + + return FilePath; +} + + +//--------------------------------------------------------------------------- +// UpdaterServiceThread +//--------------------------------------------------------------------------- + + +ULONG CUpdater::UpdaterServiceThread(void *lpParameter) +{ + m_instance->m_update_pending = true; + + ULONG_PTR *ThreadArgs = (ULONG_PTR *)lpParameter; + HWND parent = (HWND)ThreadArgs[0]; + + PUPDATER_DATA pContext = new UPDATER_DATA; + pContext->Manual = ThreadArgs[1] ? TRUE : FALSE; + pContext->ErrorCode = 0; + BOOLEAN Ret = m_instance->QueryUpdateData(pContext); + if (Ret) + { + bool bNothing = true; + + CStringList IgnoredUpdates; + CUserSettings::GetInstance().GetTextList(L"_IgnoreUpdate", IgnoredUpdates); + + if (!pContext->userMsg.IsEmpty()) + { + WCHAR MsgHash[9]; + ULONG crc = CRC_Adler32((UCHAR *)(const WCHAR*)pContext->userMsg, pContext->userMsg.GetLength() * sizeof(WCHAR)); + wsprintf(MsgHash, L"%08X", crc); + + if (IgnoredUpdates.Find(MsgHash) == NULL) + { + bNothing = false; + + CString Msg = pContext->userMsg; + + int rv; + if (pContext->infoUrl.IsEmpty()) + rv = CMyApp::MsgCheckBox(NULL, Msg, 0, MB_OK); + else + { + Msg += "\n \n"; + Msg += CMyMsg(MSG_3641) + L"?"; + + rv = CMyApp::MsgCheckBox(NULL, Msg, 0, MB_YESNO); + } + + if (rv < 0) { + rv = -rv; + CUserSettings::GetInstance().AppendText(L"_IgnoreUpdate", MsgHash); + } + + if (rv == IDYES) + CRunBrowser dlg(NULL, pContext->infoUrl); + } + + } + + if (!pContext->version.IsEmpty() && pContext->version.Compare(_T(MY_VERSION_STRING)) != 0) + { + if (pContext->Manual || IgnoredUpdates.Find(pContext->version) == NULL) + { + bNothing = false; + + CString Msg = pContext->updateMsg; + if (Msg.IsEmpty()) + Msg = CMyMsg(MSG_3630, pContext->version); + + int rv; + if (pContext->updateUrl.IsEmpty() && pContext->downloadUrl.IsEmpty()) + rv = CMyApp::MsgCheckBox(NULL, Msg, 0, MB_OK); + else + { + Msg += "\n \n"; + if (!pContext->downloadUrl.IsEmpty()) + Msg += CMyMsg(MSG_3631); + else + Msg += CMyMsg(MSG_3641) + L"?"; // MSG_3631 + + rv = CMyApp::MsgCheckBox(NULL, Msg, 0, MB_YESNO); + } + + if (rv < 0) { + rv = -rv; + CUserSettings::GetInstance().AppendText(L"_IgnoreUpdate", pContext->version); + } + + if (rv == IDYES) + { + if (!pContext->downloadUrl.IsEmpty()) + { + CString downloadPath = m_instance->DownloadUpdate(pContext->downloadUrl); + if (downloadPath.IsEmpty()) + CMyApp::MsgBox(NULL, MSG_3634, MB_OK); + else + { + rv = CMyApp::MsgBox(NULL, CMyMsg(MSG_3633, pContext->version, downloadPath), MB_YESNO); + if (rv == IDYES) + { + SHELLEXECUTEINFO shex; + memzero(&shex, sizeof(SHELLEXECUTEINFO)); + shex.cbSize = sizeof(SHELLEXECUTEINFO); + shex.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI; + shex.hwnd = parent; + shex.lpFile = downloadPath; + shex.lpParameters = NULL; + shex.nShow = SW_SHOWNORMAL; + shex.lpVerb = L"runas"; + if (ShellExecuteEx(&shex)) + CloseHandle(shex.hProcess); + } + } + } + else + CRunBrowser dlg(NULL, pContext->updateUrl); + } + } + } + + if (bNothing) + { + // schedule next theck for in a week, except when the user choose never + __int64 NextUpdateCheck; + CUserSettings::GetInstance().GetNum64(_NextUpdateCheck, NextUpdateCheck, 0); + if (NextUpdateCheck != -1) + CUserSettings::GetInstance().SetNum64(_NextUpdateCheck, time(NULL) + 7 * 24 * 60 * 60); + + if (pContext->Manual) + CMyApp::MsgBox(NULL, MSG_3629, MB_OK); + } + } + else if (pContext->Manual) + CMyApp::MsgBox(NULL, MSG_3634, MB_OK); + + //::PostMessage(parent, WM_UPDATERESULT, Ret, (LPARAM)pContext); + delete pContext; + + m_instance->m_update_pending = false; + + return 0; +} \ No newline at end of file diff --git a/Sandboxie/apps/control/Updater.h b/Sandboxie/apps/control/Updater.h new file mode 100644 index 0000000000..321d48d249 --- /dev/null +++ b/Sandboxie/apps/control/Updater.h @@ -0,0 +1,74 @@ +/* + * Copyright 2020 David Xanatos, xanasoft.com + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + //--------------------------------------------------------------------------- + // Updater + //--------------------------------------------------------------------------- + + +#ifndef _MY_UPDATER_H +#define _MY_UPDATER_H + +//#define WM_UPDATERESULT (WM_APP + 10) + +typedef struct _UPDATER_DATA +{ + CString userMsg; + CString infoUrl; + + CString version; + //unsigned __int64 updated; + CString updateMsg; + CString updateUrl; + CString downloadUrl; + + BOOLEAN Manual; + ULONG ErrorCode; + +} UPDATER_DATA, *PUPDATER_DATA; + +extern const WCHAR *_UpdateCheckNotify; +extern const WCHAR *_NextUpdateCheck; + +class CUpdater +{ + static CUpdater *m_instance; + + bool m_update_pending; + + OSVERSIONINFOW m_osvi; + + CUpdater(); + + BOOLEAN DownloadUpdateData(const WCHAR* Host, const WCHAR* Path, PSTR* pData, ULONG* dataLength); + BOOLEAN QueryUpdateData(UPDATER_DATA* Context); + CString DownloadUpdate(const CString& downloadUrl); + + static ULONG UpdaterServiceThread(void *lpParameter); + +public: + + ~CUpdater(); + + static CUpdater &GetInstance(); + + bool CheckUpdates(CWnd *pParentWnd, bool bManual = true); + + +}; + +#endif // _MY_UPDATER_H \ No newline at end of file diff --git a/Sandboxie/apps/control/resource.h b/Sandboxie/apps/control/resource.h index 686ba1ce46..b80680046b 100644 --- a/Sandboxie/apps/control/resource.h +++ b/Sandboxie/apps/control/resource.h @@ -46,9 +46,11 @@ #define ID_CONF_RELOAD 40037 #define ID_CONF_THIRD_PARTY 40038 #define ID_CONF_LOCK 40039 +#define ID_HELP_SUPPORT 40040 #define ID_HELP_TOPICS 40041 #define ID_HELP_TUTORIAL 40042 #define ID_HELP_FORUM 40043 +#define ID_HELP_UPDATE 40044 #define ID_HELP_ABOUT 40045 #define ID_PROCESS_TERMINATE 40051 #define ID_PROCESS_SETTINGS 40052 diff --git a/Sandboxie/common/json/JSON.cpp b/Sandboxie/common/json/JSON.cpp new file mode 100644 index 0000000000..0b966c039a --- /dev/null +++ b/Sandboxie/common/json/JSON.cpp @@ -0,0 +1,280 @@ +/* + * File JSON.cpp part of the SimpleJSON Library - http://mjpa.in/json + * + * Copyright (C) 2010 Mike Anchor + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "JSON.h" + +/** + * Blocks off the public constructor + * + * @access private + * + */ +JSON::JSON() +{ +} + +/** + * Parses a complete JSON encoded string + * This is just a wrapper around the UNICODE Parse(). + * + * @access public + * + * @param char* data The JSON text + * + * @return JSONValue* Returns a JSON Value representing the root, or NULL on error + */ +JSONValue *JSON::Parse(const char *data) +{ + size_t length = strlen(data) + 1; + wchar_t *w_data = (wchar_t*)malloc(length * sizeof(wchar_t)); + + #if defined(WIN32) && !defined(__GNUC__) + size_t ret_value = 0; + if (mbstowcs_s(&ret_value, w_data, length, data, length) != 0) + { + free(w_data); + return NULL; + } + #elif defined(ANDROID) + // mbstowcs seems to misbehave on android + for(size_t i = 0; iStringify(); + else + return L""; +} + +/** + * Skips over any whitespace characters (space, tab, \r or \n) defined by the JSON spec + * + * @access protected + * + * @param wchar_t** data Pointer to a wchar_t* that contains the JSON text + * + * @return bool Returns true if there is more data, or false if the end of the text was reached + */ +bool JSON::SkipWhitespace(const wchar_t **data) +{ + while (**data != 0 && (**data == L' ' || **data == L'\t' || **data == L'\r' || **data == L'\n')) + (*data)++; + + return **data != 0; +} + +/** + * Extracts a JSON String as defined by the spec - "" + * Any escaped characters are swapped out for their unescaped values + * + * @access protected + * + * @param wchar_t** data Pointer to a wchar_t* that contains the JSON text + * @param std::wstring& str Reference to a std::wstring to receive the extracted string + * + * @return bool Returns true on success, false on failure + */ +bool JSON::ExtractString(const wchar_t **data, std::wstring &str) +{ + str = L""; + + while (**data != 0) + { + // Save the char so we can change it if need be + wchar_t next_char = **data; + + // Escaping something? + if (next_char == L'\\') + { + // Move over the escape char + (*data)++; + + // Deal with the escaped char + switch (**data) + { + case L'"': next_char = L'"'; break; + case L'\\': next_char = L'\\'; break; + case L'/': next_char = L'/'; break; + case L'b': next_char = L'\b'; break; + case L'f': next_char = L'\f'; break; + case L'n': next_char = L'\n'; break; + case L'r': next_char = L'\r'; break; + case L't': next_char = L'\t'; break; + case L'u': + { + // We need 5 chars (4 hex + the 'u') or its not valid + if (!simplejson_wcsnlen(*data, 5)) + return false; + + // Deal with the chars + next_char = 0; + for (int i = 0; i < 4; i++) + { + // Do it first to move off the 'u' and leave us on the + // final hex digit as we move on by one later on + (*data)++; + + next_char <<= 4; + + // Parse the hex digit + if (**data >= '0' && **data <= '9') + next_char |= (**data - '0'); + else if (**data >= 'A' && **data <= 'F') + next_char |= (10 + (**data - 'A')); + else if (**data >= 'a' && **data <= 'f') + next_char |= (10 + (**data - 'a')); + else + { + // Invalid hex digit = invalid JSON + return false; + } + } + break; + } + + // By the spec, only the above cases are allowed + default: + return false; + } + } + + // End of the string? + else if (next_char == L'"') + { + (*data)++; + str.reserve(); // Remove unused capacity + return true; + } + + // Disallowed char? + else if (next_char < L' ' && next_char != L'\t') + { + // SPEC Violation: Allow tabs due to real world cases + return false; + } + + // Add the next char + str += next_char; + + // Move on + (*data)++; + } + + // If we're here, the string ended incorrectly + return false; +} + +/** + * Parses some text as though it is an integer + * + * @access protected + * + * @param wchar_t** data Pointer to a wchar_t* that contains the JSON text + * + * @return double Returns the double value of the number found + */ +double JSON::ParseInt(const wchar_t **data) +{ + double integer = 0; + while (**data != 0 && **data >= '0' && **data <= '9') + integer = integer * 10 + (*(*data)++ - '0'); + + return integer; +} + +/** + * Parses some text as though it is a decimal + * + * @access protected + * + * @param wchar_t** data Pointer to a wchar_t* that contains the JSON text + * + * @return double Returns the double value of the decimal found + */ +double JSON::ParseDecimal(const wchar_t **data) +{ + double decimal = 0.0; + double factor = 0.1; + while (**data != 0 && **data >= '0' && **data <= '9') + { + int digit = (*(*data)++ - '0'); + decimal = decimal + digit * factor; + factor *= 0.1; + } + return decimal; +} diff --git a/Sandboxie/common/json/JSON.h b/Sandboxie/common/json/JSON.h new file mode 100644 index 0000000000..184a5de3ab --- /dev/null +++ b/Sandboxie/common/json/JSON.h @@ -0,0 +1,112 @@ +/* + * File JSON.h part of the SimpleJSON Library - http://mjpa.in/json + * + * Copyright (C) 2010 Mike Anchor + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef _JSON_H_ +#define _JSON_H_ + +// Win32 incompatibilities +#if defined(WIN32) && !defined(__GNUC__) + #define wcsncasecmp _wcsnicmp + static inline bool isnan(double x) { return x != x; } + static inline bool isinf(double x) { return !isnan(x) && isnan(x - x); } +#endif + +#include +#include +#include + +// Linux compile fix - from quaker66 +#ifdef __GNUC__ + #include + #include +#endif + +// Mac compile fixes - from quaker66, Lion fix by dabrahams +#if defined(__APPLE__) && __DARWIN_C_LEVEL < 200809L || (defined(WIN32) && defined(__GNUC__)) || defined(ANDROID) + #include + #include + + static inline int wcsncasecmp(const wchar_t *s1, const wchar_t *s2, size_t n) + { + int lc1 = 0; + int lc2 = 0; + + while (n--) + { + lc1 = towlower (*s1); + lc2 = towlower (*s2); + + if (lc1 != lc2) + return (lc1 - lc2); + + if (!lc1) + return 0; + + ++s1; + ++s2; + } + + return 0; + } +#endif + +// Simple function to check a string 's' has at least 'n' characters +static inline bool simplejson_wcsnlen(const wchar_t *s, size_t n) { + if (s == 0) + return false; + + const wchar_t *save = s; + while (n-- > 0) + { + if (*(save++) == 0) return false; + } + + return true; +} + +// Custom types +class JSONValue; +typedef std::vector JSONArray; +typedef std::map JSONObject; + +#include "JSONValue.h" + +class JSON +{ + friend class JSONValue; + + public: + static JSONValue* Parse(const char *data); + static JSONValue* Parse(const wchar_t *data); + static std::wstring Stringify(const JSONValue *value); + protected: + static bool SkipWhitespace(const wchar_t **data); + static bool ExtractString(const wchar_t **data, std::wstring &str); + static double ParseInt(const wchar_t **data); + static double ParseDecimal(const wchar_t **data); + private: + JSON(); +}; + +#endif diff --git a/Sandboxie/common/json/JSONValue.cpp b/Sandboxie/common/json/JSONValue.cpp new file mode 100644 index 0000000000..6aa79bfd6d --- /dev/null +++ b/Sandboxie/common/json/JSONValue.cpp @@ -0,0 +1,944 @@ +/* + * File JSONValue.cpp part of the SimpleJSON Library - http://mjpa.in/json + * + * Copyright (C) 2010 Mike Anchor + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "JSONValue.h" + +#ifdef __MINGW32__ +#define wcsncasecmp wcsnicmp +#endif + +// Macros to free an array/object +#define FREE_ARRAY(x) { JSONArray::iterator iter; for (iter = x.begin(); iter != x.end(); iter++) { delete *iter; } } +#define FREE_OBJECT(x) { JSONObject::iterator iter; for (iter = x.begin(); iter != x.end(); iter++) { delete (*iter).second; } } + +/** + * Parses a JSON encoded value to a JSONValue object + * + * @access protected + * + * @param wchar_t** data Pointer to a wchar_t* that contains the data + * + * @return JSONValue* Returns a pointer to a JSONValue object on success, NULL on error + */ +JSONValue *JSONValue::Parse(const wchar_t **data) +{ + // Is it a string? + if (**data == '"') + { + std::wstring str; + if (!JSON::ExtractString(&(++(*data)), str)) + return NULL; + else + return new JSONValue(str); + } + + // Is it a boolean? + else if ((simplejson_wcsnlen(*data, 4) && wcsncasecmp(*data, L"true", 4) == 0) || (simplejson_wcsnlen(*data, 5) && wcsncasecmp(*data, L"false", 5) == 0)) + { + bool value = wcsncasecmp(*data, L"true", 4) == 0; + (*data) += value ? 4 : 5; + return new JSONValue(value); + } + + // Is it a null? + else if (simplejson_wcsnlen(*data, 4) && wcsncasecmp(*data, L"null", 4) == 0) + { + (*data) += 4; + return new JSONValue(); + } + + // Is it a number? + else if (**data == L'-' || (**data >= L'0' && **data <= L'9')) + { + // Negative? + bool neg = **data == L'-'; + if (neg) (*data)++; + + double number = 0.0; + + // Parse the whole part of the number - only if it wasn't 0 + if (**data == L'0') + (*data)++; + else if (**data >= L'1' && **data <= L'9') + number = JSON::ParseInt(data); + else + return NULL; + + // Could be a decimal now... + if (**data == '.') + { + (*data)++; + + // Not get any digits? + if (!(**data >= L'0' && **data <= L'9')) + return NULL; + + // Find the decimal and sort the decimal place out + // Use ParseDecimal as ParseInt won't work with decimals less than 0.1 + // thanks to Javier Abadia for the report & fix + double decimal = JSON::ParseDecimal(data); + + // Save the number + number += decimal; + } + + // Could be an exponent now... + if (**data == L'E' || **data == L'e') + { + (*data)++; + + // Check signage of expo + bool neg_expo = false; + if (**data == L'-' || **data == L'+') + { + neg_expo = **data == L'-'; + (*data)++; + } + + // Not get any digits? + if (!(**data >= L'0' && **data <= L'9')) + return NULL; + + // Sort the expo out + double expo = JSON::ParseInt(data); + for (double i = 0.0; i < expo; i++) + number = neg_expo ? (number / 10.0) : (number * 10.0); + } + + // Was it neg? + if (neg) number *= -1; + + return new JSONValue(number); + } + + // An object? + else if (**data == L'{') + { + JSONObject object; + + (*data)++; + + while (**data != 0) + { + // Whitespace at the start? + if (!JSON::SkipWhitespace(data)) + { + FREE_OBJECT(object); + return NULL; + } + + // Special case - empty object + if (object.size() == 0 && **data == L'}') + { + (*data)++; + return new JSONValue(object); + } + + // We want a string now... + std::wstring name; + if (!JSON::ExtractString(&(++(*data)), name)) + { + FREE_OBJECT(object); + return NULL; + } + + // More whitespace? + if (!JSON::SkipWhitespace(data)) + { + FREE_OBJECT(object); + return NULL; + } + + // Need a : now + if (*((*data)++) != L':') + { + FREE_OBJECT(object); + return NULL; + } + + // More whitespace? + if (!JSON::SkipWhitespace(data)) + { + FREE_OBJECT(object); + return NULL; + } + + // The value is here + JSONValue *value = Parse(data); + if (value == NULL) + { + FREE_OBJECT(object); + return NULL; + } + + // Add the name:value + if (object.find(name) != object.end()) + delete object[name]; + object[name] = value; + + // More whitespace? + if (!JSON::SkipWhitespace(data)) + { + FREE_OBJECT(object); + return NULL; + } + + // End of object? + if (**data == L'}') + { + (*data)++; + return new JSONValue(object); + } + + // Want a , now + if (**data != L',') + { + FREE_OBJECT(object); + return NULL; + } + + (*data)++; + } + + // Only here if we ran out of data + FREE_OBJECT(object); + return NULL; + } + + // An array? + else if (**data == L'[') + { + JSONArray array; + + (*data)++; + + while (**data != 0) + { + // Whitespace at the start? + if (!JSON::SkipWhitespace(data)) + { + FREE_ARRAY(array); + return NULL; + } + + // Special case - empty array + if (array.size() == 0 && **data == L']') + { + (*data)++; + return new JSONValue(array); + } + + // Get the value + JSONValue *value = Parse(data); + if (value == NULL) + { + FREE_ARRAY(array); + return NULL; + } + + // Add the value + array.push_back(value); + + // More whitespace? + if (!JSON::SkipWhitespace(data)) + { + FREE_ARRAY(array); + return NULL; + } + + // End of array? + if (**data == L']') + { + (*data)++; + return new JSONValue(array); + } + + // Want a , now + if (**data != L',') + { + FREE_ARRAY(array); + return NULL; + } + + (*data)++; + } + + // Only here if we ran out of data + FREE_ARRAY(array); + return NULL; + } + + // Ran out of possibilites, it's bad! + else + { + return NULL; + } +} + +/** + * Basic constructor for creating a JSON Value of type NULL + * + * @access public + */ +JSONValue::JSONValue(/*NULL*/) +{ + type = JSONType_Null; +} + +/** + * Basic constructor for creating a JSON Value of type String + * + * @access public + * + * @param wchar_t* m_char_value The string to use as the value + */ +JSONValue::JSONValue(const wchar_t *m_char_value) +{ + type = JSONType_String; + string_value = new std::wstring(std::wstring(m_char_value)); +} + +/** + * Basic constructor for creating a JSON Value of type String + * + * @access public + * + * @param std::wstring m_string_value The string to use as the value + */ +JSONValue::JSONValue(const std::wstring &m_string_value) +{ + type = JSONType_String; + string_value = new std::wstring(m_string_value); +} + +/** + * Basic constructor for creating a JSON Value of type Bool + * + * @access public + * + * @param bool m_bool_value The bool to use as the value + */ +JSONValue::JSONValue(bool m_bool_value) +{ + type = JSONType_Bool; + bool_value = m_bool_value; +} + +/** + * Basic constructor for creating a JSON Value of type Number + * + * @access public + * + * @param double m_number_value The number to use as the value + */ +JSONValue::JSONValue(double m_number_value) +{ + type = JSONType_Number; + number_value = m_number_value; +} + +/** + * Basic constructor for creating a JSON Value of type Number + * + * @access public + * + * @param int m_integer_value The number to use as the value + */ +JSONValue::JSONValue(int m_integer_value) +{ + type = JSONType_Number; + number_value = (double) m_integer_value; +} + +/** + * Basic constructor for creating a JSON Value of type Array + * + * @access public + * + * @param JSONArray m_array_value The JSONArray to use as the value + */ +JSONValue::JSONValue(const JSONArray &m_array_value) +{ + type = JSONType_Array; + array_value = new JSONArray(m_array_value); +} + +/** + * Basic constructor for creating a JSON Value of type Object + * + * @access public + * + * @param JSONObject m_object_value The JSONObject to use as the value + */ +JSONValue::JSONValue(const JSONObject &m_object_value) +{ + type = JSONType_Object; + object_value = new JSONObject(m_object_value); +} + +/** + * Copy constructor to perform a deep copy of array / object values + * + * @access public + * + * @param JSONValue m_source The source JSONValue that is being copied + */ +JSONValue::JSONValue(const JSONValue &m_source) +{ + type = m_source.type; + + switch (type) + { + case JSONType_String: + string_value = new std::wstring(*m_source.string_value); + break; + + case JSONType_Bool: + bool_value = m_source.bool_value; + break; + + case JSONType_Number: + number_value = m_source.number_value; + break; + + case JSONType_Array: + { + JSONArray source_array = *m_source.array_value; + JSONArray::iterator iter; + array_value = new JSONArray(); + for (iter = source_array.begin(); iter != source_array.end(); iter++) + array_value->push_back(new JSONValue(**iter)); + break; + } + + case JSONType_Object: + { + JSONObject source_object = *m_source.object_value; + object_value = new JSONObject(); + JSONObject::iterator iter; + for (iter = source_object.begin(); iter != source_object.end(); iter++) + { + std::wstring name = (*iter).first; + (*object_value)[name] = new JSONValue(*((*iter).second)); + } + break; + } + + case JSONType_Null: + // Nothing to do. + break; + } +} + +/** + * The destructor for the JSON Value object + * Handles deleting the objects in the array or the object value + * + * @access public + */ +JSONValue::~JSONValue() +{ + if (type == JSONType_Array) + { + JSONArray::iterator iter; + for (iter = array_value->begin(); iter != array_value->end(); iter++) + delete *iter; + delete array_value; + } + else if (type == JSONType_Object) + { + JSONObject::iterator iter; + for (iter = object_value->begin(); iter != object_value->end(); iter++) + { + delete (*iter).second; + } + delete object_value; + } + else if (type == JSONType_String) + { + delete string_value; + } +} + +/** + * Checks if the value is a NULL + * + * @access public + * + * @return bool Returns true if it is a NULL value, false otherwise + */ +bool JSONValue::IsNull() const +{ + return type == JSONType_Null; +} + +/** + * Checks if the value is a String + * + * @access public + * + * @return bool Returns true if it is a String value, false otherwise + */ +bool JSONValue::IsString() const +{ + return type == JSONType_String; +} + +/** + * Checks if the value is a Bool + * + * @access public + * + * @return bool Returns true if it is a Bool value, false otherwise + */ +bool JSONValue::IsBool() const +{ + return type == JSONType_Bool; +} + +/** + * Checks if the value is a Number + * + * @access public + * + * @return bool Returns true if it is a Number value, false otherwise + */ +bool JSONValue::IsNumber() const +{ + return type == JSONType_Number; +} + +/** + * Checks if the value is an Array + * + * @access public + * + * @return bool Returns true if it is an Array value, false otherwise + */ +bool JSONValue::IsArray() const +{ + return type == JSONType_Array; +} + +/** + * Checks if the value is an Object + * + * @access public + * + * @return bool Returns true if it is an Object value, false otherwise + */ +bool JSONValue::IsObject() const +{ + return type == JSONType_Object; +} + +/** + * Retrieves the String value of this JSONValue + * Use IsString() before using this method. + * + * @access public + * + * @return std::wstring Returns the string value + */ +const std::wstring &JSONValue::AsString() const +{ + return (*string_value); +} + +/** + * Retrieves the Bool value of this JSONValue + * Use IsBool() before using this method. + * + * @access public + * + * @return bool Returns the bool value + */ +bool JSONValue::AsBool() const +{ + return bool_value; +} + +/** + * Retrieves the Number value of this JSONValue + * Use IsNumber() before using this method. + * + * @access public + * + * @return double Returns the number value + */ +double JSONValue::AsNumber() const +{ + return number_value; +} + +/** + * Retrieves the Array value of this JSONValue + * Use IsArray() before using this method. + * + * @access public + * + * @return JSONArray Returns the array value + */ +const JSONArray &JSONValue::AsArray() const +{ + return (*array_value); +} + +/** + * Retrieves the Object value of this JSONValue + * Use IsObject() before using this method. + * + * @access public + * + * @return JSONObject Returns the object value + */ +const JSONObject &JSONValue::AsObject() const +{ + return (*object_value); +} + +/** + * Retrieves the number of children of this JSONValue. + * This number will be 0 or the actual number of children + * if IsArray() or IsObject(). + * + * @access public + * + * @return The number of children. + */ +std::size_t JSONValue::CountChildren() const +{ + switch (type) + { + case JSONType_Array: + return array_value->size(); + case JSONType_Object: + return object_value->size(); + default: + return 0; + } +} + +/** + * Checks if this JSONValue has a child at the given index. + * Use IsArray() before using this method. + * + * @access public + * + * @return bool Returns true if the array has a value at the given index. + */ +bool JSONValue::HasChild(std::size_t index) const +{ + if (type == JSONType_Array) + { + return index < array_value->size(); + } + else + { + return false; + } +} + +/** + * Retrieves the child of this JSONValue at the given index. + * Use IsArray() before using this method. + * + * @access public + * + * @return JSONValue* Returns JSONValue at the given index or NULL + * if it doesn't exist. + */ +JSONValue *JSONValue::Child(std::size_t index) +{ + if (index < array_value->size()) + { + return (*array_value)[index]; + } + else + { + return NULL; + } +} + +/** + * Checks if this JSONValue has a child at the given key. + * Use IsObject() before using this method. + * + * @access public + * + * @return bool Returns true if the object has a value at the given key. + */ +bool JSONValue::HasChild(const wchar_t* name) const +{ + if (type == JSONType_Object) + { + return object_value->find(name) != object_value->end(); + } + else + { + return false; + } +} + +/** + * Retrieves the child of this JSONValue at the given key. + * Use IsObject() before using this method. + * + * @access public + * + * @return JSONValue* Returns JSONValue for the given key in the object + * or NULL if it doesn't exist. + */ +JSONValue* JSONValue::Child(const wchar_t* name) +{ + JSONObject::const_iterator it = object_value->find(name); + if (it != object_value->end()) + { + return it->second; + } + else + { + return NULL; + } +} + +/** + * Retrieves the keys of the JSON Object or an empty vector + * if this value is not an object. + * + * @access public + * + * @return std::vector A vector containing the keys. + */ +std::vector JSONValue::ObjectKeys() const +{ + std::vector keys; + + if (type == JSONType_Object) + { + JSONObject::const_iterator iter = object_value->begin(); + while (iter != object_value->end()) + { + keys.push_back(iter->first); + + iter++; + } + } + + return keys; +} + +/** + * Creates a JSON encoded string for the value with all necessary characters escaped + * + * @access public + * + * @param bool prettyprint Enable prettyprint + * + * @return std::wstring Returns the JSON string + */ +std::wstring JSONValue::Stringify(bool const prettyprint) const +{ + size_t const indentDepth = prettyprint ? 1 : 0; + return StringifyImpl(indentDepth); +} + + +/** + * Creates a JSON encoded string for the value with all necessary characters escaped + * + * @access private + * + * @param size_t indentDepth The prettyprint indentation depth (0 : no prettyprint) + * + * @return std::wstring Returns the JSON string + */ +std::wstring JSONValue::StringifyImpl(size_t const indentDepth) const +{ + std::wstring ret_string; + size_t const indentDepth1 = indentDepth ? indentDepth + 1 : 0; + std::wstring const indentStr = Indent(indentDepth); + std::wstring const indentStr1 = Indent(indentDepth1); + + switch (type) + { + case JSONType_Null: + ret_string = L"null"; + break; + + case JSONType_String: + ret_string = StringifyString(*string_value); + break; + + case JSONType_Bool: + ret_string = bool_value ? L"true" : L"false"; + break; + + case JSONType_Number: + { + if (isinf(number_value) || isnan(number_value)) + ret_string = L"null"; + else + { + std::wstringstream ss; + ss.precision(15); + ss << number_value; + ret_string = ss.str(); + } + break; + } + + case JSONType_Array: + { + ret_string = indentDepth ? L"[\n" + indentStr1 : L"["; + JSONArray::const_iterator iter = array_value->begin(); + while (iter != array_value->end()) + { + ret_string += (*iter)->StringifyImpl(indentDepth1); + + // Not at the end - add a separator + if (++iter != array_value->end()) + ret_string += L","; + } + ret_string += indentDepth ? L"\n" + indentStr + L"]" : L"]"; + break; + } + + case JSONType_Object: + { + ret_string = indentDepth ? L"{\n" + indentStr1 : L"{"; + JSONObject::const_iterator iter = object_value->begin(); + while (iter != object_value->end()) + { + ret_string += StringifyString((*iter).first); + ret_string += L":"; + ret_string += (*iter).second->StringifyImpl(indentDepth1); + + // Not at the end - add a separator + if (++iter != object_value->end()) + ret_string += L","; + } + ret_string += indentDepth ? L"\n" + indentStr + L"}" : L"}"; + break; + } + } + + return ret_string; +} + +/** + * Creates a JSON encoded string with all required fields escaped + * Works from http://www.ecma-internationl.org/publications/files/ECMA-ST/ECMA-262.pdf + * Section 15.12.3. + * + * @access private + * + * @param std::wstring str The string that needs to have the characters escaped + * + * @return std::wstring Returns the JSON string + */ +std::wstring JSONValue::StringifyString(const std::wstring &str) +{ + std::wstring str_out = L"\""; + + std::wstring::const_iterator iter = str.begin(); + while (iter != str.end()) + { + wchar_t chr = *iter; + + if (chr == L'"' || chr == L'\\' || chr == L'/') + { + str_out += L'\\'; + str_out += chr; + } + else if (chr == L'\b') + { + str_out += L"\\b"; + } + else if (chr == L'\f') + { + str_out += L"\\f"; + } + else if (chr == L'\n') + { + str_out += L"\\n"; + } + else if (chr == L'\r') + { + str_out += L"\\r"; + } + else if (chr == L'\t') + { + str_out += L"\\t"; + } + else if (chr < L' ' || chr > 126) + { + str_out += L"\\u"; + for (int i = 0; i < 4; i++) + { + int value = (chr >> 12) & 0xf; + if (value >= 0 && value <= 9) + str_out += (wchar_t)('0' + value); + else if (value >= 10 && value <= 15) + str_out += (wchar_t)('A' + (value - 10)); + chr <<= 4; + } + } + else + { + str_out += chr; + } + + iter++; + } + + str_out += L"\""; + return str_out; +} + +/** + * Creates the indentation string for the depth given + * + * @access private + * + * @param size_t indent The prettyprint indentation depth (0 : no indentation) + * + * @return std::wstring Returns the string + */ +std::wstring JSONValue::Indent(size_t depth) +{ + const size_t indent_step = 2; + depth ? --depth : 0; + std::wstring indentStr(depth * indent_step, ' '); + return indentStr; +} diff --git a/Sandboxie/common/json/JSONValue.h b/Sandboxie/common/json/JSONValue.h new file mode 100644 index 0000000000..1e3822888d --- /dev/null +++ b/Sandboxie/common/json/JSONValue.h @@ -0,0 +1,95 @@ +/* + * File JSONValue.h part of the SimpleJSON Library - http://mjpa.in/json + * + * Copyright (C) 2010 Mike Anchor + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef _JSONVALUE_H_ +#define _JSONVALUE_H_ + +#include +#include + +#include "JSON.h" + +class JSON; + +enum JSONType { JSONType_Null, JSONType_String, JSONType_Bool, JSONType_Number, JSONType_Array, JSONType_Object }; + +class JSONValue +{ + friend class JSON; + + public: + JSONValue(/*NULL*/); + JSONValue(const wchar_t *m_char_value); + JSONValue(const std::wstring &m_string_value); + JSONValue(bool m_bool_value); + JSONValue(double m_number_value); + JSONValue(int m_integer_value); + JSONValue(const JSONArray &m_array_value); + JSONValue(const JSONObject &m_object_value); + JSONValue(const JSONValue &m_source); + ~JSONValue(); + + bool IsNull() const; + bool IsString() const; + bool IsBool() const; + bool IsNumber() const; + bool IsArray() const; + bool IsObject() const; + + const std::wstring &AsString() const; + bool AsBool() const; + double AsNumber() const; + const JSONArray &AsArray() const; + const JSONObject &AsObject() const; + + std::size_t CountChildren() const; + bool HasChild(std::size_t index) const; + JSONValue *Child(std::size_t index); + bool HasChild(const wchar_t* name) const; + JSONValue *Child(const wchar_t* name); + std::vector ObjectKeys() const; + + std::wstring Stringify(bool const prettyprint = false) const; + protected: + static JSONValue *Parse(const wchar_t **data); + + private: + static std::wstring StringifyString(const std::wstring &str); + std::wstring StringifyImpl(size_t const indentDepth) const; + static std::wstring Indent(size_t depth); + + JSONType type; + + union + { + bool bool_value; + double number_value; + std::wstring *string_value; + JSONArray *array_value; + JSONObject *object_value; + }; + +}; + +#endif diff --git a/Sandboxie/core/svc/DriverAssistStart.cpp b/Sandboxie/core/svc/DriverAssistStart.cpp index e04b36a3cb..48cfd40634 100644 --- a/Sandboxie/core/svc/DriverAssistStart.cpp +++ b/Sandboxie/core/svc/DriverAssistStart.cpp @@ -49,10 +49,23 @@ ULONG DriverAssist::StartDriverAsync(void *arg) // get windows version // - OSVERSIONINFO osvi; + OSVERSIONINFOW osvi; memzero(&osvi, sizeof(osvi)); - osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - GetVersionEx(&osvi); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOW); + NTSTATUS(WINAPI *RtlGetVersion)(LPOSVERSIONINFOW); + *(FARPROC*)&RtlGetVersion = GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlGetVersion"); + if (RtlGetVersion == NULL || !NT_SUCCESS(RtlGetVersion(&osvi))) + GetVersionExW(&osvi); + + // + // get own path + // + + WCHAR svcPath[MAX_PATH] = { 0 }; + GetModuleFileName(NULL, svcPath, MAX_PATH); + WCHAR* pathPtr = wcsrchr(svcPath, L'\\'); + if (pathPtr++) + *pathPtr = L'\0'; // // start the driver, but only if it isn't already active @@ -76,46 +89,132 @@ ULONG DriverAssist::StartDriverAsync(void *arg) L"\\Registry\\Machine\\System\\CurrentControlSet" L"\\Services\\" SBIEDRV); - rc = NtLoadDriver(&uni); - if (rc == 0 || rc == STATUS_IMAGE_ALREADY_LOADED) { - ok = true; - goto driver_started; - } + //while (!IsDebuggerPresent()) + // Sleep(100); + + m_instance->LogMessage_Single(MSG_2201, L"Starting Driver..."); + + bool PrivilegeSet = false; + bool CopyDriver = false; + bool DriverUnPacked = false; + for (; ; ) //for (int i = 0; i < 10; i++) + { + m_instance->LogMessage_Single(MSG_2201, L"Attempting to Load Driver..."); - if (rc != STATUS_PRIVILEGE_NOT_HELD || rc == STATUS_ACCESS_DENIED) { - LogEvent(MSG_9234, 0x9153, rc); - goto driver_started; - } + rc = NtLoadDriver(&uni); + if (rc == 0 || rc == STATUS_IMAGE_ALREADY_LOADED) { + ok = true; + break; + } - // - // we have to enable a privilege to load the driver - // + if (rc == STATUS_PRIVILEGE_NOT_HELD && !PrivilegeSet) + { + m_instance->LogMessage_Single(MSG_2201, L"Acquiring necessary privileges..."); - WCHAR priv_space[64]; - TOKEN_PRIVILEGES *privs = (TOKEN_PRIVILEGES *)priv_space; - HANDLE hToken; + // + // we have to enable a privilege to load the driver + // - BOOL b = LookupPrivilegeValue( - L"", SE_LOAD_DRIVER_NAME, &privs->Privileges[0].Luid); - if (b) { + WCHAR priv_space[64]; + TOKEN_PRIVILEGES *privs = (TOKEN_PRIVILEGES *)priv_space; + HANDLE hToken; - privs->Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - privs->PrivilegeCount = 1; + BOOL b = LookupPrivilegeValue( + L"", SE_LOAD_DRIVER_NAME, &privs->Privileges[0].Luid); + if (b) { - b = OpenProcessToken( - GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken); - if (b) { + privs->Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + privs->PrivilegeCount = 1; - b = AdjustTokenPrivileges(hToken, FALSE, privs, 0, NULL, NULL); - CloseHandle(hToken); - } - } + b = OpenProcessToken( + GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken); + if (b) { - rc = NtLoadDriver(&uni); - if (rc == 0 || rc == STATUS_IMAGE_ALREADY_LOADED) - ok = true; - else - LogEvent(MSG_9234, 0x9153, rc); + b = AdjustTokenPrivileges(hToken, FALSE, privs, 0, NULL, NULL); + CloseHandle(hToken); + } + } + + PrivilegeSet = true; + continue; + } + + /*if (rc == STATUS_OBJECT_NAME_NOT_FOUND && DriverUnPacked) + { + m_instance->LogMessage_Single(MSG_2201, L"The provisionally driver got deleted..."); + + // + // The provisionally signed driver got deleted, try to restore it + // + + DriverUnPacked = false; + rc == STATUS_INVALID_IMAGE_HASH; + }*/ + + if (rc == STATUS_OBJECT_NAME_NOT_FOUND && !CopyDriver) + { + m_instance->LogMessage_Single(MSG_2201, L"Preparing the windows 10 signed driver..."); + + // + // there is no driver file we have to prepare one + // + + WCHAR srcPath[MAX_PATH]; + wcscpy(srcPath, svcPath); + wcscat(srcPath, SBIEDRV_SYS L".w10"); + + WCHAR destPath[MAX_PATH]; + wcscpy(destPath, svcPath); + wcscat(destPath, SBIEDRV_SYS); + + CopyFile(srcPath, destPath, FALSE); + + CopyDriver = true; + continue; + } + + if (rc == STATUS_INVALID_IMAGE_HASH && !DriverUnPacked) + { + m_instance->LogMessage_Single(MSG_2201, L"Preparing the provisionally signed driver..."); + + // + // the driver signature was not accepted, try the provisionally signed driver + // + + WCHAR cmd[512]; + wcscpy(cmd, L"\""); + wcscat(cmd, svcPath); + wcscat(cmd, L"KmdUtil.exe"); + wcscat(cmd, L"\" rc4 \""); + wcscat(cmd, svcPath); + wcscat(cmd, SBIEDRV_SYS L".rc4"); + wcscat(cmd, L"\""); + + STARTUPINFO si; + ZeroMemory(&si, sizeof(STARTUPINFO)); + si.cb = sizeof(STARTUPINFO); + si.dwFlags = STARTF_FORCEOFFFEEDBACK; + + PROCESS_INFORMATION pi; + if (CreateProcess(NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { + + WaitForSingleObject(pi.hProcess, 10 * 1000); + + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + } + + DriverUnPacked = true; + continue; + } + + // + // we tryed all we could, log an error and break + // + + LogEvent(MSG_9234, 0x9153, rc); + break; + } // // the driver has been started (or was started already), check diff --git a/Sandboxie/core/svc/GuiServer.cpp b/Sandboxie/core/svc/GuiServer.cpp index dd1b75d408..9f5171d260 100644 --- a/Sandboxie/core/svc/GuiServer.cpp +++ b/Sandboxie/core/svc/GuiServer.cpp @@ -1,5 +1,6 @@ /* * Copyright 2004-2020 Sandboxie Holdings, LLC + * Copyright 2020 David Xanatos, xanasoft.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -32,6 +33,7 @@ #include #include #include +#include "misc.h" #define PATTERN XPATTERN extern "C" { @@ -87,9 +89,14 @@ GuiServer::GuiServer() m_ParentPid = 0; m_SessionId = 0; - OSVERSIONINFO osvi = { 0 }; - osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - GetVersionEx(&osvi); + OSVERSIONINFOW osvi = { 0 }; + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOW); + /*NTSTATUS(WINAPI *RtlGetVersion)(LPOSVERSIONINFOW); + *(FARPROC*)&RtlGetVersion = GetProcAddress(_Ntdll, "RtlGetVersion"); + if (RtlGetVersion != NULL) + RtlGetVersion(&osvi); + else*/ + GetVersionExW(&osvi); // since windows 10 this one is lying m_nOSVersion = osvi.dwMajorVersion * 10 + osvi.dwMinorVersion; } @@ -999,6 +1006,16 @@ HANDLE GuiServer::GetJobObjectForAssign(const WCHAR *boxname) | JOB_OBJECT_UILIMIT_READCLIPBOARD; BOOL ok = FALSE; // set TRUE to skip UIRestrictions + + // OpenToken BEGIN + if ((SbieApi_QueryConfBool(boxname, L"OpenToken", FALSE) || SbieApi_QueryConfBool(boxname, L"UnrestrictedToken", FALSE))) + ok = TRUE; + // OpenToken END + // OriginalToken BEGIN + if (SbieApi_QueryConfBool(boxname, L"OriginalToken", FALSE)) + ok = TRUE; + // OriginalToken END + if (! ok) { ok = SetInformationJobObject( hJobObject, JobObjectBasicUIRestrictions, diff --git a/Sandboxie/install/SandboxieVS.nsi b/Sandboxie/install/SandboxieVS.nsi index 7d178c8674..8838cefaaf 100644 --- a/Sandboxie/install/SandboxieVS.nsi +++ b/Sandboxie/install/SandboxieVS.nsi @@ -38,6 +38,7 @@ SetCompressor /SOLID /FINAL lzma !define BIN_ROOT_BASE "${SBIE_INSTALLER_PATH}" !define SBIEDRV_SYS4 "${SBIEDRV_SYS}.rc4" +!define SBIEDRV_SYSX "${SBIEDRV_SYS}.w10" !define OUTFILE_BOTH "${PRODUCT_NAME}Install.exe" !define NAME_Win32 "${PRODUCT_FULL_NAME} ${VERSION} (32-bit)" @@ -431,7 +432,7 @@ InstDir_Check_Suffix: Push -12 Pop $2 StrCpy $1 $0 "" $2 - StrCmp $1 "\${SBIEDRV_SYS4}" InstDir_Suffix_Good + StrCmp $1 "\${SBIEDRV_SYS}" InstDir_Suffix_Good Goto InstDir_ProgramFiles @@ -466,7 +467,7 @@ InstDir_Done: StrCmp "$EXEDIR" "$WINDIR\Installer\" InstType_Remove StrCmp "$EXEDIR\" "$WINDIR\Installer" InstType_Remove - IfFileExists $INSTDIR\${SBIEDRV_SYS4} InstType_Upgrade + IfFileExists $INSTDIR\${SBIEDRV_SYS} InstType_Upgrade IfFileExists $INSTDIR\${SBIESVC_EXE} InstType_Upgrade IfFileExists $INSTDIR\${SBIEDLL_DLL} InstType_Upgrade @@ -971,6 +972,9 @@ WriteLoop: File /oname=${SBIEMSG_DLL} "${BIN_ROOT}\SbieMsg.dll" File /oname=${SBIEDRV_SYS4} "${BIN_ROOT}\SbieDrv.sys.rc4" + File /oname=${SBIEDRV_SYSX} "${BIN_ROOT}\SbieDrv.sys.w10" + + File /oname=KmdUtil.exe "${BIN_ROOT}\KmdUtil.Exe" File /oname=SboxHostDll.dll "${BIN_ROOT}\SboxHostDll.dll" @@ -1075,7 +1079,13 @@ Function DeleteProgramFiles Delete "$INSTDIR\${SBIEMSG_DLL}" + Delete "$INSTDIR\${SBIEDRV_SYS}" Delete "$INSTDIR\${SBIEDRV_SYS4}" + Delete "$INSTDIR\${SBIEDRV_SYSX}" + + Delete "$INSTDIR\KmdUtil.exe" + + Delete "$INSTDIR\boxHostDll.dll" Delete "$INSTDIR\${SANDBOXIE}WUAU.exe" Delete "$INSTDIR\${SANDBOXIE}EventSys.exe" @@ -1425,7 +1435,7 @@ Driver_Silent: ; For Install and Upgrade, install the driver ; - StrCpy $0 'install ${SBIEDRV} "$INSTDIR\${SBIEDRV_SYS4}" type=kernel start=demand "msgfile=$INSTDIR\${SBIEMSG_DLL}" altitude=${FILTER_ALTITUDE}' + StrCpy $0 'install ${SBIEDRV} "$INSTDIR\${SBIEDRV_SYS}" type=kernel start=demand "msgfile=$INSTDIR\${SBIEMSG_DLL}" altitude=${FILTER_ALTITUDE}' Push $0 Call KmdUtil diff --git a/Sandboxie/install/kmdutil/kmdutil.c b/Sandboxie/install/kmdutil/kmdutil.c index e9971259a8..f1a4d870a5 100644 --- a/Sandboxie/install/kmdutil/kmdutil.c +++ b/Sandboxie/install/kmdutil/kmdutil.c @@ -42,7 +42,8 @@ typedef enum _COMMAND { CMD_START, CMD_STOP, CMD_SCANDLL, - CMD_SCANDLL_SILENT + CMD_SCANDLL_SILENT, + CMD_RC4 } COMMAND; typedef enum _OPTIONS { @@ -219,7 +220,11 @@ BOOL Parse_Command_Line( *Command = CMD_STOP; num_args_needed = 1; - } else { + } else if (_wcsicmp(args[1], L"rc4") == 0) { + *Command = CMD_RC4; + num_args_needed = 1; + + } else { *Command = CMD_ERROR; MessageBox(NULL, L"Invalid command", L"KmdUtil", MB_ICONEXCLAMATION | MB_OK); @@ -238,6 +243,11 @@ BOOL Parse_Command_Line( if (num_args_needed >= 2) *Driver_Path = args[3]; + if (*Command == CMD_RC4) { + *Driver_Path = *Driver_Name; + *Driver_Name = NULL; + } + *Options = OPT_NONE; next_arg = num_args_needed + 2; while (next_arg < num_args_given) { @@ -716,41 +726,46 @@ int __stdcall WinMain( &Options)) return EXIT_FAILURE; - if (Driver_Path) + if (Command == CMD_RC4) { int path_len = wcslen(Driver_Path); - if (path_len > 8 && wcscmp(Driver_Path + path_len - 8, L".sys.rc4") == 0) - { - PWSTR Driver_Path_tmp = Driver_Path; // strip \??\ if present - if (Driver_Path_tmp[0] == L'\\' && Driver_Path_tmp[1] == L'?' && Driver_Path_tmp[2] == L'?' && Driver_Path_tmp[3] == L'\\') - Driver_Path_tmp += 4; + BOOLEAN has_rc4 = path_len > 8 && wcscmp(Driver_Path + path_len - 8, L".sys.rc4") == 0; - FILE* inFile = _wfopen(Driver_Path_tmp, L"rb"); - if (inFile) - { + PWSTR Driver_Path_tmp = Driver_Path; // strip \??\ if present + if (Driver_Path_tmp[0] == L'\\' && Driver_Path_tmp[1] == L'?' && Driver_Path_tmp[2] == L'?' && Driver_Path_tmp[3] == L'\\') + Driver_Path_tmp += 4; + + FILE* inFile = _wfopen(Driver_Path_tmp, L"rb"); + if (inFile) + { + if (has_rc4) Driver_Path_tmp[path_len - 4] = L'\0'; // strip .rc4 - FILE* outFile = _wfopen(Driver_Path_tmp, L"wb"); - if (outFile) - { - fseek(inFile, 0, SEEK_END); - DWORD fileSize = ftell(inFile); - fseek(inFile, 0, SEEK_SET); - - void* buffer = HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, fileSize); - fread(buffer, 1, fileSize, inFile); + else + wcscat(Driver_Path_tmp, L".rc4"); // add .rc4 + FILE* outFile = _wfopen(Driver_Path_tmp, L"wb"); + if (outFile) + { + fseek(inFile, 0, SEEK_END); + DWORD fileSize = ftell(inFile); + fseek(inFile, 0, SEEK_SET); + + void* buffer = HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, fileSize); + fread(buffer, 1, fileSize, inFile); - char key[] = "default_key"; - rc4_sbox_t sbox; - rc4_init(&sbox, key, strlen(key)); - rc4_transform(&sbox, buffer, fileSize); + char key[] = "default_key"; + rc4_sbox_t sbox; + rc4_init(&sbox, key, strlen(key)); + rc4_transform(&sbox, buffer, fileSize); - fwrite(buffer, 1, fileSize, outFile); + if(fwrite(buffer, 1, fileSize, outFile) == fileSize) + ok = TRUE; - fclose(outFile); - } - fclose(inFile); + fclose(outFile); } + fclose(inFile); } + + goto exit; } ScMgr = OpenSCManager( @@ -795,6 +810,7 @@ int __stdcall WinMain( if (Command == CMD_STOP) ok = Kmd_Stop_Service(Driver_Name); +exit: if (! ok) return EXIT_FAILURE; diff --git a/Sandboxie/msgs/Sbie-English-1033.txt b/Sandboxie/msgs/Sbie-English-1033.txt index 64a85f2f9a6d43c70ba9d64810f2f6448dc7fac7..bc061b1635eca3057e452e6a1e797037ca7cafd7 100644 GIT binary patch delta 47 zcmbR6g7eM`&JAwMCtp~~GI>&t7;i8`DMJAe<}(yAluTB9sXA#TOS9MV?Ow|nkDUPk D+M^R$ delta 22 gcmV+x0O|kS=n0VM39v?|lMtu`gO;bamZt&6%!Dxuf&c&j diff --git a/SandboxiePlus/MiscHelpers/Common/CheckableMessageBox.cpp b/SandboxiePlus/MiscHelpers/Common/CheckableMessageBox.cpp index 36ea785e13..7f457e5570 100644 --- a/SandboxiePlus/MiscHelpers/Common/CheckableMessageBox.cpp +++ b/SandboxiePlus/MiscHelpers/Common/CheckableMessageBox.cpp @@ -138,6 +138,18 @@ void CCheckableMessageBox::setIconPixmap(const QPixmap &p) d->pixmapLabel->setVisible(!p.isNull()); } +Qt::TextFormat CCheckableMessageBox::textFormat() const +{ + return d->messageLabel->textFormat(); +} + +void CCheckableMessageBox::setTextFormat(Qt::TextFormat format) +{ + d->messageLabel->setTextFormat(format); + d->messageLabel->setWordWrap(format == Qt::RichText + || (format == Qt::AutoText && Qt::mightBeRichText(d->messageLabel->text()))); +} + bool CCheckableMessageBox::isChecked() const { return d->checkBox->isChecked(); diff --git a/SandboxiePlus/MiscHelpers/Common/CheckableMessageBox.h b/SandboxiePlus/MiscHelpers/Common/CheckableMessageBox.h index 56026b8f3d..50cf975844 100644 --- a/SandboxiePlus/MiscHelpers/Common/CheckableMessageBox.h +++ b/SandboxiePlus/MiscHelpers/Common/CheckableMessageBox.h @@ -12,6 +12,7 @@ class MISCHELPERS_EXPORT CCheckableMessageBox : public QDialog { Q_OBJECT Q_PROPERTY(QString text READ text WRITE setText) + Q_PROPERTY(Qt::TextFormat textFormat READ textFormat WRITE setTextFormat) Q_PROPERTY(QPixmap iconPixmap READ iconPixmap WRITE setIconPixmap) Q_PROPERTY(bool isChecked READ isChecked WRITE setChecked) Q_PROPERTY(QString checkBoxText READ checkBoxText WRITE setCheckBoxText) @@ -35,6 +36,9 @@ class MISCHELPERS_EXPORT CCheckableMessageBox : public QDialog QString text() const; void setText(const QString &); + Qt::TextFormat textFormat() const; + void setTextFormat(Qt::TextFormat format); + bool isChecked() const; void setChecked(bool s); diff --git a/SandboxiePlus/MiscHelpers/Common/NetworkAccessManager.cpp b/SandboxiePlus/MiscHelpers/Common/NetworkAccessManager.cpp new file mode 100644 index 0000000000..64fb68dc30 --- /dev/null +++ b/SandboxiePlus/MiscHelpers/Common/NetworkAccessManager.cpp @@ -0,0 +1,76 @@ +#include "stdafx.h" +#include "NetworkAccessManager.h" +#include "Common.h" + +CNetworkAccessManager::CNetworkAccessManager(int TimeOut, QObject* parent) +:QNetworkAccessManager(parent) +{ + m_TimeOut = TimeOut; + connect(this, SIGNAL(finished(QNetworkReply*)), this, SLOT(finishedRequest(QNetworkReply*))); +#ifndef QT_NO_OPENSSL + connect(this, SIGNAL(sslErrors(QNetworkReply*, const QList&)), this, SLOT(sslErrors(QNetworkReply*, const QList&))); +#endif + + m_uTimerID = startTimer(1000); +} + +CNetworkAccessManager::~CNetworkAccessManager() +{ + killTimer(m_uTimerID); +} + +void CNetworkAccessManager::timerEvent(QTimerEvent* pEvent) +{ + if (pEvent->timerId() != m_uTimerID) + return; + + foreach(QNetworkReply *pReply, m_Requests.keys()) + { + if(m_Requests[pReply] < GetCurTick()) + pReply->abort(); + } +} + +void CNetworkAccessManager::SetTimeOut(QNetworkReply *pReply) +{ + m_Requests[pReply] = GetCurTick() + m_TimeOut; +} + +void CNetworkAccessManager::StopTimeOut(QNetworkReply *pReply) +{ + m_Requests.remove(pReply); +} + +void CNetworkAccessManager::Abort(QNetworkReply *pReply) +{ + pReply->abort(); + StopTimeOut(pReply); + pReply->deleteLater(); +} + +QNetworkReply* CNetworkAccessManager::createRequest ( Operation op, const QNetworkRequest & req, QIODevice * outgoingData ) +{ + QNetworkReply* pReply = QNetworkAccessManager::createRequest(op, req, outgoingData); + connect(pReply, SIGNAL(downloadProgress (qint64, qint64)), this, SLOT(OnData(qint64, qint64))); + connect(pReply, SIGNAL(uploadProgress (qint64, qint64)), this, SLOT(OnData(qint64, qint64))); + SetTimeOut(pReply); + return pReply; +} + +void CNetworkAccessManager::finishedRequest(QNetworkReply *pReply) +{ + StopTimeOut(pReply); +} + +void CNetworkAccessManager::OnData(qint64 bytesSent, qint64 bytesTotal) +{ + // Reset TimeOut, as long as data are being transferred its not a timeout + SetTimeOut((QNetworkReply*)sender()); +} + +#ifndef QT_NO_OPENSSL +void CNetworkAccessManager::sslErrors(QNetworkReply *pReply, const QList &error) +{ + //pReply->ignoreSslErrors(); +} +#endif diff --git a/SandboxiePlus/MiscHelpers/Common/NetworkAccessManager.h b/SandboxiePlus/MiscHelpers/Common/NetworkAccessManager.h new file mode 100644 index 0000000000..a9a930aba7 --- /dev/null +++ b/SandboxiePlus/MiscHelpers/Common/NetworkAccessManager.h @@ -0,0 +1,34 @@ +#pragma once + +#include "../mischelpers_global.h" + +#include + +class MISCHELPERS_EXPORT CNetworkAccessManager : public QNetworkAccessManager +{ + Q_OBJECT +public: + CNetworkAccessManager(int TimeOut, QObject* parent = NULL); + ~CNetworkAccessManager(); + + void Abort(QNetworkReply* pReply); + +private slots: + void finishedRequest(QNetworkReply *pReply); + void OnData(qint64 bytesSent, qint64 bytesTotal); +#ifndef QT_NO_OPENSSL + void sslErrors(QNetworkReply *pReply, const QList &error); +#endif + +protected: + void timerEvent(QTimerEvent* pEvent); + int m_uTimerID; + + QNetworkReply* createRequest ( Operation op, const QNetworkRequest & req, QIODevice * outgoingData = 0 ); + + void SetTimeOut(QNetworkReply *pReply); + void StopTimeOut(QNetworkReply *pReply); + + int m_TimeOut; + QMap m_Requests; +}; diff --git a/SandboxiePlus/MiscHelpers/Common/ProgressDialog.h b/SandboxiePlus/MiscHelpers/Common/ProgressDialog.h index 24a251b3e4..269fce480e 100644 --- a/SandboxiePlus/MiscHelpers/Common/ProgressDialog.h +++ b/SandboxiePlus/MiscHelpers/Common/ProgressDialog.h @@ -57,12 +57,12 @@ public slots: if (Progress == -1) { if (m_pProgressBar->maximum() != 0) - m_pProgressBar->setMinimum(0); + m_pProgressBar->setMaximum(0); } else { if (m_pProgressBar->maximum() != 100) - m_pProgressBar->setMinimum(100); + m_pProgressBar->setMaximum(100); m_pProgressBar->setValue(Progress); } diff --git a/SandboxiePlus/MiscHelpers/MiscHelpers.vcxproj b/SandboxiePlus/MiscHelpers/MiscHelpers.vcxproj index d09fc27cfb..91cee42c8a 100644 --- a/SandboxiePlus/MiscHelpers/MiscHelpers.vcxproj +++ b/SandboxiePlus/MiscHelpers/MiscHelpers.vcxproj @@ -191,6 +191,7 @@ + @@ -233,6 +234,7 @@ + diff --git a/SandboxiePlus/MiscHelpers/MiscHelpers.vcxproj.filters b/SandboxiePlus/MiscHelpers/MiscHelpers.vcxproj.filters index e5ea6c3b7c..7dd76836f1 100644 --- a/SandboxiePlus/MiscHelpers/MiscHelpers.vcxproj.filters +++ b/SandboxiePlus/MiscHelpers/MiscHelpers.vcxproj.filters @@ -90,6 +90,9 @@ Common + + Common + @@ -186,5 +189,8 @@ Common + + Common + \ No newline at end of file diff --git a/SandboxiePlus/QSbieAPI/Sandboxie/SandBox.cpp b/SandboxiePlus/QSbieAPI/Sandboxie/SandBox.cpp index 94072385f9..c13e171f32 100644 --- a/SandboxiePlus/QSbieAPI/Sandboxie/SandBox.cpp +++ b/SandboxiePlus/QSbieAPI/Sandboxie/SandBox.cpp @@ -181,7 +181,7 @@ QList CSandBox::GetSnapshots(QString* pCurrent) const BoxSnapshot.NameStr = ini.value(Snapshot + "/Name").toString(); BoxSnapshot.InfoStr = ini.value(Snapshot + "/Description").toString(); - BoxSnapshot.SnapDate = ini.value(Snapshot + "/SnapshotDate").toDateTime(); + BoxSnapshot.SnapDate = QDateTime::fromTime_t(ini.value(Snapshot + "/SnapshotDate").toULongLong()); Snapshots.append(BoxSnapshot); } @@ -227,7 +227,7 @@ SB_PROGRESS CSandBox::TakeSnapshot(const QString& Name) return SB_ERR(tr("Failed to copy RegHive to snapshot")); ini.setValue("Snapshot_" + ID + "/Name", Name); - ini.setValue("Snapshot_" + ID + "/SnapshotDate", QDateTime::currentDateTime()); + ini.setValue("Snapshot_" + ID + "/SnapshotDate", QDateTime::currentDateTime().toTime_t()); QString Current = ini.value("Current/Snapshot").toString(); if(!Current.isEmpty()) ini.setValue("Snapshot_" + ID + "/Parent", Current); diff --git a/SandboxiePlus/QSbieAPI/SbieUtils.cpp b/SandboxiePlus/QSbieAPI/SbieUtils.cpp index b42fe93e40..daf83c2ce5 100644 --- a/SandboxiePlus/QSbieAPI/SbieUtils.cpp +++ b/SandboxiePlus/QSbieAPI/SbieUtils.cpp @@ -72,10 +72,11 @@ SB_STATUS CSbieUtils::Start(EComponent Component) void CSbieUtils::Start(EComponent Component, QStringList& Ops) { - if ((Component & eDriver) != 0 && GetServiceStatus(SBIEDRV) != SERVICE_RUNNING) - Ops.append(QString::fromWCharArray(L"kmdutil.exe|start|" SBIEDRV)); + // Note: Service aways starts the driver if ((Component & eService) != 0 && GetServiceStatus(SBIESVC) != SERVICE_RUNNING) Ops.append(QString::fromWCharArray(L"kmdutil.exe|start|" SBIESVC)); + else if ((Component & eDriver) != 0 && GetServiceStatus(SBIEDRV) != SERVICE_RUNNING) + Ops.append(QString::fromWCharArray(L"kmdutil.exe|start|" SBIEDRV)); } SB_STATUS CSbieUtils::Stop(EComponent Component) @@ -282,12 +283,12 @@ void CSbieUtils::RemoveContextMenu() ////////////////////////////////////////////////////////////////////////////// // Shortcuts -bool CreateShortcut(CSbieAPI* pApi, const QString &LinkPath, const QString &LinkName, const QString &boxname, const QString &arguments, const QString &iconPath, int iconIndex, const QString &workdir, BOOL run_elevated = 0) +bool CSbieUtils::CreateShortcut(CSbieAPI* pApi, const QString &LinkPath, const QString &LinkName, const QString &boxname, const QString &arguments, const QString &iconPath, int iconIndex, const QString &workdir, bool bRunElevated) { QString StartExe = pApi->GetStartPath(); QString StartArgs; - if (run_elevated) + if (bRunElevated) StartArgs += "/elevated "; StartArgs += "/box:" + boxname; if (!arguments.isEmpty()) @@ -329,35 +330,7 @@ bool CreateShortcut(CSbieAPI* pApi, const QString &LinkPath, const QString &Link return (SUCCEEDED(hr)); } -void CreateDesktopShortcut(CSbieAPI* pApi, const QString &BoxName, const QString &LinkPath, const QString &IconPath, quint32 IconIndex, const QString &WorkDir) -{ - WCHAR path[512]; - HRESULT hr = SHGetFolderPathW(NULL, CSIDL_DESKTOPDIRECTORY, NULL, SHGFP_TYPE_CURRENT, path); - if (hr != 0 || *path == L'\0') - return; - - QString LinkName; - int pos = LinkPath.lastIndexOf(L'\\'); - if (pos == -1) - return; - if (pos == 2 && LinkPath.length() == 3) - LinkName = QObject::tr("Drive %1").arg(LinkPath.left(1)); - else { - LinkName = LinkPath.mid(pos + 1); - pos = LinkName.indexOf(QRegExp("[" + QRegExp::escape("\":;,*?.") + "]")); - if (pos != -1) - LinkName = LinkName.left(pos); - } - - QString Path = QString::fromWCharArray(path); - if (Path.right(1) != "\\") - Path.append("\\"); - Path += "[" + BoxName + "] " + LinkName; - - CreateShortcut(pApi, Path, LinkName, BoxName, LinkPath , IconPath, IconIndex, WorkDir); -} - -bool GetStartMenuShortcut(CSbieAPI* pApi, QString &BoxName, QString &LinkPath, QString &IconPath, quint32& IconIndex, QString &WorkDir) +bool CSbieUtils::GetStartMenuShortcut(CSbieAPI* pApi, QString &BoxName, QString &LinkPath, QString &IconPath, quint32& IconIndex, QString &WorkDir) { WCHAR MapName[128]; wsprintf(MapName, SANDBOXIE L"_StartMenu_WorkArea_%08X_%08X", GetCurrentProcessId(), GetTickCount()); @@ -409,12 +382,4 @@ bool GetStartMenuShortcut(CSbieAPI* pApi, QString &BoxName, QString &LinkPath, Q if (BoxName.isEmpty() || LinkPath.isEmpty()) return false; return true; -} - -void CSbieUtils::CreateDesktopShortcut(const QString& BoxName, CSbieAPI* pApi) -{ - QString LinkPath, IconPath, WorkDir; - quint32 IconIndex; - if (::GetStartMenuShortcut(pApi, QString(BoxName), LinkPath, IconPath, IconIndex, WorkDir)) - ::CreateDesktopShortcut(pApi, BoxName, LinkPath, IconPath, IconIndex, WorkDir); -} +} \ No newline at end of file diff --git a/SandboxiePlus/QSbieAPI/SbieUtils.h b/SandboxiePlus/QSbieAPI/SbieUtils.h index da8cd96667..3490dcf717 100644 --- a/SandboxiePlus/QSbieAPI/SbieUtils.h +++ b/SandboxiePlus/QSbieAPI/SbieUtils.h @@ -30,7 +30,8 @@ class QSBIEAPI_EXPORT CSbieUtils static void AddContextMenu(const QString& StartPath); static void RemoveContextMenu(); - static void CreateDesktopShortcut(const QString& BoxName, class CSbieAPI* pApi); + static bool CreateShortcut(class CSbieAPI* pApi, const QString &LinkPath, const QString &LinkName, const QString &boxname, const QString &arguments, const QString &iconPath, int iconIndex, const QString &workdir, bool bRunElevated = false); + static bool GetStartMenuShortcut(class CSbieAPI* pApi, QString &BoxName, QString &LinkPath, QString &IconPath, quint32& IconIndex, QString &WorkDir); private: static SB_STATUS ElevateOps(const QStringList& Ops); diff --git a/SandboxiePlus/SandMan/Forms/OptionsWindow.ui b/SandboxiePlus/SandMan/Forms/OptionsWindow.ui index cce74d66ed..52613f06bc 100644 --- a/SandboxiePlus/SandMan/Forms/OptionsWindow.ui +++ b/SandboxiePlus/SandMan/Forms/OptionsWindow.ui @@ -45,7 +45,7 @@ QTabWidget::West - 6 + 0 @@ -1300,20 +1300,7 @@ Note: Forced Programs and Force Folders settings for a sandbox do not apply to - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + Qt::Vertical @@ -1326,6 +1313,33 @@ Note: Forced Programs and Force Folders settings for a sandbox do not apply to + + + + Pipe Trace + + + + + + + Log all access events as seen by the driver to the resource access log. + +This options set the event amsk to "*" - All access events +You can customize the logging using the ini by specifying +"A" - Allowed accesses +"D" - Denidec accesses +"I" - Ignore access requests +instead of "*". + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + @@ -1346,13 +1360,6 @@ Note: Forced Programs and Force Folders settings for a sandbox do not apply to - - - - IPC Trace - - - @@ -1360,13 +1367,6 @@ Note: Forced Programs and Force Folders settings for a sandbox do not apply to - - - - Pipe Trace - - - @@ -1374,23 +1374,30 @@ Note: Forced Programs and Force Folders settings for a sandbox do not apply to - - - - Log all access events as seen by the driver to the resource access log. - -This options set the event amsk to "*" - All access events -You can customize the logging using the ini by specifying -"A" - Allowed accesses -"D" - Denidec accesses -"I" - Ignore access requests -instead of "*". + + + + Qt::Horizontal - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + 40 + 20 + - - true + + + + + + IPC Trace + + + + + + + Log Debug Output to the Trace Log @@ -1419,8 +1426,8 @@ instead of "*". 0 0 - 530 - 251 + 98 + 28 diff --git a/SandboxiePlus/SandMan/Forms/SettingsWindow.ui b/SandboxiePlus/SandMan/Forms/SettingsWindow.ui index 373fdb7cb1..df140bfbdc 100644 --- a/SandboxiePlus/SandMan/Forms/SettingsWindow.ui +++ b/SandboxiePlus/SandMan/Forms/SettingsWindow.ui @@ -54,75 +54,65 @@ - - + + - Use Dark Theme + Show Notifications for relevant log Messages + + + false - + Show Sys-Tray - - + + - Qt::Horizontal + Qt::Vertical - 40 - 20 + 20 + 40 - - - - - 75 - true - - + + - Tray options + Use Dark Theme - - - - Restart required (!) - - - Qt::AlignCenter + + + + Qt::Horizontal - - - - - - Start with Windows + + + 40 + 20 + - + - - + + - On main window close: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Add 'Run Sandboxed' to the explorer context menu - - + + Qt::Horizontal @@ -134,46 +124,63 @@ - - - - Qt::Vertical + + + + On main window close: - - - 20 - 40 - + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + - - + + + + Start with Windows + + + + + + + Restart required (!) + + + Qt::AlignCenter + + - + Watch Sandboxie.ini for changes - - - - Show Notifications for relevant log Messages + + + + + 75 + true + - - false + + Tray options + + + - + - Add 'Run Sandboxed' to the explorer context menu + Check periodicaly for updates of Sandboxie-Plus diff --git a/SandboxiePlus/SandMan/Models/SbieModel.cpp b/SandboxiePlus/SandMan/Models/SbieModel.cpp index a7acd1e398..4cba445071 100644 --- a/SandboxiePlus/SandMan/Models/SbieModel.cpp +++ b/SandboxiePlus/SandMan/Models/SbieModel.cpp @@ -66,12 +66,65 @@ bool CSbieModel::TestProcPath(const QList& Path, const QString& BoxNam return Path.size() == Index; } -QList CSbieModel::Sync(const QMap& BoxList) +QString CSbieModel::FindParent(const QVariant& Name, const QMap& Groups) +{ + for(auto I = Groups.begin(); I != Groups.end(); ++I) + { + if (I.value().contains(Name.toString(), Qt::CaseInsensitive)) + return I.key(); + } + return QString(); +} + +QList CSbieModel::MakeBoxPath(const QVariant& Name, const QMap& Groups) +{ + QString ParentID = FindParent(Name, Groups); + + QList Path; + if (!ParentID.isEmpty() && ParentID != Name) + { + Path = MakeBoxPath(ParentID, Groups); + Path.append(ParentID); + } + return Path; +} + +QList CSbieModel::Sync(const QMap& BoxList, const QMap& Groups) { QList Added; QMap, QList > New; QHash Old = m_Map; + foreach(const QString& Group, Groups.keys()) + { + if (Group.isEmpty()) + continue; + QVariant ID = Group; + + QHash::iterator I = Old.find(ID); + SSandBoxNode* pNode = I != Old.end() ? static_cast(I.value()) : NULL; + if (!pNode) + { + pNode = static_cast(MkNode(ID)); + pNode->Values.resize(columnCount()); + if (m_bTree) + pNode->Path = MakeBoxPath(ID, Groups); + pNode->pBox = NULL; + New[pNode->Path].append(pNode); + Added.append(ID); + + pNode->Icon = m_BoxIcons[eYelow].first; + pNode->IsBold = true; + + pNode->Values[eName].Raw = Group; + pNode->Values[eStatus].Raw = tr("Box Groupe"); + } + else + { + I.value() = NULL; + } + } + foreach (const CSandBoxPtr& pBox, BoxList) { QVariant ID = pBox->GetName(); @@ -84,6 +137,8 @@ QList CSbieModel::Sync(const QMap& BoxList) { pNode = static_cast(MkNode(ID)); pNode->Values.resize(columnCount()); + if (m_bTree) + pNode->Path = MakeBoxPath(ID, Groups); pNode->pBox = pBox; New[pNode->Path].append(pNode); Added.append(ID); @@ -102,7 +157,7 @@ QList CSbieModel::Sync(const QMap& BoxList) QMap ProcessList = pBox->GetProcessList(); - bool HasActive = Sync(pBox, ProcessList, New, Old, Added); + bool HasActive = Sync(pBox, pNode->Path, ProcessList, New, Old, Added); int inUse = (HasActive ? 1 : 0); int boxType = eYelow; if(pBoxEx->HasLogApi()) @@ -166,7 +221,7 @@ QList CSbieModel::Sync(const QMap& BoxList) return Added; } -bool CSbieModel::Sync(const CSandBoxPtr& pBox, const QMap& ProcessList, QMap, QList >& New, QHash& Old, QList& Added) +bool CSbieModel::Sync(const CSandBoxPtr& pBox, const QList& Path, const QMap& ProcessList, QMap, QList >& New, QHash& Old, QList& Added) { QString BoxName = pBox->GetName(); @@ -180,12 +235,12 @@ bool CSbieModel::Sync(const CSandBoxPtr& pBox, const QMap::iterator I = Old.find(ID); SSandBoxNode* pNode = I != Old.end() ? static_cast(I.value()) : NULL; - if (!pNode || (m_bTree ? !TestProcPath(pNode->Path, BoxName, pProcess, ProcessList) : !pNode->Path.isEmpty())) + if (!pNode || (m_bTree ? !TestProcPath(pNode->Path.mid(Path.length()), BoxName, pProcess, ProcessList) : !pNode->Path.isEmpty())) // todo: improve that { pNode = static_cast(MkNode(ID)); pNode->Values.resize(columnCount()); - if(m_bTree) - pNode->Path = MakeProcPath(BoxName, pProcess, ProcessList); + if (m_bTree) + pNode->Path = Path + MakeProcPath(BoxName, pProcess, ProcessList); pNode->pBox = pBox; pNode->pProcess = pProcess; New[pNode->Path].append(pNode); @@ -295,6 +350,32 @@ CBoxedProcessPtr CSbieModel::GetProcess(const QModelIndex &index) const return pNode->pProcess; } +QVariant CSbieModel::GetID(const QModelIndex &index) const +{ + if (!index.isValid()) + return QVariant(); + + SSandBoxNode* pNode = static_cast(index.internalPointer()); + ASSERT(pNode); + + return pNode->ID; +} + +CSbieModel::ETypes CSbieModel::GetType(const QModelIndex &index) const +{ + if (!index.isValid()) + return eNone; + + SSandBoxNode* pNode = static_cast(index.internalPointer()); + ASSERT(pNode); + + if (pNode->pProcess) + return eProcess; + if (pNode->pBox) + return eBox; + return eGroup; +} + int CSbieModel::columnCount(const QModelIndex &parent) const { return eCount; diff --git a/SandboxiePlus/SandMan/Models/SbieModel.h b/SandboxiePlus/SandMan/Models/SbieModel.h index 92ed21625f..9605484b4a 100644 --- a/SandboxiePlus/SandMan/Models/SbieModel.h +++ b/SandboxiePlus/SandMan/Models/SbieModel.h @@ -12,10 +12,19 @@ class CSbieModel : public CTreeItemModel CSbieModel(QObject *parent = 0); ~CSbieModel(); - QList Sync(const QMap& BoxList); + QList Sync(const QMap& BoxList, const QMap& Groups = QMap()); CSandBoxPtr GetSandBox(const QModelIndex &index) const; CBoxedProcessPtr GetProcess(const QModelIndex &index) const; + QVariant GetID(const QModelIndex &index) const; + + enum ETypes + { + eNone = 0, + eGroup, + eBox, + eProcess + } GetType(const QModelIndex &index) const; int columnCount(const QModelIndex &parent = QModelIndex()) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; @@ -33,7 +42,7 @@ class CSbieModel : public CTreeItemModel }; protected: - bool Sync(const CSandBoxPtr& pBox, const QMap& ProcessList, QMap, QList >& New, QHash& Old, QList& Added); + bool Sync(const CSandBoxPtr& pBox, const QList& Path, const QMap& ProcessList, QMap, QList >& New, QHash& Old, QList& Added); struct SSandBoxNode: STreeNode { @@ -52,6 +61,9 @@ class CSbieModel : public CTreeItemModel QList MakeProcPath(const CBoxedProcessPtr& pProcess, const QMap& ProcessList); bool TestProcPath(const QList& Path, const QString& BoxName, const CBoxedProcessPtr& pProcess, const QMap& ProcessList, int Index = 0); + QString FindParent(const QVariant& Name, const QMap& Groups); + QList MakeBoxPath(const QVariant& Name, const QMap& Groups); + //virtual QVariant GetDefaultIcon() const; private: diff --git a/SandboxiePlus/SandMan/SandMan.cpp b/SandboxiePlus/SandMan/SandMan.cpp index 05cde8fc6c..51981df108 100644 --- a/SandboxiePlus/SandMan/SandMan.cpp +++ b/SandboxiePlus/SandMan/SandMan.cpp @@ -99,6 +99,8 @@ CSandMan::CSandMan(QWidget *parent) connect(theAPI, SIGNAL(StatusChanged()), this, SLOT(OnStatusChanged())); connect(theAPI, SIGNAL(BoxClosed(const QString&)), this, SLOT(OnBoxClosed(const QString&))); + m_RequestManager = NULL; + QString appTitle = tr("Sandboxie-Plus v%1").arg(GetVersion()); this->setWindowTitle(appTitle); @@ -111,15 +113,15 @@ CSandMan::CSandMan(QWidget *parent) m_bConnectPending = false; m_bStopPending = false; + CreateMenus(); + m_pMainWidget = new QWidget(); m_pMainLayout = new QVBoxLayout(m_pMainWidget); m_pMainLayout->setMargin(2); m_pMainLayout->setSpacing(0); this->setCentralWidget(m_pMainWidget); - m_pToolBar = new QToolBar(); - m_pMainLayout->insertWidget(0, m_pToolBar); - + CreateToolBar(); m_pLogSplitter = new QSplitter(); m_pLogSplitter->setOrientation(Qt::Vertical); @@ -181,6 +183,104 @@ CSandMan::CSandMan(QWidget *parent) m_pApiCallLog->setEnabled(false); // + + // Tray + QIcon Icon; + Icon.addFile(":/SandMan.png"); + m_pTrayIcon = new QSystemTrayIcon(Icon, this); + m_pTrayIcon->setToolTip("Sandboxie-Plus"); + connect(m_pTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(OnSysTray(QSystemTrayIcon::ActivationReason))); + m_bIconEmpty = false; + + m_pTrayMenu = new QMenu(); + m_pTrayMenu->addAction(m_pEmptyAll); + m_pTrayMenu->addAction(m_pDisableForce); + m_pTrayMenu->addSeparator(); + m_pTrayMenu->addAction(m_pExit); + + bool bAutoRun = QApplication::arguments().contains("-autorun"); + + m_pTrayIcon->show(); // Note: qt bug; without a first show hide does not work :/ + if(!bAutoRun && !theConf->GetBool("Options/ShowSysTray", true)) + m_pTrayIcon->hide(); + // + + + restoreGeometry(theConf->GetBlob("MainWindow/Window_Geometry")); + //m_pBoxTree->restoreState(theConf->GetBlob("MainWindow/BoxTree_Columns")); + m_pMessageLog->GetView()->header()->restoreState(theConf->GetBlob("MainWindow/LogList_Columns")); + QByteArray Columns = theConf->GetBlob("MainWindow/ResMonList_Columns"); + if (!Columns.isEmpty()) + ((QTreeViewEx*)m_pResourceLog->GetView())->OnResetColumns(); + else + ((QTreeViewEx*)m_pResourceLog->GetView())->restoreState(Columns); + Columns = theConf->GetBlob("MainWindow/ApiLogList_Columns"); + if (!Columns.isEmpty()) + ((QTreeViewEx*)m_pApiCallLog->GetView())->OnResetColumns(); + else + ((QTreeViewEx*)m_pApiCallLog->GetView())->restoreState(Columns); + m_pLogSplitter->restoreState(theConf->GetBlob("MainWindow/Log_Splitter")); + m_pPanelSplitter->restoreState(theConf->GetBlob("MainWindow/Panel_Splitter")); + m_pLogTabs->setCurrentIndex(theConf->GetInt("MainWindow/LogTab", 0)); + + if (theConf->GetBool("Options/NoStatusBar", false)) + statusBar()->hide(); + //else if (theConf->GetBool("Options/NoSizeGrip", false)) + // statusBar()->setSizeGripEnabled(false); + + m_pKeepTerminated->setChecked(theConf->GetBool("Options/KeepTerminated")); + + m_pProgressDialog = new CProgressDialog("", this); + m_pProgressDialog->setWindowModality(Qt::ApplicationModal); + connect(m_pProgressDialog, SIGNAL(Cancel()), this, SLOT(OnCancelAsync())); + + m_pPopUpWindow = new CPopUpWindow(this); + connect(m_pPopUpWindow, SIGNAL(RecoveryRequested(const QString&)), this, SLOT(OpenRecovery(const QString&))); + + if (!bAutoRun) + show(); + + //connect(theAPI, SIGNAL(LogMessage(const QString&, bool)), this, SLOT(OnLogMessage(const QString&, bool))); + connect(theAPI, SIGNAL(LogSbieMessage(quint32, const QStringList&, quint32)), this, SLOT(OnLogSbieMessage(quint32, const QStringList&, quint32))); + connect(theAPI, SIGNAL(NotAuthorized(bool, bool&)), this, SLOT(OnNotAuthorized(bool, bool&)), Qt::DirectConnection); + connect(theAPI, SIGNAL(QueuedRequest(quint32, quint32, quint32, const QVariantMap&)), this, SLOT(OnQueuedRequest(quint32, quint32, quint32, const QVariantMap&)), Qt::QueuedConnection); + connect(theAPI, SIGNAL(FileToRecover(const QString&, const QString&, quint32)), this, SLOT(OnFileToRecover(const QString&, const QString&, quint32)), Qt::QueuedConnection); + connect(theAPI, SIGNAL(ConfigReloaded()), this, SLOT(OnIniReloaded())); + + m_uTimerID = startTimer(250); + + if (CSbieUtils::IsRunning(CSbieUtils::eAll) || theConf->GetBool("Options/StartIfStopped", true)) + { + SB_STATUS Status = ConnectSbie(); + CheckResults(QList() << Status); + } +} + +CSandMan::~CSandMan() +{ + if(m_pEnableMonitoring->isChecked()) + theAPI->EnableMonitor(false); + + killTimer(m_uTimerID); + + m_pTrayIcon->hide(); + + theConf->SetBlob("MainWindow/Window_Geometry", saveGeometry()); + //theConf->SetBlob("MainWindow/BoxTree_Columns", m_pBoxTree->saveState()); + theConf->SetBlob("MainWindow/LogList_Columns", m_pMessageLog->GetView()->header()->saveState()); + theConf->SetBlob("MainWindow/ResMonList_Columns", m_pResourceLog->GetView()->header()->saveState()); + theConf->SetBlob("MainWindow/ApiLogList_Columns", m_pApiCallLog->GetView()->header()->saveState()); + theConf->SetBlob("MainWindow/Log_Splitter", m_pLogSplitter->saveState()); + theConf->SetBlob("MainWindow/Panel_Splitter", m_pPanelSplitter->saveState()); + theConf->SetValue("MainWindow/LogTab", m_pLogTabs->currentIndex()); + + theAPI = NULL; + + theGUI = NULL; +} + +void CSandMan::CreateMenus() +{ connect(menuBar(), SIGNAL(hovered(QAction*)), this, SLOT(OnMenuHover(QAction*))); m_pMenuFile = menuBar()->addMenu(tr("&Sandbox")); @@ -236,12 +336,21 @@ CSandMan::CSandMan(QWidget *parent) m_pMenuHelp = menuBar()->addMenu(tr("&Help")); - m_pSupport = m_pMenuHelp->addAction(tr("Support Sandboxie-Plus on Patreon"), this, SLOT(OnAbout())); + //m_pMenuHelp->addAction(tr("Support Sandboxie-Plus on Patreon"), this, SLOT(OnHelp())); + m_pSupport = m_pMenuHelp->addAction(tr("Support Sandboxie-Plus with a Donation"), this, SLOT(OnHelp())); + m_pForum = m_pMenuHelp->addAction(tr("Visit Support Forum"), this, SLOT(OnHelp())); + m_pManual = m_pMenuHelp->addAction(tr("Online Documentation"), this, SLOT(OnHelp())); + m_pMenuHelp->addSeparator(); + m_pUpdate = m_pMenuHelp->addAction(tr("Check for Updates"), this, SLOT(CheckForUpdates())); m_pMenuHelp->addSeparator(); m_pAboutQt = m_pMenuHelp->addAction(tr("About the Qt Framework"), this, SLOT(OnAbout())); - //m_pMenuHelp->addSeparator(); m_pAbout = m_pMenuHelp->addAction(QIcon(":/SandMan.png"), tr("About Sandboxie-Plus"), this, SLOT(OnAbout())); +} +void CSandMan::CreateToolBar() +{ + m_pToolBar = new QToolBar(); + m_pMainLayout->insertWidget(0, m_pToolBar); m_pToolBar->addAction(m_pMenuSettings); m_pToolBar->addSeparator(); @@ -280,102 +389,9 @@ CSandMan::CSandMan(QWidget *parent) m_pToolBar->addWidget(new QLabel(" ")); QLabel* pSupport = new QLabel("Support Sandboxie-Plus on Patreon"); pSupport->setTextInteractionFlags(Qt::TextBrowserInteraction); - connect(pSupport, SIGNAL(linkActivated(const QString&)), this, SLOT(OnAbout())); + connect(pSupport, SIGNAL(linkActivated(const QString&)), this, SLOT(OnHelp())); m_pToolBar->addWidget(pSupport); m_pToolBar->addWidget(new QLabel(" ")); - - - - QIcon Icon; - Icon.addFile(":/SandMan.png"); - m_pTrayIcon = new QSystemTrayIcon(Icon, this); - m_pTrayIcon->setToolTip("Sandboxie-Plus"); - connect(m_pTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(OnSysTray(QSystemTrayIcon::ActivationReason))); - m_bIconEmpty = false; - - m_pTrayMenu = new QMenu(); - m_pTrayMenu->addAction(m_pEmptyAll); - m_pTrayMenu->addAction(m_pDisableForce); - m_pTrayMenu->addSeparator(); - m_pTrayMenu->addAction(m_pExit); - - bool bAutoRun = QApplication::arguments().contains("-autorun"); - - m_pTrayIcon->show(); // Note: qt bug; without a first show hide does not work :/ - if(!bAutoRun && !theConf->GetBool("Options/ShowSysTray", true)) - m_pTrayIcon->hide(); - - restoreGeometry(theConf->GetBlob("MainWindow/Window_Geometry")); - //m_pBoxTree->restoreState(theConf->GetBlob("MainWindow/BoxTree_Columns")); - m_pMessageLog->GetView()->header()->restoreState(theConf->GetBlob("MainWindow/LogList_Columns")); - QByteArray Columns = theConf->GetBlob("MainWindow/ResMonList_Columns"); - if (!Columns.isEmpty()) - ((QTreeViewEx*)m_pResourceLog->GetView())->OnResetColumns(); - else - ((QTreeViewEx*)m_pResourceLog->GetView())->restoreState(Columns); - Columns = theConf->GetBlob("MainWindow/ApiLogList_Columns"); - if (!Columns.isEmpty()) - ((QTreeViewEx*)m_pApiCallLog->GetView())->OnResetColumns(); - else - ((QTreeViewEx*)m_pApiCallLog->GetView())->restoreState(Columns); - m_pLogSplitter->restoreState(theConf->GetBlob("MainWindow/Log_Splitter")); - m_pPanelSplitter->restoreState(theConf->GetBlob("MainWindow/Panel_Splitter")); - m_pLogTabs->setCurrentIndex(theConf->GetInt("MainWindow/LogTab", 0)); - - if (theConf->GetBool("Options/NoStatusBar", false)) - statusBar()->hide(); - //else if (theConf->GetBool("Options/NoSizeGrip", false)) - // statusBar()->setSizeGripEnabled(false); - - m_pKeepTerminated->setChecked(theConf->GetBool("Options/KeepTerminated")); - - m_pProgressDialog = new CProgressDialog("Maintenance operation progress...", this); - m_pProgressDialog->setWindowModality(Qt::ApplicationModal); - connect(m_pProgressDialog, SIGNAL(Cancel()), this, SLOT(OnCancelAsync())); - - m_pPopUpWindow = new CPopUpWindow(this); - connect(m_pPopUpWindow, SIGNAL(RecoveryRequested(const QString&)), this, SLOT(OpenRecovery(const QString&))); - - if (!bAutoRun) - show(); - - //connect(theAPI, SIGNAL(LogMessage(const QString&, bool)), this, SLOT(OnLogMessage(const QString&, bool))); - connect(theAPI, SIGNAL(LogSbieMessage(quint32, const QStringList&, quint32)), this, SLOT(OnLogSbieMessage(quint32, const QStringList&, quint32))); - connect(theAPI, SIGNAL(NotAuthorized(bool, bool&)), this, SLOT(OnNotAuthorized(bool, bool&)), Qt::DirectConnection); - connect(theAPI, SIGNAL(QueuedRequest(quint32, quint32, quint32, const QVariantMap&)), this, SLOT(OnQueuedRequest(quint32, quint32, quint32, const QVariantMap&)), Qt::QueuedConnection); - connect(theAPI, SIGNAL(FileToRecover(const QString&, const QString&, quint32)), this, SLOT(OnFileToRecover(const QString&, const QString&, quint32)), Qt::QueuedConnection); - connect(theAPI, SIGNAL(ConfigReloaded()), this, SLOT(OnIniReloaded())); - - m_uTimerID = startTimer(250); - - if (CSbieUtils::IsRunning(CSbieUtils::eAll) || theConf->GetBool("Options/StartIfStopped", true)) - { - SB_STATUS Status = ConnectSbie(); - CheckResults(QList() << Status); - } -} - -CSandMan::~CSandMan() -{ - if(m_pEnableMonitoring->isChecked()) - theAPI->EnableMonitor(false); - - killTimer(m_uTimerID); - - m_pTrayIcon->hide(); - - theConf->SetBlob("MainWindow/Window_Geometry", saveGeometry()); - //theConf->SetBlob("MainWindow/BoxTree_Columns", m_pBoxTree->saveState()); - theConf->SetBlob("MainWindow/LogList_Columns", m_pMessageLog->GetView()->header()->saveState()); - theConf->SetBlob("MainWindow/ResMonList_Columns", m_pResourceLog->GetView()->header()->saveState()); - theConf->SetBlob("MainWindow/ApiLogList_Columns", m_pApiCallLog->GetView()->header()->saveState()); - theConf->SetBlob("MainWindow/Log_Splitter", m_pLogSplitter->saveState()); - theConf->SetBlob("MainWindow/Panel_Splitter", m_pPanelSplitter->saveState()); - theConf->SetValue("MainWindow/LogTab", m_pLogTabs->currentIndex()); - - theAPI = NULL; - - theGUI = NULL; } void CSandMan::OnExit() @@ -503,6 +519,35 @@ void CSandMan::timerEvent(QTimerEvent* pEvent) m_pBoxView->Refresh(); OnSelectionChanged(); + + int iCheckUpdates = theConf->GetInt("Options/CheckForUpdates", 2); + if (iCheckUpdates != 0) + { + time_t NextUpdateCheck = theConf->GetUInt64("Options/NextCheckForUpdates", 0); + if (NextUpdateCheck == 0) + theConf->SetValue("Options/NextCheckForUpdates", QDateTime::currentDateTime().addDays(7).toTime_t()); + else if(QDateTime::currentDateTime().toTime_t() >= NextUpdateCheck) + { + if (iCheckUpdates == 2) + { + bool bCheck = false; + iCheckUpdates = CCheckableMessageBox::question(this, "Sandboxie-Plus", tr("Do you want to check if there is a new version of Sandboxie-Plus?") + , tr("Don't show this message again."), &bCheck, QDialogButtonBox::Yes | QDialogButtonBox::No, QDialogButtonBox::Yes, QMessageBox::Information) == QDialogButtonBox::Ok ? 1 : 0; + + if (bCheck) + theConf->SetValue("Options/CheckForUpdates", iCheckUpdates); + } + + if (iCheckUpdates == 0) + theConf->SetValue("Options/NextCheckForUpdates", QDateTime::currentDateTime().addDays(7).toTime_t()); + else + { + theConf->SetValue("Options/NextCheckForUpdates", QDateTime::currentDateTime().addDays(1).toTime_t()); + + CheckForUpdates(false); + } + } + } } void CSandMan::OnBoxClosed(const QString& BoxName) @@ -980,6 +1025,7 @@ void CSandMan::OnReloadIni() void CSandMan::OnIniReloaded() { + m_pBoxView->ReloadGroups(); m_pPopUpWindow->ReloadHiddenMessages(); } @@ -1026,10 +1072,12 @@ void CSandMan::AddAsyncOp(const CSbieProgressPtr& pProgress) m_pAsyncProgress.insert(pProgress.data(), pProgress); connect(pProgress.data(), SIGNAL(Message(const QString&)), this, SLOT(OnAsyncMessage(const QString&))); connect(pProgress.data(), SIGNAL(Finished()), this, SLOT(OnAsyncFinished())); - if (pProgress->IsFinished()) // Note: the operation runs asynchroniusly it may have already finished so we need to test for that - OnAsyncFinished(pProgress.data()); + m_pProgressDialog->OnStatusMessage(""); m_pProgressDialog->show(); + + if (pProgress->IsFinished()) // Note: the operation runs asynchroniusly it may have already finished so we need to test for that + OnAsyncFinished(pProgress.data()); } void CSandMan::OnAsyncFinished() @@ -1139,6 +1187,211 @@ QString CSandMan::GetVersion() return Version; } +void CSandMan::CheckForUpdates(bool bManual) +{ + m_pProgressDialog->OnStatusMessage(tr("Checking for updates...")); + m_pProgressDialog->show(); + + if(m_RequestManager == NULL) + m_RequestManager = new CNetworkAccessManager(30 * 1000, this); + + QUrlQuery Query; + Query.addQueryItem("software", "sandboxie-plus"); + //QString Branche = theConf->GetString("Options/ReleaseBranche"); + //if (!Branche.isEmpty()) + // Query.addQueryItem("branche", Branche); + Query.addQueryItem("version", GetVersion()); + //Query.addQueryItem("version", QString::number(VERSION_MJR) + "." + QString::number(VERSION_MIN) + "." + QString::number(VERSION_REV) + "." + QString::number(VERSION_UPD)); + Query.addQueryItem("system", "windows-" + QSysInfo::kernelVersion() + "-" + QSysInfo::currentCpuArchitecture()); + Query.addQueryItem("language", QString::number(m_LanguageId)); + QString UpdateKey = theAPI->GetGlobalSettings()->GetText("UpdateKey"); // theConf->GetString("Options/UpdateKey"); + if (!UpdateKey.isEmpty()) + Query.addQueryItem("update_key", UpdateKey); + Query.addQueryItem("auto", bManual ? "0" : "1"); + + QUrl Url("https://xanasoft.com/update.php"); + Url.setQuery(Query); + + QNetworkRequest Request = QNetworkRequest(Url); + Request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + //Request.setRawHeader("Accept-Encoding", "gzip"); + QNetworkReply* pReply = m_RequestManager->get(Request); + pReply->setProperty("manual", bManual); + connect(pReply, SIGNAL(finished()), this, SLOT(OnUpdateCheck())); +} + +void CSandMan::OnUpdateCheck() +{ + QNetworkReply* pReply = qobject_cast(sender()); + QByteArray Reply = pReply->readAll(); + bool bManual = pReply->property("manual").toBool(); + pReply->deleteLater(); + + m_pProgressDialog->hide(); + + QVariantMap Data = QJsonDocument::fromJson(Reply).toVariant().toMap(); + if (Data.isEmpty() || Data["error"].toBool()) + { + QString Error = Data.isEmpty() ? tr("server not reachable") : Data["errorMsg"].toString(); + OnLogMessage(tr("Failed to check for updates, error: %1").arg(Error), !bManual); + if (bManual) + QMessageBox::critical(this, "Sandboxie-Plus", tr("Failed to check for updates, error: %1").arg(Error)); + return; + } + + bool bNothing = true; + + QStringList IgnoredUpdates = theConf->GetStringList("Options/IgnoredUpdates"); + + QString UserMsg = Data["userMsg"].toString(); + if (!UserMsg.isEmpty()) + { + QString MsgHash = QCryptographicHash::hash(Data["userMsg"].toByteArray(), QCryptographicHash::Md5).toHex().left(8); + if (!IgnoredUpdates.contains(MsgHash)) + { + CCheckableMessageBox mb(this); + mb.setWindowTitle("Sandboxie-Plus"); + QIcon ico(QLatin1String(":/SandMan.png")); + mb.setIconPixmap(ico.pixmap(64, 64)); + mb.setText(UserMsg); + mb.setCheckBoxText(tr("Don't show this announcement in future.")); + mb.setStandardButtons(QDialogButtonBox::Close); + mb.exec(); + + if (mb.isChecked()) + theConf->SetValue("Options/IgnoredUpdates", IgnoredUpdates << MsgHash); + + bNothing = false; + } + } + + QString Version = Data["version"].toString(); + if (!Version.isEmpty() && Version != GetVersion()) + { + if (bManual || !IgnoredUpdates.contains(Version)) // when checked manually always show result + { + bNothing = false; + //QDateTime Updated = QDateTime::fromTime_t(Data["updated"].toULongLong()); + + QString UpdateMsg = Data["updateMsg"].toString(); + QString UpdateUrl = Data["updateUrl"].toString(); + + QString DownloadUrl = Data["downloadUrl"].toString(); + // 'sha256' + // 'signature' + + QString FullMessage = UpdateMsg.isEmpty() ? tr("

There is a new version of Sandboxie-Plus available.
New version: %1

").arg(Version) : UpdateMsg; + if (!DownloadUrl.isEmpty()) + FullMessage += tr("

Do you want to download the latest version?

"); + else if (!UpdateUrl.isEmpty()) + FullMessage += tr("

Do you want to go to the download page?

").arg(UpdateUrl); + + CCheckableMessageBox mb(this); + mb.setWindowTitle("Sandboxie-Plus"); + QIcon ico(QLatin1String(":/SandMan.png")); + mb.setIconPixmap(ico.pixmap(64, 64)); + //mb.setTextFormat(Qt::RichText); + mb.setText(FullMessage); + mb.setCheckBoxText(tr("Ignore this update, notify me anout the next one.")); + mb.setCheckBoxVisible(!bManual); + + if (!UpdateUrl.isEmpty() || !DownloadUrl.isEmpty()) { + mb.setStandardButtons(QDialogButtonBox::Yes | QDialogButtonBox::No); + mb.setDefaultButton(QDialogButtonBox::Yes); + } + else + mb.setStandardButtons(QDialogButtonBox::Ok); + + mb.exec(); + + if (mb.isChecked()) + theConf->SetValue("Options/IgnoredUpdates", IgnoredUpdates << Version); + + if (mb.clickedStandardButton() == QDialogButtonBox::Yes) + { + if (!DownloadUrl.isEmpty()) + { + QNetworkRequest Request = QNetworkRequest(DownloadUrl); + Request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + //Request.setRawHeader("Accept-Encoding", "gzip"); + QNetworkReply* pReply = m_RequestManager->get(Request); + connect(pReply, SIGNAL(finished()), this, SLOT(OnUpdateDownload())); + connect(pReply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(OnUpdateProgress(qint64, qint64))); + + m_pProgressDialog->OnStatusMessage(tr("Downloading new version...")); + m_pProgressDialog->show(); + } + else + QDesktopServices::openUrl(UpdateUrl); + } + } + } + + if (bNothing) + { + theConf->SetValue("Options/NextCheckForUpdates", QDateTime::currentDateTime().addDays(7).toTime_t()); + + if (bManual) + QMessageBox::information(this, "Sandboxie-Plus", tr("No new updates found, your Sandboxie-Plus is up to date.")); + } +} + +void CSandMan::OnUpdateProgress(qint64 bytes, qint64 bytesTotal) +{ + if (bytesTotal != 0) + m_pProgressDialog->OnProgressMessage("", 100 * bytes / bytesTotal); +} + +void CSandMan::OnUpdateDownload() +{ + QString TempDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation); + if (TempDir.right(1) != "/") + TempDir += "/"; + + m_pProgressDialog->OnProgressMessage("", -1); + + QNetworkReply* pReply = qobject_cast(sender()); + quint64 Size = pReply->bytesAvailable(); + QString Name = pReply->request().url().fileName(); + if (Name.isEmpty() || Name.right(4).compare(".exe", Qt::CaseInsensitive) != 0) + Name = "Sandboxie-Plus-Install.exe"; + + QString FilePath = TempDir + Name; + + QFile File(FilePath); + if (File.open(QFile::WriteOnly)) { + while (pReply->bytesAvailable() > 0) + File.write(pReply->read(4096)); + File.close(); + } + + pReply->deleteLater(); + + m_pProgressDialog->hide(); + + if (File.size() != Size) { + QMessageBox::critical(this, "Sandboxie-Plus", tr("Failed to download update from: %1").arg(pReply->request().url().toString())); + return; + } + + QString Message = tr("

New Sandboxie-Plus has been downloaded to the following location:

%1

Do you want to begin the installation. If any programs are running sandboxed, they will be terminated.

") + .arg(FilePath).arg("File:///" + TempDir); + if (QMessageBox("Sandboxie-Plus", Message, QMessageBox::Information, QMessageBox::Yes | QMessageBox::Default, QMessageBox::No | QMessageBox::Escape, QMessageBox::NoButton).exec() == QMessageBox::Yes) + QProcess::startDetached(FilePath); +} + +void CSandMan::OnHelp() +{ + if (sender() == m_pSupport) + QDesktopServices::openUrl(QUrl("https://xanasoft.com/go.php?to=donate")); + else if (sender() == m_pForum) + QDesktopServices::openUrl(QUrl("https://xanasoft.com/go.php?to=forum")); + else if (sender() == m_pManual) + QDesktopServices::openUrl(QUrl("https://xanasoft.com/go.php?to=sbie-docs")); + else + QDesktopServices::openUrl(QUrl("https://www.patreon.com/DavidXanatos")); +} + void CSandMan::OnAbout() { if (sender() == m_pAbout) @@ -1172,8 +1425,6 @@ void CSandMan::OnAbout() } else if (sender() == m_pAboutQt) QMessageBox::aboutQt(this); - else - QDesktopServices::openUrl(QUrl("https://www.patreon.com/DavidXanatos")); } void CSandMan::SetDarkTheme(bool bDark) diff --git a/SandboxiePlus/SandMan/SandMan.h b/SandboxiePlus/SandMan/SandMan.h index 7b029ffa24..4c6dcb1cae 100644 --- a/SandboxiePlus/SandMan/SandMan.h +++ b/SandboxiePlus/SandMan/SandMan.h @@ -6,6 +6,7 @@ #include "../MiscHelpers/Common/TreeViewEx.h" #include "../MiscHelpers/Common/PanelView.h" #include "../MiscHelpers/Common/ProgressDialog.h" +#include "../MiscHelpers/Common/NetworkAccessManager.h" #include "Models/ResMonModel.h" #include "Models/ApiMonModel.h" #include @@ -42,6 +43,9 @@ class CSandMan : public QMainWindow void AddAsyncOp(const CSbieProgressPtr& pProgress); static void CheckResults(QList Results); + QAction* GetNewAction() { return m_pNew; } + QAction* GetEmptyAllAction() { return m_pEmptyAll; } + protected: SB_STATUS ConnectSbie(); SB_STATUS ConnectSbieImpl(); @@ -64,6 +68,8 @@ class CSandMan : public QMainWindow QMap m_pAsyncProgress; + CNetworkAccessManager* m_RequestManager; + public slots: void OnMessage(const QString&); @@ -88,6 +94,8 @@ public slots: void OnBoxClosed(const QString& BoxName); + void CheckForUpdates(bool bManual = true); + private slots: void OnSelectionChanged(); @@ -108,11 +116,19 @@ private slots: void OnSetLogging(); void OnExit(); + void OnHelp(); void OnAbout(); void OnSysTray(QSystemTrayIcon::ActivationReason Reason); + void OnUpdateCheck(); + void OnUpdateProgress(qint64 bytes, qint64 bytesTotal); + void OnUpdateDownload(); + private: + void CreateMenus(); + void CreateToolBar(); + QWidget* m_pMainWidget; QVBoxLayout* m_pMainLayout; @@ -170,8 +186,11 @@ private slots: QAction* m_pEnableLogging; QMenu* m_pMenuHelp; - QAction* m_pAbout; QAction* m_pSupport; + QAction* m_pForum; + QAction* m_pManual; + QAction* m_pUpdate; + QAction* m_pAbout; QAction* m_pAboutQt; QSystemTrayIcon* m_pTrayIcon; diff --git a/SandboxiePlus/SandMan/Views/SbieView.cpp b/SandboxiePlus/SandMan/Views/SbieView.cpp index 5d8e977d83..f8d5964b7d 100644 --- a/SandboxiePlus/SandMan/Views/SbieView.cpp +++ b/SandboxiePlus/SandMan/Views/SbieView.cpp @@ -53,6 +53,13 @@ CSbieView::CSbieView(QWidget* parent) : CPanelView(parent) connect(m_pSbieModel, SIGNAL(ToolTipCallback(const QVariant&, QString&)), this, SLOT(OnToolTipCallback(const QVariant&, QString&)), Qt::DirectConnection); + m_pMenu->addAction(theGUI->GetNewAction()); + //m_pMenu->addAction(theGUI->GetEmptyAllAction()); + m_pAddGroupe = m_pMenu->addAction(QIcon(":/Actions/Group"), tr("Add Group"), this, SLOT(OnGroupAction())); + m_pDelGroupe = m_pMenu->addAction(QIcon(":/Actions/Remove"), tr("Remove Group"), this, SLOT(OnGroupAction())); + m_iMenuTop = m_pMenu->actions().count(); + //m_pMenu->addSeparator(); + m_pMenuRun = m_pMenu->addMenu(QIcon(":/Actions/Run"), tr("Run")); m_pMenuRunAny = m_pMenuRun->addAction(tr("Run Program"), this, SLOT(OnSandBoxAction())); m_pMenuRunMenu = m_pMenuRun->addAction(tr("Run from Start Menu"), this, SLOT(OnSandBoxAction())); @@ -82,6 +89,8 @@ CSbieView::CSbieView(QWidget* parent) : CPanelView(parent) m_pMenuPresetsNoAdmin->setCheckable(true); m_pMenuOptions = m_pMenu->addAction(QIcon(":/Actions/Options"), tr("Sandbox Options"), this, SLOT(OnSandBoxAction())); m_pMenuRename = m_pMenu->addAction(QIcon(":/Actions/Rename"), tr("Rename Sandbox"), this, SLOT(OnSandBoxAction())); + m_iMoveTo = m_pMenu->actions().count(); + m_pMenuMoveTo = m_pMenu->addMenu(QIcon(":/Actions/Group"), tr("Move to Group")); m_pMenuRemove = m_pMenu->addAction(QIcon(":/Actions/Remove"), tr("Remove Sandbox"), this, SLOT(OnSandBoxAction())); m_iMenuBox = m_pMenu->actions().count(); @@ -123,7 +132,7 @@ CSbieView::~CSbieView() void CSbieView::Refresh() { - QList Added = m_pSbieModel->Sync(theAPI->GetAllBoxes()); + QList Added = m_pSbieModel->Sync(theAPI->GetAllBoxes(), m_Groups); if (m_pSbieModel->IsTree()) { @@ -168,7 +177,8 @@ void CSbieView::OnMenu(const QPoint& Point) int iProcessCount = 0; int iSandBoxeCount = 0; int iSuspendedCount = 0; - foreach(const QModelIndex& Index, m_pSbieTree->selectedRows()) + QModelIndexList Rows = m_pSbieTree->selectedRows(); + foreach(const QModelIndex& Index, Rows) { QModelIndex ModelIndex = m_pSortProxy->mapToSource(Index); pProcess = m_pSbieModel->GetProcess(ModelIndex); @@ -188,10 +198,16 @@ void CSbieView::OnMenu(const QPoint& Point) QList MenuActions = m_pMenu->actions(); - for (int i = 0; i < m_iMenuBox; i++) + for (int i = 0; i < m_iMenuTop; i++) + MenuActions[i]->setVisible(iSandBoxeCount == 0 && iProcessCount == 0); + m_pDelGroupe->setVisible(!Rows.isEmpty() && iSandBoxeCount == 0 && iProcessCount == 0); + + for (int i = m_iMenuTop; i < m_iMenuBox; i++) MenuActions[i]->setVisible(iSandBoxeCount > 0 && iProcessCount == 0); m_pMenuRun->setEnabled(iSandBoxeCount == 1); + MenuActions[m_iMoveTo]->setVisible(!Rows.isEmpty() && iProcessCount == 0); + if(iSandBoxeCount == 1) UpdateRunMenu(pBox); @@ -247,6 +263,134 @@ void CSbieView::OnMenu(const QPoint& Point) CPanelView::OnMenu(Point); } +int CSbieView__ParseGroup(const QString& Grouping, QMap& m_Groups, const QString& Parent = "", int Index = 0) +{ + QRegExp RegExp("[,()]", Qt::CaseInsensitive, QRegExp::RegExp); + for (; ; ) + { + int pos = Grouping.indexOf(RegExp, Index); + QString Name; + if (pos == -1) { + Name = Grouping.mid(Index); + Index = Grouping.length(); + } + else { + Name = Grouping.mid(Index, pos - Index); + Index = pos + 1; + } + if (!Name.isEmpty()) + m_Groups[Parent].append(Name); + if (pos == -1) + break; + if (Grouping.at(pos) == "(") + Index = CSbieView__ParseGroup(Grouping, m_Groups, Name, Index); + else if (Grouping.at(pos) == ")") + break; + } + return Index; +} + +void CSbieView::ReloadGroups() +{ + m_Groups.clear(); + + QString Grouping = theAPI->GetUserSettings()->GetText("BoxDisplayOrder"); + + CSbieView__ParseGroup(Grouping, m_Groups); + + // update move to menu + foreach(QAction* pAction, m_pMenuMoveTo->actions()) + m_pMenuMoveTo->removeAction(pAction); + foreach(const QString& Group, m_Groups.keys()) + { + QAction* pAction = m_pMenuMoveTo->addAction(Group.isEmpty() ? tr("[None]") : Group, this, SLOT(OnGroupAction())); + pAction->setData(Group); + } +} + +QString CSbieView__SerializeGroup(QMap& m_Groups, const QString& Parent = "") +{ + QStringList Grouping; + foreach(const QString& Name, m_Groups[Parent]) + { + if (m_Groups.contains(Name)) + Grouping.append(Name + "(" + CSbieView__SerializeGroup(m_Groups, Name) + ")"); + else + Grouping.append(Name); + } + return Grouping.join(","); +} + +void CSbieView::OnGroupAction() +{ + QAction* Action = qobject_cast(sender()); + + if (Action == m_pAddGroupe) + { + QString Name = QInputDialog::getText(this, "Sandboxie-Plus", tr("Please enter a new group name"), QLineEdit::Normal); + if (Name.isEmpty() || m_Groups.contains(Name)) + return; + m_Groups[Name] = QStringList(); + + QModelIndex ModelIndex = m_pSortProxy->mapToSource(m_pSbieTree->currentIndex()); + QString Parent; + if (m_pSbieModel->GetType(ModelIndex) == CSbieModel::eGroup) + Parent = m_pSbieModel->GetID(ModelIndex).toString(); + + if (!Parent.isEmpty()) + m_Groups[Parent].append(Name); + } + else if (Action == m_pDelGroupe) + { + if (QMessageBox("Sandboxie-Plus", tr("Do you really want remove the selected group(s)?"), QMessageBox::Warning, QMessageBox::Yes, QMessageBox::No | QMessageBox::Default | QMessageBox::Escape, QMessageBox::NoButton).exec() != QMessageBox::Yes) + return; + + foreach(const QModelIndex& Index, m_pSbieTree->selectedRows()) + { + QModelIndex ModelIndex = m_pSortProxy->mapToSource(Index); + if (m_pSbieModel->GetType(ModelIndex) == CSbieModel::eGroup) + { + QString Group = m_pSbieModel->GetID(ModelIndex).toString(); + m_Groups.remove(Group); + + // remove from parents + for (auto I = m_Groups.begin(); I != m_Groups.end(); ++I) + I.value().removeAll(Group); + } + } + + m_pSbieModel->Clear(); //todo improve that, also move boxes to grant parent? + } + else // move to groupe + { + QString Group = Action->data().toString(); + + foreach(const QModelIndex& Index, m_pSbieTree->selectedRows()) + { + QModelIndex ModelIndex = m_pSortProxy->mapToSource(Index); + QString Name; + if (m_pSbieModel->GetType(ModelIndex) == CSbieModel::eGroup) + Name = m_pSbieModel->GetID(ModelIndex).toString(); + else if (m_pSbieModel->GetType(ModelIndex) == CSbieModel::eBox) + Name = m_pSbieModel->GetSandBox(ModelIndex)->GetName(); + if (Name.isEmpty() || Name == Group) + continue; + + // remove from old + for (auto I = m_Groups.begin(); I != m_Groups.end(); ++I) + I.value().removeAll(Name); + + // add to new + m_Groups[Group].append(Name); + } + + m_pSbieModel->Clear(); //todo improve that + } + + QString Grouping = CSbieView__SerializeGroup(m_Groups); + theAPI->GetUserSettings()->SetText("BoxDisplayOrder", Grouping); +} + void CSbieView::OnSandBoxAction() { QList Results; @@ -337,7 +481,39 @@ void CSbieView::OnSandBoxAction() } else if (Action == m_pMenuMkLink) { - CSbieUtils::CreateDesktopShortcut(SandBoxes.first()->GetName(), theAPI); + QString BoxName = SandBoxes.first()->GetName(); + QString LinkPath, IconPath, WorkDir; + quint32 IconIndex; + if (!CSbieUtils::GetStartMenuShortcut(theAPI, BoxName, LinkPath, IconPath, IconIndex, WorkDir)) + return; + + QString LinkName; + int pos = LinkPath.lastIndexOf(L'\\'); + if (pos == -1) + return; + if (pos == 2 && LinkPath.length() == 3) + LinkName = QObject::tr("Drive %1").arg(LinkPath.left(1)); + else { + LinkName = LinkPath.mid(pos + 1); + pos = LinkName.indexOf(QRegExp("[" + QRegExp::escape("\":;,*?.") + "]")); + if (pos != -1) + LinkName = LinkName.left(pos); + } + + QString Path = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).replace("/", "\\"); + //Path = QFileDialog::getExistingDirectory(this, tr("Select Directory to create Shorcut in"), Path).replace("/", "\\"); + //if (Path.isEmpty()) + // return; + + if (Path.right(1) != "\\") + Path.append("\\"); + Path += "[" + BoxName + "] " + LinkName; + + Path = QFileDialog::getSaveFileName(this, tr("Create Shortcut to sandbox %1").arg(BoxName), Path, QString("Shortcut files (*.lnk)")).replace("/", "\\"); + if (Path.isEmpty()) + return; + + CSbieUtils::CreateShortcut(theAPI, Path, LinkName, BoxName, LinkPath, IconPath, IconIndex, WorkDir); } else // custom run menu command { @@ -422,13 +598,14 @@ void CSbieView::ProcessSelection(const QItemSelection& selected, const QItemSele return; QItemSelectionModel* selectionModel = m_pSbieTree->selectionModel(); - QItemSelection selection = selectionModel->selection(); + QItemSelection invalid; + + /* QModelIndex root_parent = m_pSbieTree->currentIndex().parent(); while (root_parent.isValid() && root_parent.parent().isValid()) root_parent = root_parent.parent(); - QItemSelection invalid; foreach(const QModelIndex& index, selection.indexes()) { QModelIndex parent = index.parent(); @@ -437,7 +614,16 @@ void CSbieView::ProcessSelection(const QItemSelection& selected, const QItemSele if (parent != root_parent) invalid.select(index, index); + }*/ + + int Type = m_pSbieModel->GetType(m_pSortProxy->mapToSource(m_pSbieTree->currentIndex())); + + foreach(const QModelIndex& index, selection.indexes()) + { + if (m_pSbieModel->GetType(m_pSortProxy->mapToSource(index)) != Type) + invalid.select(index, index); } + selectionModel->select(invalid, QItemSelectionModel::Deselect); } diff --git a/SandboxiePlus/SandMan/Views/SbieView.h b/SandboxiePlus/SandMan/Views/SbieView.h index eb78edb017..02915349f4 100644 --- a/SandboxiePlus/SandMan/Views/SbieView.h +++ b/SandboxiePlus/SandMan/Views/SbieView.h @@ -22,6 +22,7 @@ class CSbieView : public CPanelView public slots: void Refresh(); + void ReloadGroups(); private slots: void OnToolTipCallback(const QVariant& ID, QString& ToolTip); @@ -29,6 +30,7 @@ private slots: void OnDoubleClicked(const QModelIndex& index); void ProcessSelection(const QItemSelection& selected, const QItemSelection& deselected); + void OnGroupAction(); void OnSandBoxAction(); void OnProcessAction(); @@ -39,6 +41,8 @@ private slots: virtual void UpdateRunMenu(const CSandBoxPtr& pBox); + QMap m_Groups; + private: QVBoxLayout* m_pMainLayout; @@ -47,7 +51,9 @@ private slots: CSbieModel* m_pSbieModel; QSortFilterProxyModel* m_pSortProxy; - + QAction* m_pAddGroupe; + QAction* m_pDelGroupe; + int m_iMenuTop; QMenu* m_pMenuRun; QAction* m_pMenuRunAny; QAction* m_pMenuRunMenu; @@ -68,6 +74,8 @@ private slots: QAction* m_pMenuRecover; QAction* m_pMenuCleanUp; QAction* m_pMenuRemove; + QMenu* m_pMenuMoveTo; + int m_iMoveTo; QAction* m_pMenuRename; int m_iMenuBox; diff --git a/SandboxiePlus/SandMan/Windows/OptionsWindow.cpp b/SandboxiePlus/SandMan/Windows/OptionsWindow.cpp index 99c37b529e..84ddf50d37 100644 --- a/SandboxiePlus/SandMan/Windows/OptionsWindow.cpp +++ b/SandboxiePlus/SandMan/Windows/OptionsWindow.cpp @@ -234,6 +234,7 @@ COptionsWindow::COptionsWindow(const QSharedPointer& pBox, const QStri connect(ui.chkKeyTrace, SIGNAL(clicked(bool)), this, SLOT(OnAdvancedChanged())); connect(ui.chkIpcTrace, SIGNAL(clicked(bool)), this, SLOT(OnAdvancedChanged())); connect(ui.chkGuiTrace, SIGNAL(clicked(bool)), this, SLOT(OnAdvancedChanged())); + connect(ui.chkDbgTrace, SIGNAL(clicked(bool)), this, SLOT(OnAdvancedChanged())); connect(ui.chkHideOtherBoxes, SIGNAL(clicked(bool)), this, SLOT(OnAdvancedChanged())); connect(ui.btnAddProcess, SIGNAL(pressed()), this, SLOT(OnAddProcess())); @@ -356,7 +357,7 @@ void COptionsWindow::LoadConfig() LoadStop(); { - ui.chkStartBlockMsg->setEnabled(ui.radStartAll->isChecked()); + ui.chkStartBlockMsg->setEnabled(!ui.radStartAll->isChecked()); ui.chkStartBlockMsg->setChecked(m_pBox->GetBool("NotifyStartRunAccessDenied", true)); m_StartChanged = false; @@ -392,6 +393,7 @@ void COptionsWindow::LoadConfig() ReadAdvancedCheck("KeyTrace", ui.chkKeyTrace, "*"); ReadAdvancedCheck("IpcTrace", ui.chkIpcTrace, "*"); ReadAdvancedCheck("GuiTrace", ui.chkGuiTrace, "*"); + ui.chkDbgTrace->setChecked(m_pBox->GetBool("DebugTrace", false)); ui.chkHideOtherBoxes->setChecked(m_pBox->GetBool("HideOtherBoxes", false)); QStringList Processes = m_pBox->GetTextList("HideHostProcess", false); @@ -507,6 +509,7 @@ void COptionsWindow::SaveConfig() WriteAdvancedCheck(ui.chkKeyTrace, "KeyTrace", "*"); WriteAdvancedCheck(ui.chkIpcTrace, "IpcTrace", "*"); WriteAdvancedCheck(ui.chkGuiTrace, "GuiTrace", "*"); + WriteAdvancedCheck(ui.chkDbgTrace, "DebugTrace", "y"); WriteAdvancedCheck(ui.chkHideOtherBoxes, "HideOtherBoxes"); @@ -992,7 +995,7 @@ void COptionsWindow::OnRestrictStart() else DelAccessEntry(eIPC, "", eClosed, "*"); - ui.chkStartBlockMsg->setEnabled(ui.radStartAll->isChecked()); + ui.chkStartBlockMsg->setEnabled(!ui.radStartAll->isChecked()); //m_StartChanged = true; } @@ -1011,6 +1014,8 @@ void COptionsWindow::OnDelStartProg() void COptionsWindow::OnBlockINet() { bool Enable = ui.chkBlockINet->isChecked(); + ui.chkINetBlockPrompt->setEnabled(Enable); + ui.chkINetBlockMsg->setEnabled(Enable); if (Enable) SetAccessEntry(eFile, "!", eClosed, "InternetAccessDevices"); else @@ -1030,14 +1035,6 @@ void COptionsWindow::OnDelINetProg() //m_INetBlockChanged = true; } -void COptionsWindow::OnINetBlockChanged() -{ - ui.chkINetBlockPrompt->setEnabled(ui.chkBlockINet->isChecked()); - ui.chkINetBlockMsg->setEnabled(ui.chkBlockINet->isChecked()); - - m_INetBlockChanged = true; -} - void COptionsWindow::AddProgToGroup(QTreeWidget* pTree, const QString& Groupe) { QString Value = SelectProgram(); diff --git a/SandboxiePlus/SandMan/Windows/OptionsWindow.h b/SandboxiePlus/SandMan/Windows/OptionsWindow.h index d71851eb69..220ce3104f 100644 --- a/SandboxiePlus/SandMan/Windows/OptionsWindow.h +++ b/SandboxiePlus/SandMan/Windows/OptionsWindow.h @@ -87,7 +87,7 @@ private slots: void OnGeneralChanged(); void OnStartChanged() { m_StartChanged = true; } //void OnRestrictionChanged() { m_RestrictionChanged = true; } - void OnINetBlockChanged(); + void OnINetBlockChanged() { m_INetBlockChanged = true; } void OnRecoveryChanged() { m_RecoveryChanged = true; } void OnAdvancedChanged(); void OnDebugChanged(); diff --git a/SandboxiePlus/SandMan/Windows/SettingsWindow.cpp b/SandboxiePlus/SandMan/Windows/SettingsWindow.cpp index 0ab5796e64..3f6c55a70f 100644 --- a/SandboxiePlus/SandMan/Windows/SettingsWindow.cpp +++ b/SandboxiePlus/SandMan/Windows/SettingsWindow.cpp @@ -28,6 +28,13 @@ CSettingsWindow::CSettingsWindow(QWidget *parent) ui.chkAutoStart->setChecked(IsAutorunEnabled()); + switch (theConf->GetInt("Options/CheckForUpdates", 2)) { + case 0: ui.chkAutoUpdate->setCheckState(Qt::Unchecked); break; + case 1: ui.chkAutoUpdate->setCheckState(Qt::Checked); break; + case 2: ui.chkAutoUpdate->setCheckState(Qt::PartiallyChecked); break; + } + + ui.chkShellMenu->setCheckState((Qt::CheckState)CSbieUtils::IsContextMenu()); ui.chkDarkTheme->setChecked(theConf->GetBool("Options/DarkTheme", false)); @@ -140,6 +147,12 @@ void CSettingsWindow::apply() AutorunEnable(ui.chkAutoStart->isChecked()); + switch (ui.chkAutoUpdate->checkState()) { + case Qt::Unchecked: theConf->SetValue("Options/CheckForUpdates", 0); break; + case Qt::PartiallyChecked: theConf->SetValue("Options/CheckForUpdates", 2); break; + case Qt::Checked: theConf->SetValue("Options/CheckForUpdates", 1); break; + } + if (ui.chkShellMenu->checkState() != CSbieUtils::IsContextMenu()) { if (ui.chkShellMenu->isChecked()) diff --git a/SandboxiePlus/SandMan/main.cpp b/SandboxiePlus/SandMan/main.cpp index 0e9fa2bf0b..fc3a9f0cdb 100644 --- a/SandboxiePlus/SandMan/main.cpp +++ b/SandboxiePlus/SandMan/main.cpp @@ -10,9 +10,6 @@ CSettings* theConf = NULL; -void PackDriver(); -void UnPackDrivers(); - int main(int argc, char *argv[]) { #ifdef Q_OS_WIN @@ -25,11 +22,6 @@ int main(int argc, char *argv[]) QtSingleApplication app(argc, argv); //InitConsole(false); - if (app.arguments().contains("-rc4")) - { - PackDriver(); - return 0; - } SB_STATUS Status = CSbieUtils::DoAssist(); if (Status.GetStatus()) { @@ -42,8 +34,6 @@ int main(int argc, char *argv[]) theConf = new CSettings("Sandboxie-Plus"); - UnPackDrivers(); - //QThreadPool::globalInstance()->setMaxThreadCount(theConf->GetInt("Options/MaxThreadPool", 10)); CSandMan* pWnd = new CSandMan(); @@ -58,118 +48,3 @@ int main(int argc, char *argv[]) return ret; } - -bool TransformFile(const QString& InName, const QString& OutName, const QString& Key = "default_key") -{ - QFile InFile(InName); - QFile OutFile(OutName); - if (InFile.open(QIODevice::ReadOnly)) - { - if (OutFile.open(QIODevice::WriteOnly)) - { - rc4_sbox_s sbox; - rc4_init(&sbox, Key.toLatin1()); - OutFile.write(rc4_transform(&sbox, InFile.readAll())); - OutFile.flush(); - return true; - } - } - return false; -} - -bool TestFile(const QString& OutName) -{ - QThread::sleep(3); - return QFile::exists(OutName); -} - -void PackDriver() -{ - int pos = QCoreApplication::arguments().indexOf("-rc4"); - QString InName = QCoreApplication::arguments().at(pos + 1); - QString OutName = InName; - if (OutName.right(4) == ".rc4") - OutName.truncate(OutName.length() - 4); - else - OutName.append(".rc4"); - - if (TransformFile(InName, OutName)) - printf("success\r\n"); - else - printf("failed\r\n"); -} - -bool NotifyCert() -{ - QString Caption = QObject::tr( - "

Important Notie

" - ); - QString Text = QObject::tr( - "

Sandboxie requires a driver to operate (SbieDrv.sys), Windows however denies loading a driver file that has not been digitally signed using an expensive EV-Code Signing Certificate. " - "Such certificates cost upwards of 1000$ per year!

" - "

Luckily brave hackers have Leaked a few of these Certificates over the years, one of them was found by the author of this software and put to good use.

" - "

Unfortunately, such certificates have been abused by malware authors resulting in many Anti Malware Fools being Lazy and flagging Everything signed with them Wrongfully as Malware. " - "This Prejudice is Damaging the Open Source Ecosystem as most nonprofit developers can't afford to waste this kind of money every year only to pay that Windows Kernel \"Tax\".

" - "

Therefore, the required driver is provided in an obfuscated form and before use must be unpacked. " - "When doing so said said Anti Viruses will complain and attempt to destroy the freshly created file. " - "Please be aware that this is a false positive caused by the necessary use of a compromitted certificate. " - "If this happens you will be notified and offered the option to repeat the unpacking operation, for the operation to succeed you will need to configure an aproproate exemption.

" - - "

" - "

If you want the project to obtain a genuine EV-Code Signing Certificate please visit the Homepage and please donate.

" - "

" - - "

If you want to proceed with the unpacking of the driver pres YES.

" - ); - QMessageBox *msgBox = new QMessageBox(NULL); - msgBox->setAttribute(Qt::WA_DeleteOnClose); - msgBox->setWindowTitle("Sandboxie-Plus"); - msgBox->setText(Caption); - msgBox->setInformativeText(Text); - msgBox->setStandardButtons(QMessageBox::Yes); - msgBox->addButton(QMessageBox::No); - msgBox->setDefaultButton(QMessageBox::Yes); - - QIcon ico(QLatin1String(":/SandMan.png")); - msgBox->setIconPixmap(ico.pixmap(64, 64)); - - return msgBox->exec() == QMessageBox::Yes; -} - -void UnPackDrivers() -{ - bool notifyNotOk = false; - QDir appDir(QApplication::applicationDirPath()); - foreach(const QString& FileName, appDir.entryList(QStringList("*.sys.rc4"), QDir::Files)) - { - QString InName = QApplication::applicationDirPath() + "/" + FileName; - QString OutName = InName.mid(0, InName.length() - 4); - - QFileInfo InInfo(InName); - QFileInfo OutInfo(OutName); - if (InInfo.size() != OutInfo.size() /*|| InInfo.lastModified() > OutInfo.lastModified()*/) - { - if (theConf->GetBool("Options/NotifyUnPack", true)) { - if (!NotifyCert()) { - notifyNotOk = true; - break; - } - theConf->SetValue("Options/NotifyUnPack", false); - } - - retry: - if (!TransformFile(InName, OutName)) - QMessageBox::warning(NULL, "Sandboxie-Plus", QObject::tr("Failed to decrypt %1 ensure app directory is writable.").arg(FileName)); - else if (!TestFile(OutName)) - { - if (QMessageBox("Sandboxie-Plus", - QObject::tr("The decrypted file %1 seam to have been removed. Retry file extraction?").arg(FileName), - QMessageBox::Information, QMessageBox::Yes | QMessageBox::Default, QMessageBox::Cancel, QMessageBox::NoButton).exec() == QMessageBox::Yes) - goto retry; - notifyNotOk = true; - } - } - } - if (notifyNotOk) - QMessageBox::warning(NULL, "Sandboxie-Plus", QObject::tr("Without the Driver Sandboxie-Plus wont be able to run properly.")); -} \ No newline at end of file