mirror of
https://github.com/notepad-plus-plus/notepad-plus-plus.git
synced 2025-09-21 17:08:12 +02:00
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 #886, fix #8655, fix #9561, fix #10302, fix #14990, fix #15008, fix #15137, fix #15323, close #16933
This commit is contained in:
parent
dc58d41359
commit
4b0fc8d316
@ -1537,7 +1537,25 @@ bool toggleReadOnlyFlagFromFileAttributes(const wchar_t* fileFullPath, bool& isC
|
||||
}
|
||||
else
|
||||
{
|
||||
// probably the ERROR_ACCESS_DENIED (5) (TODO: UAC-prompt candidate)
|
||||
if (::GetLastError() == ERROR_ACCESS_DENIED)
|
||||
{
|
||||
// try to set elevated
|
||||
// (notepad++.exe #UAC-SETFILEATTRIBUTES# attrib_flags_number_str dest_file_path)
|
||||
wstring strCmdLineParams = NPP_UAC_SETFILEATTRIBUTES_SIGN;
|
||||
strCmdLineParams += L" \"" + to_wstring(dwFileAttribs) + L"\" \"";
|
||||
strCmdLineParams += fileFullPath;
|
||||
strCmdLineParams += L"\"";
|
||||
DWORD dwNppUacOpError = invokeNppUacOp(strCmdLineParams);
|
||||
if (dwNppUacOpError == NO_ERROR)
|
||||
{
|
||||
isChangedToReadOnly = (dwFileAttribs & FILE_ATTRIBUTE_READONLY) != 0;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
::SetLastError(dwNppUacOpError); // set that as our current thread one for a possible reporting later
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -2154,3 +2172,40 @@ void ControlInfoTip::hide()
|
||||
}
|
||||
|
||||
#pragma warning(default:4996)
|
||||
|
||||
DWORD invokeNppUacOp(std::wstring& strCmdLineParams)
|
||||
{
|
||||
if ((strCmdLineParams.length() == 0) || (strCmdLineParams.length() > (USHRT_MAX / sizeof(WCHAR))))
|
||||
{
|
||||
// no cmdline or it exceeds the current max WinOS 32767 WCHARs
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
wchar_t wszNppFullPath[MAX_PATH]{};
|
||||
::SetLastError(NO_ERROR);
|
||||
if (!::GetModuleFileName(NULL, wszNppFullPath, MAX_PATH) || (::GetLastError() == ERROR_INSUFFICIENT_BUFFER))
|
||||
{
|
||||
return ::GetLastError();
|
||||
}
|
||||
|
||||
SHELLEXECUTEINFOW sei{};
|
||||
sei.cbSize = sizeof(SHELLEXECUTEINFOW);
|
||||
sei.lpVerb = L"runas"; // UAC prompt
|
||||
sei.nShow = SW_SHOWNORMAL;
|
||||
sei.fMask = SEE_MASK_NOCLOSEPROCESS; // sei.hProcess member receives the launched process handle
|
||||
sei.lpFile = wszNppFullPath;
|
||||
sei.lpParameters = strCmdLineParams.c_str();
|
||||
if (!::ShellExecuteExW(&sei))
|
||||
return ::GetLastError();
|
||||
|
||||
// wait for the elevated Notepad++ process to finish
|
||||
DWORD dwError = NO_ERROR;
|
||||
if (sei.hProcess) // beware - do not check here for the INVALID_HANDLE_VALUE (valid GetCurrentProcess() pseudohandle)
|
||||
{
|
||||
::WaitForSingleObject(sei.hProcess, INFINITE);
|
||||
::GetExitCodeProcess(sei.hProcess, &dwError);
|
||||
::CloseHandle(sei.hProcess);
|
||||
}
|
||||
|
||||
return dwError;
|
||||
}
|
||||
|
@ -331,3 +331,9 @@ private:
|
||||
ControlInfoTip(const ControlInfoTip&) = delete;
|
||||
ControlInfoTip& operator=(const ControlInfoTip&) = delete;
|
||||
};
|
||||
|
||||
|
||||
#define NPP_UAC_SAVE_SIGN L"#UAC-SAVE#"
|
||||
#define NPP_UAC_SETFILEATTRIBUTES_SIGN L"#UAC-SETFILEATTRIBUTES#"
|
||||
#define NPP_UAC_MOVEFILE_SIGN L"#UAC-MOVEFILE#"
|
||||
DWORD invokeNppUacOp(std::wstring& strCmdLineParams);
|
||||
|
@ -54,7 +54,10 @@ Win32_IO_File::Win32_IO_File(const wchar_t *fname)
|
||||
{
|
||||
bool isFromNetwork = PathIsNetworkPath(fname);
|
||||
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
|
||||
return; // In this case, we don't call createFile to prevent hanging
|
||||
{
|
||||
_dwErrorCode = ERROR_FILE_NOT_FOUND; // store
|
||||
return; // In this case, we don't call createFile to prevent hanging
|
||||
}
|
||||
}
|
||||
|
||||
_hFile = ::CreateFileW(fname, _accessParam, _shareParam, NULL, dispParam, _attribParam, NULL);
|
||||
@ -68,6 +71,9 @@ Win32_IO_File::Win32_IO_File(const wchar_t *fname)
|
||||
_hFile = ::CreateFileW(fname, _accessParam, _shareParam, NULL, dispParam, _attribParam, NULL);
|
||||
}
|
||||
|
||||
if (_hFile == INVALID_HANDLE_VALUE)
|
||||
_dwErrorCode = ::GetLastError(); // store
|
||||
|
||||
if (fileExists && (dispParam == CREATE_ALWAYS) && (_hFile != INVALID_HANDLE_VALUE))
|
||||
{
|
||||
// restore back the original creation date & attributes
|
||||
@ -91,7 +97,7 @@ Win32_IO_File::Win32_IO_File(const wchar_t *fname)
|
||||
else
|
||||
{
|
||||
msg += " failed to open, CreateFileW ErrorCode: ";
|
||||
msg += std::to_string(::GetLastError());
|
||||
msg += std::to_string(_dwErrorCode);
|
||||
}
|
||||
writeLog(nppIssueLog.c_str(), msg.c_str());
|
||||
}
|
||||
@ -100,11 +106,13 @@ Win32_IO_File::Win32_IO_File(const wchar_t *fname)
|
||||
|
||||
void Win32_IO_File::close()
|
||||
{
|
||||
_dwErrorCode = NO_ERROR; // reset
|
||||
|
||||
if (isOpened())
|
||||
{
|
||||
NppParameters& nppParam = NppParameters::getInstance();
|
||||
|
||||
DWORD flushError = NOERROR;
|
||||
DWORD flushError = NO_ERROR;
|
||||
if (_written)
|
||||
{
|
||||
if (!::FlushFileBuffers(_hFile))
|
||||
@ -159,7 +167,14 @@ Please try using another storage and also check if your saved data is not corrup
|
||||
}
|
||||
}
|
||||
}
|
||||
::CloseHandle(_hFile);
|
||||
|
||||
_dwErrorCode = flushError; // store possible flushing error 1st
|
||||
|
||||
if (!::CloseHandle(_hFile))
|
||||
{
|
||||
if (!flushError)
|
||||
_dwErrorCode = ::GetLastError(); // store
|
||||
}
|
||||
|
||||
_hFile = INVALID_HANDLE_VALUE;
|
||||
|
||||
@ -194,6 +209,8 @@ Please try using another storage and also check if your saved data is not corrup
|
||||
|
||||
bool Win32_IO_File::write(const void *wbuf, size_t buf_size)
|
||||
{
|
||||
_dwErrorCode = NO_ERROR; // reset
|
||||
|
||||
if (!isOpened() || (wbuf == nullptr))
|
||||
return false;
|
||||
|
||||
@ -203,6 +220,7 @@ bool Win32_IO_File::write(const void *wbuf, size_t buf_size)
|
||||
size_t bytes_left_to_write = buf_size;
|
||||
|
||||
BOOL success = FALSE;
|
||||
DWORD writeError = NO_ERROR; // use also a local var here to be 100% thread-safe
|
||||
|
||||
do
|
||||
{
|
||||
@ -219,6 +237,10 @@ bool Win32_IO_File::write(const void *wbuf, size_t buf_size)
|
||||
bytes_left_to_write -= static_cast<size_t>(bytes_written);
|
||||
total_bytes_written += static_cast<size_t>(bytes_written);
|
||||
}
|
||||
else
|
||||
{
|
||||
writeError = ::GetLastError();
|
||||
}
|
||||
} while (success && bytes_left_to_write);
|
||||
|
||||
NppParameters& nppParam = NppParameters::getInstance();
|
||||
@ -234,11 +256,11 @@ bool Win32_IO_File::write(const void *wbuf, size_t buf_size)
|
||||
|
||||
std::string msg = _path;
|
||||
msg += " written failed: ";
|
||||
std::wstring lastErrorMsg = GetLastErrorAsString(::GetLastError());
|
||||
std::wstring lastErrorMsg = GetLastErrorAsString(writeError);
|
||||
msg += wstring2string(lastErrorMsg, CP_UTF8);
|
||||
writeLog(nppIssueLog.c_str(), msg.c_str());
|
||||
}
|
||||
|
||||
_dwErrorCode = writeError; // store
|
||||
return false;
|
||||
}
|
||||
else
|
||||
|
@ -48,6 +48,10 @@ public:
|
||||
return write(str.c_str(), str.length());
|
||||
};
|
||||
|
||||
DWORD getLastErrorCode() {
|
||||
return _dwErrorCode;
|
||||
};
|
||||
|
||||
private:
|
||||
HANDLE _hFile {INVALID_HANDLE_VALUE};
|
||||
bool _written {false};
|
||||
@ -56,4 +60,6 @@ private:
|
||||
const DWORD _accessParam { GENERIC_READ | GENERIC_WRITE };
|
||||
const DWORD _shareParam { FILE_SHARE_READ | FILE_SHARE_WRITE };
|
||||
const DWORD _attribParam { FILE_ATTRIBUTE_NORMAL };
|
||||
|
||||
DWORD _dwErrorCode{ NO_ERROR };
|
||||
};
|
||||
|
@ -683,8 +683,11 @@ bool Notepad_plus::doSave(BufferID id, const wchar_t * filename, bool isCopy)
|
||||
}
|
||||
else if (res == SavingStatus::SaveWritingFailed)
|
||||
{
|
||||
wstring errorMessage = GetLastErrorAsString(GetLastError());
|
||||
::MessageBox(_pPublicInterface->getHSelf(), errorMessage.c_str(), L"Save failed", MB_OK | MB_ICONWARNING);
|
||||
if (!(NppParameters::getInstance()).isEndSessionCritical()) // can we report to the user?
|
||||
{
|
||||
wstring errorMessage = GetLastErrorAsString(::GetLastError());
|
||||
::MessageBox(_pPublicInterface->getHSelf(), errorMessage.c_str(), L"Save failed", MB_OK | MB_ICONWARNING);
|
||||
}
|
||||
}
|
||||
else if (res == SavingStatus::SaveOpenFailed)
|
||||
{
|
||||
|
@ -65,6 +65,15 @@ namespace // anonymous
|
||||
return defvalue; // fallback unknown
|
||||
}
|
||||
|
||||
// local helper to get the current system time in milliseconds since Unix epoch (January 1, 1970)
|
||||
ULONGLONG GetUnixSysTimeInMilliseconds()
|
||||
{
|
||||
FILETIME ft;
|
||||
::GetSystemTimeAsFileTime(&ft); // 100-nanosecond intervals since January 1, 1601 (UTC)
|
||||
ULONGLONG ullTime = (((ULONGLONG)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
|
||||
const ULONGLONG EPOCH_DIFF = 116444736000000000ULL; // difference between Jan 1, 1601 and Jan 1, 1970 in 100-ns intervals
|
||||
return (ullTime - EPOCH_DIFF) / 10000; // subtract the diff and convert to milliseconds
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
using namespace std;
|
||||
@ -1093,13 +1102,31 @@ bool FileManager::deleteFile(BufferID id)
|
||||
}
|
||||
|
||||
|
||||
bool FileManager::moveFile(BufferID id, const wchar_t * newFileName)
|
||||
bool FileManager::moveFile(BufferID id, const wchar_t* newFileName)
|
||||
{
|
||||
Buffer* buf = getBufferByID(id);
|
||||
const wchar_t *fileNamePath = buf->getFullPathName();
|
||||
if (::MoveFileEx(fileNamePath, newFileName, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH) == 0)
|
||||
if (id == BUFFER_INVALID)
|
||||
return false;
|
||||
|
||||
Buffer* buf = getBufferByID(id);
|
||||
const wchar_t* fileNamePath = buf->getFullPathName();
|
||||
if (!::MoveFileExW(fileNamePath, newFileName, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH))
|
||||
{
|
||||
if (::GetLastError() != ERROR_ACCESS_DENIED)
|
||||
return false;
|
||||
|
||||
// ERROR_ACCESS_DENIED, try to move elevated
|
||||
// (notepad++.exe #UAC-MOVEFILE# original_file_path new_file_path)
|
||||
wstring strCmdLineParams = NPP_UAC_MOVEFILE_SIGN;
|
||||
strCmdLineParams += L" \"";
|
||||
strCmdLineParams += fileNamePath;
|
||||
strCmdLineParams += L"\" \"";
|
||||
strCmdLineParams += newFileName;
|
||||
strCmdLineParams += L"\"";
|
||||
DWORD dwNppUacOpError = invokeNppUacOp(strCmdLineParams);
|
||||
if (dwNppUacOpError != NO_ERROR)
|
||||
return false;
|
||||
}
|
||||
|
||||
buf->setFileName(newFileName);
|
||||
return true;
|
||||
}
|
||||
@ -1323,7 +1350,7 @@ SavingStatus FileManager::saveBuffer(BufferID id, const wchar_t* filename, bool
|
||||
Buffer* buffer = getBufferByID(id);
|
||||
bool isHiddenOrSys = false;
|
||||
|
||||
wchar_t fullpath[MAX_PATH] = { 0 };
|
||||
wchar_t fullpath[MAX_PATH]{};
|
||||
if (isWin32NamespacePrefixedFileName(filename))
|
||||
{
|
||||
// use directly the raw file name, skip the GetFullPathName WINAPI
|
||||
@ -1338,7 +1365,7 @@ SavingStatus FileManager::saveBuffer(BufferID id, const wchar_t* filename, bool
|
||||
}
|
||||
}
|
||||
|
||||
wchar_t dirDest[MAX_PATH];
|
||||
wchar_t dirDest[MAX_PATH]{};
|
||||
wcscpy_s(dirDest, MAX_PATH, fullpath);
|
||||
::PathRemoveFileSpecW(dirDest);
|
||||
|
||||
@ -1379,105 +1406,137 @@ SavingStatus FileManager::saveBuffer(BufferID id, const wchar_t* filename, bool
|
||||
|
||||
int encoding = buffer->getEncoding();
|
||||
|
||||
if (UnicodeConvertor.openFile(fullpath))
|
||||
wstring strTempFile = L"";
|
||||
if (!UnicodeConvertor.openFile(fullpath))
|
||||
{
|
||||
_pscratchTilla->execute(SCI_SETDOCPOINTER, 0, buffer->_doc); //generate new document
|
||||
if (NppParameters::getInstance().isEndSessionCritical())
|
||||
return SavingStatus::SaveOpenFailed; // cannot continue to the UAC-prompt at the Windows logoff/reboot/shutdown time
|
||||
|
||||
size_t lengthDoc = _pscratchTilla->getCurrentDocLen();
|
||||
char* buf = (char*)_pscratchTilla->execute(SCI_GETCHARACTERPOINTER); //to get characters directly from Scintilla buffer
|
||||
bool isWrittenSuccessful = false;
|
||||
if (UnicodeConvertor.getLastFileErrorState() != ERROR_ACCESS_DENIED)
|
||||
return SavingStatus::SaveOpenFailed; // cannot be solved by the UAC-prompt
|
||||
|
||||
if (encoding == -1) //no special encoding; can be handled directly by Utf8_16_Write
|
||||
{
|
||||
isWrittenSuccessful = UnicodeConvertor.writeFile(buf, lengthDoc);
|
||||
if (lengthDoc == 0)
|
||||
isWrittenSuccessful = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lengthDoc == 0)
|
||||
{
|
||||
isWrittenSuccessful = UnicodeConvertor.writeFile(buf, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
WcharMbcsConvertor& wmc = WcharMbcsConvertor::getInstance();
|
||||
size_t grabSize = 0;
|
||||
for (size_t i = 0; i < lengthDoc; i += grabSize)
|
||||
{
|
||||
grabSize = lengthDoc - i;
|
||||
if (grabSize > blockSize)
|
||||
grabSize = blockSize;
|
||||
// ERROR_ACCESS_DENIED, swap to temporary file copy for the UAC elevation way
|
||||
|
||||
int newDataLen = 0;
|
||||
int incompleteMultibyteChar = 0;
|
||||
const char* newData = wmc.encode(SC_CP_UTF8, encoding, buf + i, static_cast<int>(grabSize), &newDataLen, &incompleteMultibyteChar);
|
||||
grabSize -= incompleteMultibyteChar;
|
||||
isWrittenSuccessful = UnicodeConvertor.writeFile(newData, newDataLen);
|
||||
}
|
||||
}
|
||||
}
|
||||
wchar_t wszBuf[MAX_PATH + 1]{};
|
||||
if (::GetTempPath(MAX_PATH, wszBuf) == 0)
|
||||
return SavingStatus::SaveOpenFailed; // cannot continue
|
||||
|
||||
UnicodeConvertor.closeFile();
|
||||
strTempFile = wszBuf;
|
||||
strTempFile += L"npp-" + std::to_wstring(GetUnixSysTimeInMilliseconds()) + L".tmp"; // make unique temporary filename
|
||||
if (!UnicodeConvertor.openFile(strTempFile.c_str()))
|
||||
return SavingStatus::SaveOpenFailed; // cannot continue, weird
|
||||
}
|
||||
|
||||
// Error, we didn't write the entire document to disk.
|
||||
if (!isWrittenSuccessful)
|
||||
{
|
||||
_pscratchTilla->execute(SCI_SETDOCPOINTER, 0, _scratchDocDefault);
|
||||
return SavingStatus::SaveWritingFailed;
|
||||
}
|
||||
_pscratchTilla->execute(SCI_SETDOCPOINTER, 0, buffer->_doc); //generate new document
|
||||
|
||||
if (isHiddenOrSys)
|
||||
::SetFileAttributes(fullpath, attributes.dwFileAttributes);
|
||||
size_t lengthDoc = _pscratchTilla->getCurrentDocLen();
|
||||
char* buf = (char*)_pscratchTilla->execute(SCI_GETCHARACTERPOINTER); //to get characters directly from Scintilla buffer
|
||||
bool isWrittenSuccessful = false;
|
||||
|
||||
if (isCopy) // "Save a Copy As..." command
|
||||
{
|
||||
unsigned long MODEVENTMASK_ON = NppParameters::getInstance().getScintillaModEventMask();
|
||||
_pscratchTilla->execute(SCI_SETMODEVENTMASK, MODEVENTMASK_OFF);
|
||||
_pscratchTilla->execute(SCI_SETDOCPOINTER, 0, _scratchDocDefault);
|
||||
_pscratchTilla->execute(SCI_SETMODEVENTMASK, MODEVENTMASK_ON);
|
||||
return SavingStatus::SaveOK; //all done - we don't change the current buffer's path to "fullpath", since it's "Save a Copy As..." action.
|
||||
}
|
||||
|
||||
buffer->setFileName(fullpath);
|
||||
|
||||
// if not a large file and language is normal text (not defined)
|
||||
// we may try determine its language from its content
|
||||
if (!buffer->isLargeFile() && buffer->_lang == L_TEXT)
|
||||
{
|
||||
LangType detectedLang = detectLanguageFromTextBeginning((unsigned char*)buf, lengthDoc);
|
||||
|
||||
// if a language is detected from the content
|
||||
if (detectedLang != L_TEXT)
|
||||
{
|
||||
buffer->_lang = detectedLang;
|
||||
buffer->doNotify(BufferChangeFilename | BufferChangeTimestamp | BufferChangeLanguage);
|
||||
}
|
||||
}
|
||||
buffer->setDirty(false);
|
||||
buffer->setUnsync(false);
|
||||
buffer->setSavePointDirty(false);
|
||||
buffer->setStatus(DOC_REGULAR);
|
||||
buffer->checkFileState();
|
||||
|
||||
|
||||
_pscratchTilla->execute(SCI_SETSAVEPOINT);
|
||||
_pscratchTilla->execute(SCI_SETDOCPOINTER, 0, _scratchDocDefault);
|
||||
|
||||
wstring backupFilePath = buffer->getBackupFileName();
|
||||
if (!backupFilePath.empty())
|
||||
{
|
||||
// delete backup file
|
||||
buffer->setBackupFileName(wstring());
|
||||
::DeleteFile(backupFilePath.c_str());
|
||||
}
|
||||
|
||||
return SavingStatus::SaveOK;
|
||||
if (encoding == -1) //no special encoding; can be handled directly by Utf8_16_Write
|
||||
{
|
||||
isWrittenSuccessful = UnicodeConvertor.writeFile(buf, lengthDoc);
|
||||
if (lengthDoc == 0)
|
||||
isWrittenSuccessful = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return SavingStatus::SaveOpenFailed;
|
||||
if (lengthDoc == 0)
|
||||
{
|
||||
isWrittenSuccessful = UnicodeConvertor.writeFile(buf, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
WcharMbcsConvertor& wmc = WcharMbcsConvertor::getInstance();
|
||||
size_t grabSize = 0;
|
||||
for (size_t i = 0; i < lengthDoc; i += grabSize)
|
||||
{
|
||||
grabSize = lengthDoc - i;
|
||||
if (grabSize > blockSize)
|
||||
grabSize = blockSize;
|
||||
|
||||
int newDataLen = 0;
|
||||
int incompleteMultibyteChar = 0;
|
||||
const char* newData = wmc.encode(SC_CP_UTF8, encoding, buf + i, static_cast<int>(grabSize), &newDataLen, &incompleteMultibyteChar);
|
||||
grabSize -= incompleteMultibyteChar;
|
||||
isWrittenSuccessful = UnicodeConvertor.writeFile(newData, newDataLen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UnicodeConvertor.closeFile();
|
||||
|
||||
if (isHiddenOrSys && strTempFile.empty())
|
||||
::SetFileAttributes(fullpath, attributes.dwFileAttributes);
|
||||
|
||||
// Error, we didn't write the entire document to disk.
|
||||
if (!isWrittenSuccessful)
|
||||
{
|
||||
_pscratchTilla->execute(SCI_SETDOCPOINTER, 0, _scratchDocDefault);
|
||||
if (!strTempFile.empty())
|
||||
::DeleteFileW(strTempFile.c_str());
|
||||
return SavingStatus::SaveWritingFailed;
|
||||
}
|
||||
|
||||
if (!strTempFile.empty())
|
||||
{
|
||||
// elevated saving/overwriting of the original file by the help of the tempfile
|
||||
// (notepad++.exe #UAC-SAVE# temp_file_path dest_file_path)
|
||||
wstring strCmdLineParams = NPP_UAC_SAVE_SIGN;
|
||||
strCmdLineParams += L" \"" + strTempFile + L"\" \"";
|
||||
strCmdLineParams += fullpath;
|
||||
strCmdLineParams += L"\"";
|
||||
DWORD dwNppUacOpError = invokeNppUacOp(strCmdLineParams);
|
||||
if (dwNppUacOpError != NO_ERROR)
|
||||
{
|
||||
::DeleteFileW(strTempFile.c_str()); // ensure no failed op remnant
|
||||
::SetLastError(dwNppUacOpError); // set that as our current thread one for reporting later
|
||||
return SavingStatus::SaveWritingFailed;
|
||||
}
|
||||
}
|
||||
|
||||
if (isCopy) // "Save a Copy As..." command
|
||||
{
|
||||
unsigned long MODEVENTMASK_ON = NppParameters::getInstance().getScintillaModEventMask();
|
||||
_pscratchTilla->execute(SCI_SETMODEVENTMASK, MODEVENTMASK_OFF);
|
||||
_pscratchTilla->execute(SCI_SETDOCPOINTER, 0, _scratchDocDefault);
|
||||
_pscratchTilla->execute(SCI_SETMODEVENTMASK, MODEVENTMASK_ON);
|
||||
return SavingStatus::SaveOK; //all done - we don't change the current buffer's path to "fullpath", since it's "Save a Copy As..." action.
|
||||
}
|
||||
|
||||
buffer->setFileName(fullpath);
|
||||
|
||||
// if not a large file and language is normal text (not defined)
|
||||
// we may try determine its language from its content
|
||||
if (!buffer->isLargeFile() && buffer->_lang == L_TEXT)
|
||||
{
|
||||
LangType detectedLang = detectLanguageFromTextBeginning((unsigned char*)buf, lengthDoc);
|
||||
|
||||
// if a language is detected from the content
|
||||
if (detectedLang != L_TEXT)
|
||||
{
|
||||
buffer->_lang = detectedLang;
|
||||
buffer->doNotify(BufferChangeFilename | BufferChangeTimestamp | BufferChangeLanguage);
|
||||
}
|
||||
}
|
||||
buffer->setDirty(false);
|
||||
buffer->setUnsync(false);
|
||||
buffer->setSavePointDirty(false);
|
||||
buffer->setStatus(DOC_REGULAR);
|
||||
buffer->checkFileState();
|
||||
|
||||
_pscratchTilla->execute(SCI_SETSAVEPOINT);
|
||||
_pscratchTilla->execute(SCI_SETDOCPOINTER, 0, _scratchDocDefault);
|
||||
|
||||
wstring backupFilePath = buffer->getBackupFileName();
|
||||
if (!backupFilePath.empty())
|
||||
{
|
||||
// delete backup file
|
||||
buffer->setBackupFileName(wstring());
|
||||
::DeleteFile(backupFilePath.c_str());
|
||||
}
|
||||
|
||||
return SavingStatus::SaveOK;
|
||||
}
|
||||
|
||||
size_t FileManager::nextUntitledNewNumber() const
|
||||
|
@ -292,6 +292,7 @@ Utf8_16_Write::Utf8_16_Write()
|
||||
m_pNewBuf = NULL;
|
||||
m_bFirstWrite = true;
|
||||
m_nBufSize = 0;
|
||||
m_dwLastFileError = NO_ERROR;
|
||||
}
|
||||
|
||||
Utf8_16_Write::~Utf8_16_Write()
|
||||
@ -301,11 +302,14 @@ Utf8_16_Write::~Utf8_16_Write()
|
||||
|
||||
bool Utf8_16_Write::openFile(const wchar_t *name)
|
||||
{
|
||||
m_pFile = std::make_unique<Win32_IO_File>(name);
|
||||
m_dwLastFileError = NO_ERROR;
|
||||
|
||||
m_pFile = std::make_unique<Win32_IO_File>(name);
|
||||
if (!m_pFile)
|
||||
return false;
|
||||
|
||||
m_dwLastFileError = m_pFile->getLastErrorCode();
|
||||
|
||||
if (!m_pFile->isOpened())
|
||||
{
|
||||
m_pFile = nullptr;
|
||||
@ -319,78 +323,85 @@ bool Utf8_16_Write::openFile(const wchar_t *name)
|
||||
|
||||
bool Utf8_16_Write::writeFile(const void* p, size_t _size)
|
||||
{
|
||||
// no file open
|
||||
m_dwLastFileError = NO_ERROR;
|
||||
|
||||
// no file open
|
||||
if (!m_pFile)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_bFirstWrite)
|
||||
{
|
||||
switch (m_eEncoding)
|
||||
{
|
||||
case uniUTF8:
|
||||
{
|
||||
switch (m_eEncoding)
|
||||
{
|
||||
case uniUTF8:
|
||||
{
|
||||
if (!m_pFile->write(k_Boms[m_eEncoding], 3))
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case uni16BE:
|
||||
case uni16LE:
|
||||
{
|
||||
if (!m_pFile->write(k_Boms[m_eEncoding], 2))
|
||||
if (!m_pFile->write(k_Boms[m_eEncoding], 3))
|
||||
{
|
||||
m_dwLastFileError = m_pFile->getLastErrorCode();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
case uni16BE:
|
||||
case uni16LE:
|
||||
{
|
||||
if (!m_pFile->write(k_Boms[m_eEncoding], 2))
|
||||
{
|
||||
m_dwLastFileError = m_pFile->getLastErrorCode();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
// nothing to do
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_bFirstWrite = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool isOK = false;
|
||||
bool isOK = false;
|
||||
|
||||
switch (m_eEncoding)
|
||||
{
|
||||
switch (m_eEncoding)
|
||||
{
|
||||
case uni7Bit:
|
||||
case uni8Bit:
|
||||
case uniCookie:
|
||||
case uniUTF8:
|
||||
case uni8Bit:
|
||||
case uniCookie:
|
||||
case uniUTF8:
|
||||
{
|
||||
// Normal write
|
||||
// Normal write
|
||||
if (m_pFile->write(p, _size))
|
||||
isOK = true;
|
||||
|
||||
}
|
||||
m_dwLastFileError = m_pFile->getLastErrorCode();
|
||||
}
|
||||
break;
|
||||
|
||||
case uni16BE_NoBOM:
|
||||
case uni16LE_NoBOM:
|
||||
case uni16BE:
|
||||
case uni16LE:
|
||||
case uni16BE_NoBOM:
|
||||
case uni16LE_NoBOM:
|
||||
case uni16BE:
|
||||
case uni16LE:
|
||||
{
|
||||
static const unsigned int bufSize = 64*1024;
|
||||
static const unsigned int bufSize = 64 * 1024;
|
||||
utf16* buf = new utf16[bufSize];
|
||||
|
||||
Utf8_Iter iter8;
|
||||
iter8.set(static_cast<const ubyte*>(p), _size, m_eEncoding);
|
||||
Utf8_Iter iter8;
|
||||
iter8.set(static_cast<const ubyte*>(p), _size, m_eEncoding);
|
||||
|
||||
unsigned int bufIndex = 0;
|
||||
while (iter8)
|
||||
{
|
||||
++iter8;
|
||||
while ((bufIndex < bufSize) && iter8.canGet())
|
||||
iter8.get(&buf [bufIndex++]);
|
||||
iter8.get(&buf[bufIndex++]);
|
||||
|
||||
if (bufIndex == bufSize || !iter8)
|
||||
{
|
||||
if (!m_pFile->write(buf, bufIndex * sizeof(utf16)))
|
||||
{
|
||||
m_dwLastFileError = m_pFile->getLastErrorCode();
|
||||
delete[] buf;
|
||||
return 0;
|
||||
}
|
||||
@ -399,15 +410,14 @@ bool Utf8_16_Write::writeFile(const void* p, size_t _size)
|
||||
}
|
||||
isOK = true;
|
||||
delete[] buf;
|
||||
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return isOK;
|
||||
return isOK;
|
||||
}
|
||||
|
||||
|
||||
@ -506,7 +516,15 @@ void Utf8_16_Write::closeFile()
|
||||
}
|
||||
|
||||
if (m_pFile)
|
||||
{
|
||||
m_pFile->close(); // explicit closing for getting the possible flushing/closing error code
|
||||
m_dwLastFileError = m_pFile->getLastErrorCode();
|
||||
m_pFile = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_dwLastFileError = NO_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -148,11 +148,14 @@ public:
|
||||
size_t convert(char* p, size_t _size);
|
||||
char* getNewBuf() { return reinterpret_cast<char*>(m_pNewBuf); }
|
||||
|
||||
DWORD getLastFileErrorState() { return m_dwLastFileError; }
|
||||
|
||||
protected:
|
||||
UniMode m_eEncoding;
|
||||
std::unique_ptr<Win32_IO_File> m_pFile;
|
||||
ubyte* m_pNewBuf;
|
||||
size_t m_nBufSize;
|
||||
bool m_bFirstWrite;
|
||||
DWORD m_dwLastFileError;
|
||||
};
|
||||
|
||||
|
@ -1077,7 +1077,7 @@ wstring CustomFileDialog::doSaveDlg()
|
||||
|
||||
CurrentDirBackup backup;
|
||||
|
||||
_impl->addFlags(FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_FORCEFILESYSTEM);
|
||||
_impl->addFlags(FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_FORCEFILESYSTEM | FOS_NOTESTFILECREATE);
|
||||
bool bOk = _impl->show();
|
||||
return bOk ? _impl->getResultFilename() : L"";
|
||||
}
|
||||
|
@ -408,6 +408,87 @@ bool launchUpdater(const std::wstring& updaterFullPath, const std::wstring& upda
|
||||
return true;
|
||||
}
|
||||
|
||||
DWORD nppUacSave(const wchar_t* wszTempFilePath, const wchar_t* wszProtectedFilePath2Save)
|
||||
{
|
||||
if ((lstrlenW(wszTempFilePath) == 0) || (lstrlenW(wszProtectedFilePath2Save) == 0)) // safe check (lstrlen returns 0 for possible nullptr)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
if (!doesFileExist(wszTempFilePath))
|
||||
return ERROR_FILE_NOT_FOUND;
|
||||
|
||||
DWORD dwRetCode = ERROR_SUCCESS;
|
||||
|
||||
bool isOutputReadOnly = false;
|
||||
bool isOutputHidden = false;
|
||||
bool isOutputSystem = false;
|
||||
WIN32_FILE_ATTRIBUTE_DATA attributes{};
|
||||
attributes.dwFileAttributes = INVALID_FILE_ATTRIBUTES;
|
||||
if (getFileAttributesExWithTimeout(wszProtectedFilePath2Save, &attributes))
|
||||
{
|
||||
if (attributes.dwFileAttributes != INVALID_FILE_ATTRIBUTES && !(attributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
|
||||
{
|
||||
isOutputReadOnly = (attributes.dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0;
|
||||
isOutputHidden = (attributes.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0;
|
||||
isOutputSystem = (attributes.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) != 0;
|
||||
if (isOutputReadOnly) attributes.dwFileAttributes &= ~FILE_ATTRIBUTE_READONLY;
|
||||
if (isOutputHidden) attributes.dwFileAttributes &= ~FILE_ATTRIBUTE_HIDDEN;
|
||||
if (isOutputSystem) attributes.dwFileAttributes &= ~FILE_ATTRIBUTE_SYSTEM;
|
||||
if (isOutputReadOnly || isOutputHidden || isOutputSystem)
|
||||
::SetFileAttributes(wszProtectedFilePath2Save, attributes.dwFileAttributes); // temporarily remove the problematic ones
|
||||
}
|
||||
}
|
||||
|
||||
// cannot use simple MoveFile here as it retains the tempfile permissions when on the same volume...
|
||||
if (!::CopyFileW(wszTempFilePath, wszProtectedFilePath2Save, FALSE))
|
||||
{
|
||||
// fails if the destination file exists and has the R/O and/or Hidden attribute set
|
||||
dwRetCode = ::GetLastError();
|
||||
}
|
||||
else
|
||||
{
|
||||
// ok, now dispose of the tempfile used
|
||||
::DeleteFileW(wszTempFilePath);
|
||||
}
|
||||
|
||||
// set back the possible original file attributes
|
||||
if (isOutputReadOnly || isOutputHidden || isOutputSystem)
|
||||
{
|
||||
if (isOutputReadOnly) attributes.dwFileAttributes |= FILE_ATTRIBUTE_READONLY;
|
||||
if (isOutputHidden) attributes.dwFileAttributes |= FILE_ATTRIBUTE_HIDDEN;
|
||||
if (isOutputSystem) attributes.dwFileAttributes |= FILE_ATTRIBUTE_SYSTEM;
|
||||
::SetFileAttributes(wszProtectedFilePath2Save, attributes.dwFileAttributes);
|
||||
}
|
||||
|
||||
return dwRetCode;
|
||||
}
|
||||
|
||||
DWORD nppUacSetFileAttributes(const DWORD dwFileAttribs, const wchar_t* wszFilePath)
|
||||
{
|
||||
if (lstrlenW(wszFilePath) == 0) // safe check (lstrlen returns 0 for possible nullptr)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
if (!doesFileExist(wszFilePath))
|
||||
return ERROR_FILE_NOT_FOUND;
|
||||
if (dwFileAttribs == INVALID_FILE_ATTRIBUTES || (dwFileAttribs & FILE_ATTRIBUTE_DIRECTORY))
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
|
||||
if (!::SetFileAttributes(wszFilePath, dwFileAttribs))
|
||||
return ::GetLastError();
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
DWORD nppUacMoveFile(const wchar_t* wszOriginalFilePath, const wchar_t* wszNewFilePath)
|
||||
{
|
||||
if ((lstrlenW(wszOriginalFilePath) == 0) || (lstrlenW(wszNewFilePath) == 0)) // safe check (lstrlen returns 0 for possible nullptr)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
if (!doesFileExist(wszOriginalFilePath))
|
||||
return ERROR_FILE_NOT_FOUND;
|
||||
|
||||
if (!::MoveFileEx(wszOriginalFilePath, wszNewFilePath, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH))
|
||||
return ::GetLastError();
|
||||
else
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
@ -418,6 +499,39 @@ int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE /*hPrevInstance
|
||||
{
|
||||
g_nppStartTimePoint = std::chrono::steady_clock::now();
|
||||
|
||||
// Notepad++ UAC OPS /////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if ((lstrlenW(pCmdLine) > 0) && (__argc >= 2)) // safe (if pCmdLine is NULL, lstrlen returns 0)
|
||||
{
|
||||
const wchar_t* wszNppUacOpSign = __wargv[1];
|
||||
if (lstrlenW(wszNppUacOpSign) > lstrlenW(L"#UAC-#"))
|
||||
{
|
||||
if ((__argc == 4) && (wcscmp(wszNppUacOpSign, NPP_UAC_SAVE_SIGN) == 0))
|
||||
{
|
||||
// __wargv[x]: 2 ... tempFilePath, 3 ... protectedFilePath2Save
|
||||
return static_cast<int>(nppUacSave(__wargv[2], __wargv[3]));
|
||||
}
|
||||
|
||||
if ((__argc == 4) && (wcscmp(wszNppUacOpSign, NPP_UAC_SETFILEATTRIBUTES_SIGN) == 0))
|
||||
{
|
||||
// __wargv[x]: 2 ... dwFileAttributes (string), 3 ... filePath
|
||||
try
|
||||
{
|
||||
return static_cast<int>(nppUacSetFileAttributes(static_cast<DWORD>(std::stoul(std::wstring(__wargv[2]))), __wargv[3]));
|
||||
}
|
||||
catch ([[maybe_unused]] const std::exception& e)
|
||||
{
|
||||
return static_cast<int>(ERROR_INVALID_PARAMETER); // conversion error (check e.what() for details)
|
||||
}
|
||||
}
|
||||
|
||||
if ((__argc == 4) && (wcscmp(wszNppUacOpSign, NPP_UAC_MOVEFILE_SIGN) == 0))
|
||||
{
|
||||
// __wargv[x]: 2 ... originalFilePath, 3 ... newFilePath
|
||||
return static_cast<int>(nppUacMoveFile(__wargv[2], __wargv[3]));
|
||||
}
|
||||
}
|
||||
} // Notepad++ UAC OPS////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool TheFirstOne = true;
|
||||
::SetLastError(NO_ERROR);
|
||||
::CreateMutex(NULL, false, L"nppInstance");
|
||||
|
Loading…
x
Reference in New Issue
Block a user