Skip to content

Commit 4b0fc8d

Browse files
xomxdonho
authored andcommitted
Improve Notepad++ UAC operations largely
This native implementation of UAC (User Account Control) operations in Notepad++ is designed not only to substitute the deprecated & problematic NppSaveAsAdminPlugin - which interferes with the FlushFileBuffers WINAPI used by Notepad++ - but also to support any future Notepad++ feature which may require elevated privileges. When a user attempts an operation that fails due to indufficient rights, the system performs only that specific requested action with elevated privileges. After completing it, the elevated Notepad++ instance immediately exits, returning the user to his/her original Notepad++ instance seamlessly, as if nothing unusual occured. This mechanism is independent of any Notepad++ features such as backup-snapshot or multi-instance mode. All UAC-related operations are executed at the very beginning of the wWinMain function, ensuring they are not affected by mutex handling, or other internal logic. Importantly, this approach eliminates the need for a separate signed helper executable like NppAdminAcess.exe. Everything is handled within the main Notepad++ project, just as before. In this commit, the NPP_UAC_SAVE, NPP_UAC_SETFILEATTRIBUTES & NPP_UAC_MOVEFILE are implemented. Summary of the changes: added last _dwErrorCode in: .\PowerEditor\src\MISC\Common\FileInterface.h .\PowerEditor\src\MISC\Common\FileInterface.cpp FileManager::saveBuffer adjustment for the NPP_UAC_SAVE_SIGN in: .\PowerEditor\src\ScintillaComponent\Buffer.cpp N++ UAC ops signatures definitions & new invokeNppUacOp common func, toggleReadOnlyFlagFromFileAttributes func adjustment for the NPP_UAC_SETFILEATTRIBUTES_SIGN in: .\PowerEditor\src\MISC\Common\Common.h .\PowerEditor\src\MISC\Common\Common.cpp only to fix Notepad_plus::doSave for isEndSessionCritical() in: .\PowerEditor\src\NppIO.cpp added getLastFileErrorState() & m_dwLastFileError in: .\PowerEditor\src\Utf8_16.h .\PowerEditor\src\Utf8_16.cpp UAC ops handling at the very start of wWinMain + added new NPP_UAC_ handling nppUacSave and nppUacSetFileAttributes funcs in: .\PowerEditor\src\winmain.cpp Fix notepad-plus-plus#886, fix notepad-plus-plus#8655, fix notepad-plus-plus#9561, fix notepad-plus-plus#10302, fix notepad-plus-plus#14990, fix notepad-plus-plus#15008, fix notepad-plus-plus#15137, fix notepad-plus-plus#15323, close notepad-plus-plus#16933
1 parent dc58d41 commit 4b0fc8d

10 files changed

Lines changed: 415 additions & 129 deletions

File tree

PowerEditor/src/MISC/Common/Common.cpp

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1537,7 +1537,25 @@ bool toggleReadOnlyFlagFromFileAttributes(const wchar_t* fileFullPath, bool& isC
15371537
}
15381538
else
15391539
{
1540-
// probably the ERROR_ACCESS_DENIED (5) (TODO: UAC-prompt candidate)
1540+
if (::GetLastError() == ERROR_ACCESS_DENIED)
1541+
{
1542+
// try to set elevated
1543+
// (notepad++.exe #UAC-SETFILEATTRIBUTES# attrib_flags_number_str dest_file_path)
1544+
wstring strCmdLineParams = NPP_UAC_SETFILEATTRIBUTES_SIGN;
1545+
strCmdLineParams += L" \"" + to_wstring(dwFileAttribs) + L"\" \"";
1546+
strCmdLineParams += fileFullPath;
1547+
strCmdLineParams += L"\"";
1548+
DWORD dwNppUacOpError = invokeNppUacOp(strCmdLineParams);
1549+
if (dwNppUacOpError == NO_ERROR)
1550+
{
1551+
isChangedToReadOnly = (dwFileAttribs & FILE_ATTRIBUTE_READONLY) != 0;
1552+
return true;
1553+
}
1554+
else
1555+
{
1556+
::SetLastError(dwNppUacOpError); // set that as our current thread one for a possible reporting later
1557+
}
1558+
}
15411559
return false;
15421560
}
15431561
}
@@ -2154,3 +2172,40 @@ void ControlInfoTip::hide()
21542172
}
21552173

21562174
#pragma warning(default:4996)
2175+
2176+
DWORD invokeNppUacOp(std::wstring& strCmdLineParams)
2177+
{
2178+
if ((strCmdLineParams.length() == 0) || (strCmdLineParams.length() > (USHRT_MAX / sizeof(WCHAR))))
2179+
{
2180+
// no cmdline or it exceeds the current max WinOS 32767 WCHARs
2181+
return ERROR_INVALID_PARAMETER;
2182+
}
2183+
2184+
wchar_t wszNppFullPath[MAX_PATH]{};
2185+
::SetLastError(NO_ERROR);
2186+
if (!::GetModuleFileName(NULL, wszNppFullPath, MAX_PATH) || (::GetLastError() == ERROR_INSUFFICIENT_BUFFER))
2187+
{
2188+
return ::GetLastError();
2189+
}
2190+
2191+
SHELLEXECUTEINFOW sei{};
2192+
sei.cbSize = sizeof(SHELLEXECUTEINFOW);
2193+
sei.lpVerb = L"runas"; // UAC prompt
2194+
sei.nShow = SW_SHOWNORMAL;
2195+
sei.fMask = SEE_MASK_NOCLOSEPROCESS; // sei.hProcess member receives the launched process handle
2196+
sei.lpFile = wszNppFullPath;
2197+
sei.lpParameters = strCmdLineParams.c_str();
2198+
if (!::ShellExecuteExW(&sei))
2199+
return ::GetLastError();
2200+
2201+
// wait for the elevated Notepad++ process to finish
2202+
DWORD dwError = NO_ERROR;
2203+
if (sei.hProcess) // beware - do not check here for the INVALID_HANDLE_VALUE (valid GetCurrentProcess() pseudohandle)
2204+
{
2205+
::WaitForSingleObject(sei.hProcess, INFINITE);
2206+
::GetExitCodeProcess(sei.hProcess, &dwError);
2207+
::CloseHandle(sei.hProcess);
2208+
}
2209+
2210+
return dwError;
2211+
}

PowerEditor/src/MISC/Common/Common.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,3 +331,9 @@ class ControlInfoTip final
331331
ControlInfoTip(const ControlInfoTip&) = delete;
332332
ControlInfoTip& operator=(const ControlInfoTip&) = delete;
333333
};
334+
335+
336+
#define NPP_UAC_SAVE_SIGN L"#UAC-SAVE#"
337+
#define NPP_UAC_SETFILEATTRIBUTES_SIGN L"#UAC-SETFILEATTRIBUTES#"
338+
#define NPP_UAC_MOVEFILE_SIGN L"#UAC-MOVEFILE#"
339+
DWORD invokeNppUacOp(std::wstring& strCmdLineParams);

PowerEditor/src/MISC/Common/FileInterface.cpp

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@ Win32_IO_File::Win32_IO_File(const wchar_t *fname)
5454
{
5555
bool isFromNetwork = PathIsNetworkPath(fname);
5656
if (isFromNetwork && isTimeoutReached) // The file doesn't exist, and the file is a network file, plus the network problem has been detected due to timeout
57-
return; // In this case, we don't call createFile to prevent hanging
57+
{
58+
_dwErrorCode = ERROR_FILE_NOT_FOUND; // store
59+
return; // In this case, we don't call createFile to prevent hanging
60+
}
5861
}
5962

6063
_hFile = ::CreateFileW(fname, _accessParam, _shareParam, NULL, dispParam, _attribParam, NULL);
@@ -68,6 +71,9 @@ Win32_IO_File::Win32_IO_File(const wchar_t *fname)
6871
_hFile = ::CreateFileW(fname, _accessParam, _shareParam, NULL, dispParam, _attribParam, NULL);
6972
}
7073

74+
if (_hFile == INVALID_HANDLE_VALUE)
75+
_dwErrorCode = ::GetLastError(); // store
76+
7177
if (fileExists && (dispParam == CREATE_ALWAYS) && (_hFile != INVALID_HANDLE_VALUE))
7278
{
7379
// restore back the original creation date & attributes
@@ -91,7 +97,7 @@ Win32_IO_File::Win32_IO_File(const wchar_t *fname)
9197
else
9298
{
9399
msg += " failed to open, CreateFileW ErrorCode: ";
94-
msg += std::to_string(::GetLastError());
100+
msg += std::to_string(_dwErrorCode);
95101
}
96102
writeLog(nppIssueLog.c_str(), msg.c_str());
97103
}
@@ -100,11 +106,13 @@ Win32_IO_File::Win32_IO_File(const wchar_t *fname)
100106

101107
void Win32_IO_File::close()
102108
{
109+
_dwErrorCode = NO_ERROR; // reset
110+
103111
if (isOpened())
104112
{
105113
NppParameters& nppParam = NppParameters::getInstance();
106114

107-
DWORD flushError = NOERROR;
115+
DWORD flushError = NO_ERROR;
108116
if (_written)
109117
{
110118
if (!::FlushFileBuffers(_hFile))
@@ -159,7 +167,14 @@ Please try using another storage and also check if your saved data is not corrup
159167
}
160168
}
161169
}
162-
::CloseHandle(_hFile);
170+
171+
_dwErrorCode = flushError; // store possible flushing error 1st
172+
173+
if (!::CloseHandle(_hFile))
174+
{
175+
if (!flushError)
176+
_dwErrorCode = ::GetLastError(); // store
177+
}
163178

164179
_hFile = INVALID_HANDLE_VALUE;
165180

@@ -194,6 +209,8 @@ Please try using another storage and also check if your saved data is not corrup
194209

195210
bool Win32_IO_File::write(const void *wbuf, size_t buf_size)
196211
{
212+
_dwErrorCode = NO_ERROR; // reset
213+
197214
if (!isOpened() || (wbuf == nullptr))
198215
return false;
199216

@@ -203,6 +220,7 @@ bool Win32_IO_File::write(const void *wbuf, size_t buf_size)
203220
size_t bytes_left_to_write = buf_size;
204221

205222
BOOL success = FALSE;
223+
DWORD writeError = NO_ERROR; // use also a local var here to be 100% thread-safe
206224

207225
do
208226
{
@@ -219,6 +237,10 @@ bool Win32_IO_File::write(const void *wbuf, size_t buf_size)
219237
bytes_left_to_write -= static_cast<size_t>(bytes_written);
220238
total_bytes_written += static_cast<size_t>(bytes_written);
221239
}
240+
else
241+
{
242+
writeError = ::GetLastError();
243+
}
222244
} while (success && bytes_left_to_write);
223245

224246
NppParameters& nppParam = NppParameters::getInstance();
@@ -234,11 +256,11 @@ bool Win32_IO_File::write(const void *wbuf, size_t buf_size)
234256

235257
std::string msg = _path;
236258
msg += " written failed: ";
237-
std::wstring lastErrorMsg = GetLastErrorAsString(::GetLastError());
259+
std::wstring lastErrorMsg = GetLastErrorAsString(writeError);
238260
msg += wstring2string(lastErrorMsg, CP_UTF8);
239261
writeLog(nppIssueLog.c_str(), msg.c_str());
240262
}
241-
263+
_dwErrorCode = writeError; // store
242264
return false;
243265
}
244266
else

PowerEditor/src/MISC/Common/FileInterface.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ class Win32_IO_File final
4848
return write(str.c_str(), str.length());
4949
};
5050

51+
DWORD getLastErrorCode() {
52+
return _dwErrorCode;
53+
};
54+
5155
private:
5256
HANDLE _hFile {INVALID_HANDLE_VALUE};
5357
bool _written {false};
@@ -56,4 +60,6 @@ class Win32_IO_File final
5660
const DWORD _accessParam { GENERIC_READ | GENERIC_WRITE };
5761
const DWORD _shareParam { FILE_SHARE_READ | FILE_SHARE_WRITE };
5862
const DWORD _attribParam { FILE_ATTRIBUTE_NORMAL };
63+
64+
DWORD _dwErrorCode{ NO_ERROR };
5965
};

PowerEditor/src/NppIO.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -683,8 +683,11 @@ bool Notepad_plus::doSave(BufferID id, const wchar_t * filename, bool isCopy)
683683
}
684684
else if (res == SavingStatus::SaveWritingFailed)
685685
{
686-
wstring errorMessage = GetLastErrorAsString(GetLastError());
687-
::MessageBox(_pPublicInterface->getHSelf(), errorMessage.c_str(), L"Save failed", MB_OK | MB_ICONWARNING);
686+
if (!(NppParameters::getInstance()).isEndSessionCritical()) // can we report to the user?
687+
{
688+
wstring errorMessage = GetLastErrorAsString(::GetLastError());
689+
::MessageBox(_pPublicInterface->getHSelf(), errorMessage.c_str(), L"Save failed", MB_OK | MB_ICONWARNING);
690+
}
688691
}
689692
else if (res == SavingStatus::SaveOpenFailed)
690693
{

0 commit comments

Comments
 (0)