diff --git a/PowerEditor/src/MISC/Common/Common.cpp b/PowerEditor/src/MISC/Common/Common.cpp index 1e7fe1274..4199fe604 100644 --- a/PowerEditor/src/MISC/Common/Common.cpp +++ b/PowerEditor/src/MISC/Common/Common.cpp @@ -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; +} diff --git a/PowerEditor/src/MISC/Common/Common.h b/PowerEditor/src/MISC/Common/Common.h index 47395796b..971d403db 100644 --- a/PowerEditor/src/MISC/Common/Common.h +++ b/PowerEditor/src/MISC/Common/Common.h @@ -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); diff --git a/PowerEditor/src/MISC/Common/FileInterface.cpp b/PowerEditor/src/MISC/Common/FileInterface.cpp index ddc657d23..eca19603c 100644 --- a/PowerEditor/src/MISC/Common/FileInterface.cpp +++ b/PowerEditor/src/MISC/Common/FileInterface.cpp @@ -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(bytes_written); total_bytes_written += static_cast(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 diff --git a/PowerEditor/src/MISC/Common/FileInterface.h b/PowerEditor/src/MISC/Common/FileInterface.h index 147c9d69f..678cf73f4 100644 --- a/PowerEditor/src/MISC/Common/FileInterface.h +++ b/PowerEditor/src/MISC/Common/FileInterface.h @@ -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 }; }; diff --git a/PowerEditor/src/NppIO.cpp b/PowerEditor/src/NppIO.cpp index ba68f7c92..e74466f46 100644 --- a/PowerEditor/src/NppIO.cpp +++ b/PowerEditor/src/NppIO.cpp @@ -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) { diff --git a/PowerEditor/src/ScintillaComponent/Buffer.cpp b/PowerEditor/src/ScintillaComponent/Buffer.cpp index 4d6a65dec..d8c9946a1 100644 --- a/PowerEditor/src/ScintillaComponent/Buffer.cpp +++ b/PowerEditor/src/ScintillaComponent/Buffer.cpp @@ -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(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(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 diff --git a/PowerEditor/src/Utf8_16.cpp b/PowerEditor/src/Utf8_16.cpp index 42ee64e74..861e42089 100644 --- a/PowerEditor/src/Utf8_16.cpp +++ b/PowerEditor/src/Utf8_16.cpp @@ -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(name); + m_dwLastFileError = NO_ERROR; + m_pFile = std::make_unique(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(p), _size, m_eEncoding); + + Utf8_Iter iter8; + iter8.set(static_cast(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; - } - return isOK; + default: + break; + } + + 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; + } } diff --git a/PowerEditor/src/Utf8_16.h b/PowerEditor/src/Utf8_16.h index 355ce36ed..75766dc72 100644 --- a/PowerEditor/src/Utf8_16.h +++ b/PowerEditor/src/Utf8_16.h @@ -148,11 +148,14 @@ public: size_t convert(char* p, size_t _size); char* getNewBuf() { return reinterpret_cast(m_pNewBuf); } + DWORD getLastFileErrorState() { return m_dwLastFileError; } + protected: UniMode m_eEncoding; std::unique_ptr m_pFile; ubyte* m_pNewBuf; size_t m_nBufSize; bool m_bFirstWrite; + DWORD m_dwLastFileError; }; diff --git a/PowerEditor/src/WinControls/OpenSaveFileDialog/CustomFileDialog.cpp b/PowerEditor/src/WinControls/OpenSaveFileDialog/CustomFileDialog.cpp index 8d4c4eb9a..611ad0cc9 100644 --- a/PowerEditor/src/WinControls/OpenSaveFileDialog/CustomFileDialog.cpp +++ b/PowerEditor/src/WinControls/OpenSaveFileDialog/CustomFileDialog.cpp @@ -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""; } diff --git a/PowerEditor/src/winmain.cpp b/PowerEditor/src/winmain.cpp index 9d2cffa33..5ec2c7ded 100644 --- a/PowerEditor/src/winmain.cpp +++ b/PowerEditor/src/winmain.cpp @@ -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 @@ -417,6 +498,39 @@ std::chrono::steady_clock::time_point g_nppStartTimePoint{}; int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE /*hPrevInstance*/, _In_ PWSTR pCmdLine, _In_ int /*nShowCmd*/) { 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(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(nppUacSetFileAttributes(static_cast(std::stoul(std::wstring(__wargv[2]))), __wargv[3])); + } + catch ([[maybe_unused]] const std::exception& e) + { + return static_cast(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(nppUacMoveFile(__wargv[2], __wargv[3])); + } + } + } // Notepad++ UAC OPS//////////////////////////////////////////////////////////////////////////////////////////// bool TheFirstOne = true; ::SetLastError(NO_ERROR);