diff --git a/src/winmain.cpp b/src/winmain.cpp index 48dc49d8..839d8693 100644 --- a/src/winmain.cpp +++ b/src/winmain.cpp @@ -88,6 +88,8 @@ static constexpr wchar_t FLAG_HELP[] = L"--help"; static constexpr wchar_t FLAG_UUZIP[] = L"-unzipTo"; static constexpr wchar_t FLAG_CLEANUP[] = L"-clean"; +static constexpr wchar_t FLAG_DEACTIVATE[] = L"-deactivate"; +static constexpr wchar_t FLAG_ACTIVATE[] = L"-activate"; static constexpr wchar_t FLAG_INFOURL[] = L"-infoUrl="; static constexpr wchar_t FLAG_FORCEDOMAIN[] = L"-forceDomain="; @@ -171,6 +173,16 @@ gup -unzipTo [-clean] FOLDER_TO_ACTION ZIP_URL\r\n\ -unzipTo : Download zip file from ZIP_URL then unzip it into FOLDER_TO_ACTION.\r\n\ ZIP_URL : The URL to download zip file.\r\n\ FOLDER_TO_ACTION : The folder where we clean or/and unzip to.\r\n\ +\r\n\ +Plugin deactivate/activate mode:\r\n\ +\r\n\ +gup -deactivate \"appPath2Launch\" \"SRC_ROOT\" \"DEST_ROOT\" \"folder1\" \"folder2\" ...\r\n\ +gup -activate \"appPath2Launch\" \"SRC_ROOT\" \"DEST_ROOT\" \"folder1\" \"folder2\" ...\r\n\ +\r\n\ + -deactivate : Move the given plugin folders from SRC_ROOT (plugins dir)\r\n\ + to DEST_ROOT (plugins_disabled dir).\r\n\ + -activate : Move the given plugin folders from SRC_ROOT (plugins_disabled dir)\r\n\ + back to DEST_ROOT (plugins dir).\r\n\ "; HFONT hCmdLineEditFont = nullptr; @@ -508,6 +520,53 @@ bool deleteFileOrFolder(const wstring& f2delete) return (res == 0); }; +// Moves a file or folder using SHFileOperation (FO_MOVE). +// If overwriteIfExists is true and fTo already exists, fTo is deleted first. +// Conflict resolution (asking the user Yes/No/Yes to all/No to all) is expected +// to happen upstream (in Notepad++, before it exits and launches gup), since +// gup itself runs headless after the host application has already closed. +bool moveFileOrFolder(const wstring& fFrom, const wstring& fTo, bool overwriteIfExists) +{ + if (overwriteIfExists && ::PathFileExists(fTo.c_str())) + { + deleteFileOrFolder(fTo); + } + + auto lenFrom = fFrom.length(); + wchar_t* actionFrom = new wchar_t[lenFrom + 2]; + lstrcpy(actionFrom, fFrom.c_str()); + actionFrom[lenFrom] = 0; + actionFrom[lenFrom + 1] = 0; + + auto lenTo = fTo.length(); + wchar_t* actionTo = new wchar_t[lenTo + 2]; + lstrcpy(actionTo, fTo.c_str()); + actionTo[lenTo] = 0; + actionTo[lenTo + 1] = 0; + + SHFILEOPSTRUCT fileOpStruct = { 0 }; + fileOpStruct.hwnd = NULL; + fileOpStruct.pFrom = actionFrom; + fileOpStruct.pTo = actionTo; + fileOpStruct.wFunc = FO_MOVE; + fileOpStruct.fFlags = FOF_NOCONFIRMATION | FOF_SILENT | FOF_ALLOWUNDO; + fileOpStruct.fAnyOperationsAborted = false; + fileOpStruct.hNameMappings = NULL; + fileOpStruct.lpszProgressTitle = NULL; + + int res = SHFileOperation(&fileOpStruct); + if (res != 0) + { + // beware that some of the SHFileOperation error codes are not the standard WIN32 ones + // (e.g. 124 (0x7C) here means DE_INVALIDFILES and not the usual ERROR_INVALID_LEVEL) + WRITE_LOG(GUP_LOG_FILENAME, L"moveFileOrFolder, SHFileOperation failed with error code: ", std::to_wstring(res).c_str()); + } + + delete[] actionFrom; + delete[] actionTo; + return (res == 0); +}; + // unzipDestTo should be plugin home root + plugin folder name // ex: %APPDATA%\..\local\Notepad++\plugins\myAwesomePlugin @@ -1320,6 +1379,8 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR lpszCmdLine, int) bool isHelp = isInList(FLAG_HELP, params); bool isCleanUp = isInList(FLAG_CLEANUP, params); bool isUnzip = isInList(FLAG_UUZIP, params); + bool isDeactivate = isInList(FLAG_DEACTIVATE, params); + bool isActivate = isInList(FLAG_ACTIVATE, params); getParamVal('v', params, version); getParamVal('p', params, customParam); @@ -1512,7 +1573,118 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR lpszCmdLine, int) return 0; } - + + // deactivate plugin(s): move folders from plugins dir to plugins_disabled dir + // gup.exe -deactivate "appPath2Launch" "plugins_root" "disabled_root" "fold1" "fold2" ... + if (isDeactivate) + { + if (nbParam < 4) + { + WRITE_LOG(GUP_LOG_FILENAME, L"-1 in plugin updater's part - if (isDeactivate): ", L"nbParam < 4"); + return -1; + } + + wstring prog2Launch = params[0]; + wstring srcRoot = params[1]; + wstring destRoot = params[2]; + +#ifdef _DEBUG + // Don't check any thing in debug mode +#else + // Check signature of the launched program, with the same certif as gup.exe + SecurityGuard securityGuard4PluginsInstall; + + if (!securityGuard4PluginsInstall.initFromSelfCertif()) + { + securityGuard.writeSecurityError(L"Above certificate init error from \"gup -deactivate\" (deactivate plugins)", L""); + return -1; + } + + bool isSecured = securityGuard4PluginsInstall.verifySignedBinary(prog2Launch.c_str()); + if (!isSecured) + { + securityGuard.writeSecurityError(L"Above certificate verification error from \"gup -deactivate\" (deactivate plugins)", L""); + return -1; + } +#endif + + if (!::PathFileExists(destRoot.c_str())) + { + ::CreateDirectory(destRoot.c_str(), NULL); + } + + for (size_t i = 3; i < nbParam; ++i) + { + wstring srcPath = srcRoot; + ::PathAppend(srcPath, params[i]); + + wstring destPath = destRoot; + ::PathAppend(destPath, params[i]); + + // Overwrite conflicts are already resolved by the caller (Notepad++) before + // invoking gup: every folder name reaching this point has been confirmed by + // the user, so it is safe (and necessary) to overwrite an existing destination. + moveFileOrFolder(srcPath, destPath, true); + } + + safeLaunchAsUser(prog2Launch); + + return 0; + } + + // activate plugin(s): move folders from plugins_disabled dir back to plugins dir + // gup.exe -activate "appPath2Launch" "disabled_root" "plugins_root" "fold1" "fold2" ... + if (isActivate) + { + if (nbParam < 4) + { + WRITE_LOG(GUP_LOG_FILENAME, L"-1 in plugin updater's part - if (isActivate): ", L"nbParam < 4"); + return -1; + } + + wstring prog2Launch = params[0]; + wstring srcRoot = params[1]; + wstring destRoot = params[2]; + +#ifdef _DEBUG + // Don't check any thing in debug mode +#else + // Check signature of the launched program, with the same certif as gup.exe + SecurityGuard securityGuard4PluginsInstall; + + if (!securityGuard4PluginsInstall.initFromSelfCertif()) + { + securityGuard.writeSecurityError(L"Above certificate init error from \"gup -activate\" (activate plugins)", L""); + return -1; + } + + bool isSecured = securityGuard4PluginsInstall.verifySignedBinary(prog2Launch.c_str()); + if (!isSecured) + { + securityGuard.writeSecurityError(L"Above certificate verification error from \"gup -activate\" (activate plugins)", L""); + return -1; + } +#endif + + for (size_t i = 3; i < nbParam; ++i) + { + wstring srcPath = srcRoot; + ::PathAppend(srcPath, params[i]); + + wstring destPath = destRoot; + ::PathAppend(destPath, params[i]); + + // Overwrite conflicts are already resolved by the caller (Notepad++) before + // invoking gup: every folder name reaching this point has been confirmed by + // the user, so it is safe (and necessary) to overwrite an existing destination. + moveFileOrFolder(srcPath, destPath, true); + } + + safeLaunchAsUser(prog2Launch); + + return 0; + } + // update: // gup.exe -unzip -clean "appPath2Launch" "dest_folder" "pluginFolderName1 http://pluginFolderName1/pluginFolderName1.zip sha256Hash1" "pluginFolderName2 http://pluginFolderName2/pluginFolderName2.zip sha256Hash2" "plugin Folder Name3 http://plugin_Folder_Name3/plugin_Folder_Name3.zip sha256Hash3" // gup.exe -unzip -clean "c:\npp\notepad++.exe" "c:\donho\notepad++\plugins" "toto http://toto/toto.zip 7c31a97b..." "ti et ti http://ti_ti/ti_ti.zip 087a0591..." "tata http://tata/tata.zip 2e9766c..."