Skip to content

Commit

Permalink
Supports 'Save As' functionality (#17)
Browse files Browse the repository at this point in the history
Supports 'Save As' functionality
  • Loading branch information
Hsilgos authored Jun 16, 2019
1 parent 23ab03b commit ba46f4b
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 52 deletions.
26 changes: 4 additions & 22 deletions NppSaveAsAdmin/src/plugin/NppSaveAsAdmin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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<wchar_t> 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:
Expand All @@ -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;
}

Expand Down
20 changes: 15 additions & 5 deletions NppSaveAsAdminLib/plugin/Injection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,24 @@ class ScopedInjector {
std::function<Ret(Args...)> callback) {
static StaticFunctionWrapper<Unique> 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() {
Expand Down
33 changes: 18 additions & 15 deletions NppSaveAsAdminLib/plugin/SaveAsAdminImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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<FileHandle> new_handle = std::make_unique<FileHandle>();
new_handle->pipe_sender = Pipe::create_unique();
new_handle->pipe_receiver = Pipe::create_unique();
Expand Down Expand Up @@ -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()) {
Expand All @@ -131,7 +133,7 @@ class SaveAsAdminImpl::Impl {

private:
AdminAccessRunner& m_admin_access_runner;
std::set<std::wstring> m_allowed_to_process_files;
bool m_is_process_allowed = false;

struct FileHandle {
std::unique_ptr<Pipe> pipe_sender;
Expand All @@ -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:
Expand Down Expand Up @@ -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();
}
4 changes: 2 additions & 2 deletions NppSaveAsAdminLib/plugin/SaveAsAdminImpl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 13 additions & 7 deletions UnitTests/TestSaveAsAdminImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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();
Expand All @@ -247,17 +253,17 @@ 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 });
EXPECT_CALL(mock_admin_access_runner, run_admin_access(_, _, _)).Times(0);

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);
Expand Down

0 comments on commit ba46f4b

Please sign in to comment.