Skip to content

Commit

Permalink
Further refactor of Windows hook procedure processing
Browse files Browse the repository at this point in the history
This commit makes the HookProcessor class more idiomatic to C++.
  • Loading branch information
jimevans committed Jun 23, 2015
1 parent 1f22ce2 commit b5728fc
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 41 deletions.
12 changes: 9 additions & 3 deletions cpp/iedriver/CommandHandlers/ScreenshotCommandHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,14 @@ class ScreenshotCommandHandler : public IECommandHandler {
::ShowWindow(ie_window_handle, SW_SHOWNORMAL);
}

HookProcessor hook(ie_window_handle);
hook.InstallWindowsHook("ScreenshotWndProc", WH_CALLWNDPROC);
HookSettings hook_settings;
hook_settings.window_handle = ie_window_handle;
hook_settings.hook_procedure_name = "ScreenshotWndProc";
hook_settings.hook_procedure_type = WH_CALLWNDPROC;
hook_settings.communication_type = OneWay;

HookProcessor hook;
hook.Initialize(hook_settings);
hook.PushData(sizeof(max_image_dimensions), &max_image_dimensions);
browser->SetWidth(max_image_dimensions.right);
browser->SetHeight(max_image_dimensions.bottom);
Expand All @@ -194,7 +200,7 @@ class ScreenshotCommandHandler : public IECommandHandler {
LOG(WARN) << "PrintWindow API is not able to get content window screenshot";
}

hook.UninstallWindowsHook();
hook.Dispose();

// Restore the browser to the original dimensions.
if (is_maximized) {
Expand Down
172 changes: 166 additions & 6 deletions cpp/iedriver/HookProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,15 @@
// limitations under the License.

#include "HookProcessor.h"
#include <ctime>
#include <vector>
#include <Sddl.h>
#include "logging.h"

#define MAX_BUFFER_SIZE 32768
#define NAMED_PIPE_BUFFER_SIZE 1024
#define LOW_INTEGRITY_SDDL_SACL L"S:(ML;;NW;;;LW)"
#define PIPE_CONNECTION_TIMEOUT_IN_MILLISECONDS 5000

// Define a shared data segment. Variables in this segment can be
// shared across processes that load this DLL.
Expand Down Expand Up @@ -66,11 +72,75 @@ class CopyDataHolderWindow : public CWindowImpl<CopyDataHolderWindow> {
}
};

HookProcessor::HookProcessor(HWND window_handle) {
this->window_handle_ = window_handle;
HookProcessor::HookProcessor() {
this->window_handle_ = NULL;
this->hook_procedure_handle_ = NULL;
this->pipe_handle_ = NULL;
this->communication_type_ = OneWay;
}

HookProcessor::~HookProcessor(void) {
HookProcessor::~HookProcessor() {
this->Dispose();
}

void HookProcessor::Initialize(const std::string& hook_procedure_name,
const int hook_procedure_type) {
HookSettings hook_settings;
hook_settings.hook_procedure_name = hook_procedure_name;
hook_settings.hook_procedure_type = hook_procedure_type;
hook_settings.window_handle = NULL;
hook_settings.communication_type = OneWay;
this->Initialize(hook_settings);
}

void HookProcessor::Initialize(const HookSettings& settings) {
this->window_handle_ = settings.window_handle;
this->pipe_handle_ = INVALID_HANDLE_VALUE;
this->communication_type_ = settings.communication_type;
if (settings.communication_type == TwoWay) {
this->CreateReturnPipe();
}
bool is_hook_installed = this->InstallWindowsHook(settings.hook_procedure_name,
settings.hook_procedure_type);
}

void HookProcessor::CreateReturnPipe() {
// Set security descriptor so low-integrity processes can write to it.
PSECURITY_DESCRIPTOR pointer_to_descriptor;
::ConvertStringSecurityDescriptorToSecurityDescriptor(LOW_INTEGRITY_SDDL_SACL,
SDDL_REVISION_1,
&pointer_to_descriptor,
NULL);
SECURITY_ATTRIBUTES security_attributes;
security_attributes.lpSecurityDescriptor = pointer_to_descriptor;
security_attributes.nLength = sizeof(security_attributes);
this->pipe_handle_ = ::CreateNamedPipe(L"\\\\.\\pipe\\nhsupspipe",
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
NAMED_PIPE_BUFFER_SIZE,
0,
0,
&security_attributes);

// failed to create pipe?
if (this->pipe_handle_ == INVALID_HANDLE_VALUE) {
LOG(WARN) << "Failed to create named pipe. Communication back from browser will not work.";
}
}

void HookProcessor::Dispose() {
ClearBuffer();

if (this->pipe_handle_ != NULL) {
::CloseHandle(this->pipe_handle_);
this->pipe_handle_ = NULL;
}

if (this->hook_procedure_handle_ != NULL) {
this->UninstallWindowsHook();
this->hook_procedure_handle_ = NULL;
}
}

bool HookProcessor::InstallWindowsHook(const std::string& hook_proc_name,
Expand All @@ -94,9 +164,9 @@ bool HookProcessor::InstallWindowsHook(const std::string& hook_proc_name,
thread_id = ::GetWindowThreadProcessId(this->window_handle_, NULL);
}
this->hook_procedure_handle_ = ::SetWindowsHookEx(hook_proc_type,
hook_procedure,
instance_handle,
thread_id);
hook_procedure,
instance_handle,
thread_id);
if (this->hook_procedure_handle_ == NULL) {
LOGERR(WARN) << "Unable to set windows hook";
return false;
Expand All @@ -108,6 +178,7 @@ void HookProcessor::UninstallWindowsHook() {
LOG(TRACE) << "Entering HookProcessor::UninstallWindowsHook";
if (this->hook_procedure_handle_ != NULL) {
::UnhookWindowsHookEx(this->hook_procedure_handle_);
::CloseHandle(this->hook_procedure_handle_);
}
}

Expand All @@ -134,6 +205,40 @@ bool HookProcessor::PushData(int data_size,
return true;
}

bool HookProcessor::PushData(const std::wstring& data) {
std::wstring mutable_data = data;
return this->PushData(static_cast<int>(mutable_data.size() * sizeof(wchar_t)), &mutable_data[0]);
}

int HookProcessor::PullData(std::vector<char>* data) {
std::vector<char> buffer(NAMED_PIPE_BUFFER_SIZE);

// Wait for the client to connect; if it succeeds,
// the function returns a nonzero value. If the function
// returns zero, GetLastError returns ERROR_PIPE_CONNECTED.
bool is_connected = ::ConnectNamedPipe(this->pipe_handle_, NULL) ? true : (::GetLastError() == ERROR_PIPE_CONNECTED);
if (is_connected) {
unsigned long bytes_read = 0;
bool is_read_successful = ::ReadFile(this->pipe_handle_,
&buffer[0],
NAMED_PIPE_BUFFER_SIZE,
&bytes_read,
NULL);
while (!is_read_successful && ERROR_MORE_DATA == ::GetLastError()) {
data->insert(data->end(), buffer.begin(), buffer.begin() + bytes_read);
is_read_successful = ::ReadFile(this->pipe_handle_,
&buffer[0],
NAMED_PIPE_BUFFER_SIZE,
&bytes_read,
NULL);
}
if (is_read_successful) {
data->insert(data->end(), buffer.begin(), buffer.begin() + bytes_read);
}
}
return static_cast<int>(data->size());
}

int HookProcessor::GetDataBufferSize() {
return data_buffer_size;
}
Expand Down Expand Up @@ -163,10 +268,65 @@ void HookProcessor::CopyDataFromBuffer(int destination_data_size, void* destinat
ClearBuffer();
}

void HookProcessor::CopyWStringToBuffer(const std::wstring& data) {
std::wstring mutable_data = data;
int mutable_data_size = static_cast<int>(mutable_data.size() * sizeof(wchar_t));
CopyDataToBuffer(mutable_data_size, &mutable_data[0]);
}

std::wstring HookProcessor::CopyWStringFromBuffer() {
// Allocate a buffer of wchar_t the length of the data in the
// shared memory buffer, plus one extra wide char, so that we
// can null terminate.
int local_buffer_size = GetDataBufferSize() + sizeof(wchar_t);
std::vector<wchar_t> local_buffer(local_buffer_size / sizeof(wchar_t));

// Copy the data from the shared memory buffer, and force
// a terminating null char into the local vector, then
// convert to wstring.
CopyDataFromBuffer(local_buffer_size, &local_buffer[0]);
local_buffer[local_buffer.size() - 1] = L'\0';
std::wstring data = &local_buffer[0];
return data;
}

void HookProcessor::ClearBuffer() {
// Zero out the shared buffer
data_buffer_size = MAX_BUFFER_SIZE;
memset(data_buffer, 0, MAX_BUFFER_SIZE);
}

void HookProcessor::WriteDataToPipe(const int process_id, const int data_size, void* data) {
HANDLE pipe_handle = INVALID_HANDLE_VALUE;

// Create the named pipe handle. Retry up until a timeout to give
// the driver end of the pipe time to start listening for a connection.
BOOL is_pipe_available = ::WaitNamedPipe(L"\\\\.\\pipe\\nhsupspipe", PIPE_CONNECTION_TIMEOUT_IN_MILLISECONDS);
if (is_pipe_available) {
pipe_handle = ::CreateFile(L"\\\\.\\pipe\\nhsupspipe",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL);
DWORD last_error = ::GetLastError();
}

// if everything ok set mode to message mode
if (INVALID_HANDLE_VALUE != pipe_handle) {
DWORD pipe_mode = PIPE_READMODE_MESSAGE;
// if this fails bail out
if (::SetNamedPipeHandleState(pipe_handle, &pipe_mode, NULL, NULL)) {
unsigned long bytes_written = 0;
bool is_write_successful = ::WriteFile(pipe_handle,
data,
data_size,
&bytes_written,
NULL);
}
::CloseHandle(pipe_handle);
}
}

} // namespace webdriver
42 changes: 36 additions & 6 deletions cpp/iedriver/HookProcessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,55 @@

namespace webdriver {

enum HookCommunicationType {
OneWay = 0,
TwoWay = 1
};

struct HookSettings {
std::string hook_procedure_name;
int hook_procedure_type;
HookCommunicationType communication_type;
HWND window_handle;
};

class HookProcessor {
public:
HookProcessor(HWND window_handle);
~HookProcessor(void);
HookProcessor(void);
virtual ~HookProcessor(void);

static int GetDataBufferSize(void);
static void SetDataBufferSize(int size);
static void CopyDataToBuffer(int source_data_size, void* source);
static void CopyDataFromBuffer(int destination_data_size, void* destination);

bool InstallWindowsHook(const std::string& hook_proc_name,
const int hook_proc_type);
void UninstallWindowsHook(void);
static void CopyWStringToBuffer(const std::wstring& data);
static std::wstring CopyWStringFromBuffer(void);

static void WriteDataToPipe(const int process_id, const int data_size, void* data);

void Initialize(const HookSettings& settings);
void Initialize(const std::string& hook_proc_name, const int hook_proc_type);
void Dispose(void);

bool PushData(int data_size,
void* pointer_to_data);
bool PushData(const std::wstring& data);

int PullData(std::vector<char>* data);
int PullData(std::wstring data);

private:
static void ClearBuffer(void);

void CreateReturnPipe(void);
bool InstallWindowsHook(const std::string& hook_proc_name,
const int hook_proc_type);
void UninstallWindowsHook(void);

HookCommunicationType communication_type_;
HWND window_handle_;
HHOOK hook_procedure_handle_;
HANDLE pipe_handle_;
};

} // namespace webdriver
Expand Down
13 changes: 6 additions & 7 deletions cpp/iedriver/InputManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,11 @@ int InputManager::PerformInputSequence(BrowserHandle browser_wrapper, const Json
// the requireWindowFocus capability is turned on, and since SendInput is
// documented to not allow other input events to be interspersed into the
// input queue, the risk is hopefully minimized.
HookProcessor keyboard_hook(NULL);
keyboard_hook.InstallWindowsHook("KeyboardHookProc", WH_KEYBOARD);
HookProcessor keyboard_hook;
keyboard_hook.Initialize("KeyboardHookProc", WH_KEYBOARD);

HookProcessor mouse_hook(NULL);
mouse_hook.InstallWindowsHook("MouseHookProc", WH_MOUSE);
HookProcessor mouse_hook;
mouse_hook.Initialize("MouseHookProc", WH_MOUSE);

sent_event_count = ::SendInput(static_cast<UINT>(this->inputs_.size()), &this->inputs_[0], sizeof(INPUT));
LOG(DEBUG) << "Sent " << sent_event_count << " events via SendInput()";
Expand All @@ -159,9 +159,8 @@ int InputManager::PerformInputSequence(BrowserHandle browser_wrapper, const Json
LOG(DEBUG) << "Wait for input event processing returned " << success;

// We're done here, so uninstall the hooks, and reset the buffer size.
keyboard_hook.UninstallWindowsHook();
mouse_hook.UninstallWindowsHook();
HookProcessor::SetDataBufferSize(original_data_buffer_size);
keyboard_hook.Dispose();
mouse_hook.Dispose();

// A small sleep after all messages have been detected by an application
// event loop is appropriate here. This value (50 milliseconds is chosen
Expand Down
Loading

0 comments on commit b5728fc

Please sign in to comment.