From ba46f4be19d478e76f83c5c8c666aa6a9140ebd1 Mon Sep 17 00:00:00 2001 From: Yauheni Khnykin <4347948+Hsilgos@users.noreply.github.com> Date: Sun, 16 Jun 2019 17:18:51 +0200 Subject: [PATCH] Supports 'Save As' functionality (#17) Supports 'Save As' functionality --- NppSaveAsAdmin/src/plugin/NppSaveAsAdmin.cpp | 26 +++------------ NppSaveAsAdminLib/plugin/Injection.hpp | 20 +++++++++--- NppSaveAsAdminLib/plugin/SaveAsAdminImpl.cpp | 33 +++++++++++--------- NppSaveAsAdminLib/plugin/SaveAsAdminImpl.hpp | 4 +-- README.md | 2 +- UnitTests/TestSaveAsAdminImpl.cpp | 20 +++++++----- 6 files changed, 53 insertions(+), 52 deletions(-) diff --git a/NppSaveAsAdmin/src/plugin/NppSaveAsAdmin.cpp b/NppSaveAsAdmin/src/plugin/NppSaveAsAdmin.cpp index 06e0f93..138e722 100644 --- a/NppSaveAsAdmin/src/plugin/NppSaveAsAdmin.cpp +++ b/NppSaveAsAdmin/src/plugin/NppSaveAsAdmin.cpp @@ -28,7 +28,7 @@ void un_do_injection() { ////////////////////////////////////////////////////////////////////////// -BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID lpReserved) { +BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID /*lpReserved*/) { switch (reasonForCall) { case DLL_PROCESS_ATTACH: pluginInit(hModule); @@ -67,34 +67,16 @@ extern "C" __declspec(dllexport) FuncItem* getFuncsArray(int* nbF) { return funcItem; } -std::wstring current_file_path(SCNotification* notifyCode) { - int avar = SendMessage(nppData._nppHandle, NPPM_GETFULLPATHFROMBUFFERID, - (WPARAM)notifyCode->nmhdr.idFrom, (LPARAM)NULL); - - if (avar == 0) - return std::wstring(); - - std::vector theFullPath(avar + 1); - theFullPath[0] = 0; - avar = - SendMessage(nppData._nppHandle, NPPM_GETFULLPATHFROMBUFFERID, - (WPARAM)notifyCode->nmhdr.idFrom, (LPARAM)theFullPath.data()); - - std::wstring tPath = theFullPath.data(); - - return tPath; -} - extern "C" __declspec(dllexport) void beNotified(SCNotification* notifyCode) { switch (notifyCode->nmhdr.code) { case NPPN_FILEBEFORESAVE: if (m_save_as_admin_impl) { - m_save_as_admin_impl->allow_process_file(current_file_path(notifyCode)); + m_save_as_admin_impl->allow_process_file(); } break; case NPPN_FILESAVED: if (m_save_as_admin_impl) { - m_save_as_admin_impl->cancel_process_file(current_file_path(notifyCode)); + m_save_as_admin_impl->cancel_process_file(); } break; default: @@ -108,7 +90,7 @@ extern "C" __declspec(dllexport) void beNotified(SCNotification* notifyCode) { // messages : http://sourceforge.net/forum/forum.php?forum_id=482781 // extern "C" __declspec(dllexport) LRESULT - messageProc(UINT Message, WPARAM wParam, LPARAM lParam) { + messageProc(UINT /*Message*/, WPARAM /*wParam*/, LPARAM /*lParam*/) { return TRUE; } diff --git a/NppSaveAsAdminLib/plugin/Injection.hpp b/NppSaveAsAdminLib/plugin/Injection.hpp index dd4d234..9f09ed4 100644 --- a/NppSaveAsAdminLib/plugin/Injection.hpp +++ b/NppSaveAsAdminLib/plugin/Injection.hpp @@ -133,14 +133,24 @@ class ScopedInjector { std::function callback) { static StaticFunctionWrapper static_function; if (static_function.data.callback) { - throw std::logic_error( - "You can't use same injection macro to " - " inject same function more than once"); + std::stringstream descr; + descr << "You can't use same injection macro to "; + descr << " inject same function more than once."; + if (function) + descr << " Function:" << function; + if (module) + descr <<" in module: " << module; + throw std::logic_error(descr.str()); } static_function.data.callback = callback; m_data = &static_function.data; - static_function.data.injection_result = - process_injection(module, function, static_function.injected_function); + try { + static_function.data.injection_result = + process_injection(module, function, static_function.injected_function); + } catch (...) { + static_function.data.callback = nullptr; + throw; + } } ~ScopedInjector() { diff --git a/NppSaveAsAdminLib/plugin/SaveAsAdminImpl.cpp b/NppSaveAsAdminLib/plugin/SaveAsAdminImpl.cpp index 054436b..1e7c318 100644 --- a/NppSaveAsAdminLib/plugin/SaveAsAdminImpl.cpp +++ b/NppSaveAsAdminLib/plugin/SaveAsAdminImpl.cpp @@ -23,17 +23,16 @@ class SaveAsAdminImpl::Impl { GetFileType, make_injection_callback(*this, &Impl::get_file_type_hook)); m_close_handle = inject_in_module("Kernel32.dll", CloseHandle, make_injection_callback(*this, &Impl::close_handle_hook)); + m_get_save_file_name_w = inject_in_module("Comdlg32.dll", + GetSaveFileNameW, make_injection_callback(*this, &Impl::get_save_file_name_hook)); } - void allow_process_file(const std::wstring& filename) { - if (!filename.empty()) - m_allowed_to_process_files.insert(filename); + void allow_process_file() { + m_is_process_allowed = true; } - void cancel_process_file(const std::wstring& filename) { - auto it = m_allowed_to_process_files.find(filename); - if (it != m_allowed_to_process_files.end()) - m_allowed_to_process_files.erase(it); + void cancel_process_file() { + m_is_process_allowed = false; } private: @@ -68,9 +67,7 @@ class SaveAsAdminImpl::Impl { if (INVALID_HANDLE_VALUE == result && (desired_access & GENERIC_WRITE)) { const int error_code = GetLastError(); - if (ERROR_ACCESS_DENIED == error_code && - m_allowed_to_process_files.find(file_name) != - m_allowed_to_process_files.end()) { + if (ERROR_ACCESS_DENIED == error_code && m_is_process_allowed) { std::unique_ptr new_handle = std::make_unique(); new_handle->pipe_sender = Pipe::create_unique(); new_handle->pipe_receiver = Pipe::create_unique(); @@ -105,6 +102,11 @@ class SaveAsAdminImpl::Impl { return result; } + BOOL get_save_file_name_hook(LPOPENFILENAMEW ofn) { + ofn->Flags |= OFN_NOTESTFILECREATE; + return m_get_save_file_name_w->call_original(ofn); + } + BOOL close_handle_hook(HANDLE handle) { HandleMap::iterator it = m_file_handles.find(handle); if (it != m_file_handles.end()) { @@ -131,7 +133,7 @@ class SaveAsAdminImpl::Impl { private: AdminAccessRunner& m_admin_access_runner; - std::set m_allowed_to_process_files; + bool m_is_process_allowed = false; struct FileHandle { std::unique_ptr pipe_sender; @@ -147,6 +149,7 @@ class SaveAsAdminImpl::Impl { injection_ptr_type(CreateFileW) m_create_filew; injection_ptr_type(GetFileType) m_get_file_type; injection_ptr_type(CloseHandle) m_close_handle; + injection_ptr_type(GetSaveFileNameW) m_get_save_file_name_w; class DefaultOriginalFunctions : public IWinApiFunctions { public: @@ -213,10 +216,10 @@ SaveAsAdminImpl::SaveAsAdminImpl(AdminAccessRunner& admin_access_runner) SaveAsAdminImpl::~SaveAsAdminImpl() = default; -void SaveAsAdminImpl::allow_process_file(const std::wstring& filename) { - m_impl->allow_process_file(filename); +void SaveAsAdminImpl::allow_process_file() { + m_impl->allow_process_file(); } -void SaveAsAdminImpl::cancel_process_file(const std::wstring& filename) { - m_impl->allow_process_file(filename); +void SaveAsAdminImpl::cancel_process_file() { + m_impl->allow_process_file(); } diff --git a/NppSaveAsAdminLib/plugin/SaveAsAdminImpl.hpp b/NppSaveAsAdminLib/plugin/SaveAsAdminImpl.hpp index d8a20dc..989c690 100644 --- a/NppSaveAsAdminLib/plugin/SaveAsAdminImpl.hpp +++ b/NppSaveAsAdminLib/plugin/SaveAsAdminImpl.hpp @@ -21,8 +21,8 @@ class SaveAsAdminImpl { SaveAsAdminImpl(AdminAccessRunner& admin_access_runner); ~SaveAsAdminImpl(); - void allow_process_file(const std::wstring& filename); - void cancel_process_file(const std::wstring& filename); + void allow_process_file(); + void cancel_process_file(); private: class Impl; diff --git a/README.md b/README.md index 203cea7..2335788 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Notepad++ Plugin nppSaveAsAdmin Allows to save file as administrator with UAC prompt. ## Installation -Unpack to "%NOTEPAD-FOLDER-DIR%/plugins" to install +Unpack to "%APPDATA%/Notepad++/plugins" to install ## Build Status diff --git a/UnitTests/TestSaveAsAdminImpl.cpp b/UnitTests/TestSaveAsAdminImpl.cpp index 160de0c..33b51b5 100644 --- a/UnitTests/TestSaveAsAdminImpl.cpp +++ b/UnitTests/TestSaveAsAdminImpl.cpp @@ -18,8 +18,6 @@ const std::string TestFileNameA1 = "test_wfile1.txt"; const std::wstring TestFileNameW1 = L"test_wfile1.txt"; const std::string TestFileNameA1_Hooked = "test_wfile1.txt-hooked"; const std::wstring TestFileNameW1_Hooked = L"test_wfile1.txt-hooked"; -// const std::string HookedAPrefix = "hooked-"; -// const std::wstring HookedWPrefix = L"hooked-"; class MockAdminAccessRunner : public AdminAccessRunner { public: @@ -132,8 +130,16 @@ struct SaveAsAdminImplFixture : public ::testing::Test { MockAdminAccessRunner mock_admin_access_runner; SaveAsAdminImpl save_as_admin_impl; + void ensure_that_comdlg32_dll_is_imported() { + std::ifstream file("none_existing.file"); + if (file) { + GetSaveFileNameW(NULL); + } + } + SaveAsAdminImplFixture() : execution_thread(false), save_as_admin_impl(mock_admin_access_runner) { + ensure_that_comdlg32_dll_is_imported(); ON_CALL(mock_winapi_rename, create_file_a(_, _, _, _, _, _, _)) .WillByDefault( Invoke(this, &SaveAsAdminImplFixture::create_file_a_rename)); @@ -225,9 +231,9 @@ TEST_F(SaveAsAdminImplFixture, WriteFileIsHooked) { const char* TestData = "Test_data"; - save_as_admin_impl.allow_process_file(TestFileNameW1); + save_as_admin_impl.allow_process_file(); ProcessWriteFile(TestFileNameW1, TestData); - save_as_admin_impl.cancel_process_file(TestFileNameW1); + save_as_admin_impl.cancel_process_file(); check_file_not_exists(TestFileNameW1); original_functions.allow_create(); @@ -247,7 +253,7 @@ TEST_F(SaveAsAdminImplFixture, WriteFileIsHookedButNotProcessedBecauseNotAllowed check_file_not_exists(TestFileNameW1_Hooked); } -TEST_F(SaveAsAdminImplFixture, WriteFileIsHookedButNotProcessedBecaueNotNeeded) { +TEST_F(SaveAsAdminImplFixture, WriteFileIsHookedButNotProcessedBecauseNotNeeded) { original_functions.allow_create(); const auto remove_files = FileAutoremover({ TestFileNameA1, TestFileNameA1_Hooked }); @@ -255,9 +261,9 @@ TEST_F(SaveAsAdminImplFixture, WriteFileIsHookedButNotProcessedBecaueNotNeeded) const char* TestData = "Test_data"; - save_as_admin_impl.allow_process_file(TestFileNameW1); + save_as_admin_impl.allow_process_file(); ProcessWriteFile(TestFileNameW1, TestData); - save_as_admin_impl.cancel_process_file(TestFileNameW1); + save_as_admin_impl.cancel_process_file(); check_file_exists(TestFileNameW1, TestData); check_file_not_exists(TestFileNameW1_Hooked);