// Copyright 2012 Software Freedom Conservancy | |
// Licensed under the Apache License, Version 2.0 (the "License"); | |
// you may not use this file except in compliance with the License. | |
// You may obtain a copy of the License at | |
// | |
// http://www.apache.org/licenses/LICENSE-2.0 | |
// | |
// Unless required by applicable law or agreed to in writing, software | |
// distributed under the License is distributed on an "AS IS" BASIS, | |
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
// See the License for the specific language governing permissions and | |
// limitations under the License. | |
#include "stdafx.h" | |
#include "resource.h" | |
#include "IEServer.h" | |
#include "CommandLineArguments.h" | |
#include <algorithm> | |
#include <map> | |
#include <string> | |
#include <vector> | |
// TODO(JimEvans): Change the prototypes of these functions in the | |
// IEDriver project to match the prototype specified here. | |
typedef void* (__cdecl *STARTSERVEREXPROC)(int, const std::string&, const std::string&, const std::string&, const std::string&); | |
typedef void (__cdecl *STOPSERVERPROC)(void); | |
#define ERR_DLL_EXTRACT_FAIL 1 | |
#define ERR_DLL_LOAD_FAIL 2 | |
#define ERR_FUNCTION_NOT_FOUND 3 | |
#define ERR_SERVER_START 4 | |
#define RESOURCE_TYPE L"BINARY" | |
#define TEMP_FILE_PREFIX L"IEDriver" | |
#define START_SERVER_EX_API_NAME "StartServerEx" | |
#define STOP_SERVER_API_NAME "StopServer" | |
#define PORT_COMMAND_LINE_ARG "port" | |
#define HOST_COMMAND_LINE_ARG "host" | |
#define LOGLEVEL_COMMAND_LINE_ARG "log-level" | |
#define LOGFILE_COMMAND_LINE_ARG "log-file" | |
#define SILENT_COMMAND_LINE_ARG "silent" | |
#define EXTRACTPATH_COMMAND_LINE_ARG "extract-path" | |
#define BOOLEAN_COMMAND_LINE_ARG_MISSING_VALUE "value-not-specified" | |
bool ExtractResource(unsigned short resource_id, | |
const std::wstring& output_file_name) { | |
bool success = false; | |
try { | |
// First find and load the required resource | |
HRSRC resource_handle = ::FindResource(NULL, | |
MAKEINTRESOURCE(resource_id), | |
RESOURCE_TYPE); | |
HGLOBAL global_resouce_handle = ::LoadResource(NULL, resource_handle); | |
// Now open and map this to a disk file | |
LPVOID file_pointer = ::LockResource(global_resouce_handle); | |
DWORD resource_size = ::SizeofResource(NULL, resource_handle); | |
// Open the file and filemap | |
HANDLE file_handle = ::CreateFile(output_file_name.c_str(), | |
GENERIC_READ | GENERIC_WRITE, | |
0, | |
NULL, | |
CREATE_ALWAYS, | |
FILE_ATTRIBUTE_NORMAL, | |
NULL); | |
HANDLE file_mapping_handle = ::CreateFileMapping(file_handle, | |
NULL, | |
PAGE_READWRITE, | |
0, | |
resource_size, | |
NULL); | |
LPVOID base_address_pointer = ::MapViewOfFile(file_mapping_handle, | |
FILE_MAP_WRITE, | |
0, | |
0, | |
0); | |
// Write the file | |
::CopyMemory(base_address_pointer, file_pointer, resource_size); | |
// Unmap the file and close the handles | |
::UnmapViewOfFile(base_address_pointer); | |
::CloseHandle(file_mapping_handle); | |
::CloseHandle(file_handle); | |
success = true; | |
} catch(...) { | |
// Ignore all type of errors | |
} | |
return success; | |
} | |
std::string GetProcessArchitectureDescription() { | |
std::string arch_description = "32-bit"; | |
SYSTEM_INFO system_info; | |
::GetNativeSystemInfo(&system_info); | |
if (system_info.wProcessorArchitecture != 0) { | |
BOOL is_emulated; | |
HANDLE process_handle = ::GetCurrentProcess(); | |
::IsWow64Process(process_handle, &is_emulated); | |
if (!is_emulated) { | |
arch_description = "64-bit"; | |
} | |
::CloseHandle(process_handle); | |
} | |
return arch_description; | |
} | |
std::string GetExecutableVersion() { | |
struct LANGANDCODEPAGE { | |
WORD language; | |
WORD code_page; | |
} *lang_info; | |
// get the filename of the executable containing the version resource | |
std::vector<char> file_name_buffer(MAX_PATH + 1); | |
::GetModuleFileNameA(NULL, &file_name_buffer[0], MAX_PATH); | |
DWORD dummy; | |
DWORD length = ::GetFileVersionInfoSizeA(&file_name_buffer[0], | |
&dummy); | |
std::vector<BYTE> version_buffer(length); | |
::GetFileVersionInfoA(&file_name_buffer[0], | |
dummy, | |
length, | |
&version_buffer[0]); | |
UINT page_count; | |
BOOL query_result = ::VerQueryValueA(&version_buffer[0], | |
"\\VarFileInfo\\Translation", | |
reinterpret_cast<void**>(&lang_info), | |
&page_count); | |
char sub_block[MAX_PATH]; | |
_snprintf_s(sub_block, | |
MAX_PATH, | |
MAX_PATH, | |
"\\StringFileInfo\\%04x%04x\\FileVersion", | |
lang_info->language, | |
lang_info->code_page); | |
LPVOID value = NULL; | |
UINT size; | |
query_result = ::VerQueryValueA(&version_buffer[0], | |
sub_block, | |
&value, | |
&size); | |
return static_cast<char*>(value); | |
} | |
void ShowUsage(void) { | |
std::wcout << L"Launches the WebDriver server for the Internet Explorer driver" << std::endl | |
<< std::endl | |
<< L"IEDriverServer [/port=<port>] [/host=<host>] [/log-level=<level>]" << std::endl | |
<< L" [/log-file=<file>] [/extract-path=<path>] [/silent]" << std::endl | |
<< std::endl | |
<< L" /port=<port> Specifies the port on which the server will listen for" << std::endl | |
<< L" commands. Defaults to 5555 if not specified." << std::endl | |
<< L" /host=<host> Specifies the address of the host adapter on which the server" << std::endl | |
<< L" will listen for commands." << std::endl | |
<< L" /log-level=<level>" << std::endl | |
<< L" Specifies the log level used by the server. Valid values are:" << std::endl | |
<< L" TRACE, DEBUG, INFO, WARN, ERROR, and FATAL. Defaults to FATAL" << std::endl | |
<< L" if not specified." << std::endl | |
<< L" /log-file=<file>" << std::endl | |
<< L" Specifies the full path and file name of the log file used by" << std::endl | |
<< L" the server. Defaults logging to stdout if not specified. " << std::endl | |
<< L" /extract-path=<path>" << std::endl | |
<< L" Specifies the full path to the directory used to extract" << std::endl | |
<< L" supporting files used by the server. Defaults to the TEMP" << std::endl | |
<< L" directory if not specified." << std::endl | |
<< L" /silent Suppresses diagnostic output when the server is started." << std::endl; | |
} | |
int _tmain(int argc, _TCHAR* argv[]) { | |
CommandLineArguments args(argc, argv); | |
if (args.is_help_requested()) { | |
ShowUsage(); | |
return 0; | |
} | |
vector<TCHAR> temp_file_name_buffer(MAX_PATH); | |
vector<TCHAR> temp_path_buffer(MAX_PATH); | |
// Gets the temp path env string (no guarantee it's a valid path). | |
unsigned long temp_path_length = ::GetTempPath(MAX_PATH, | |
&temp_path_buffer[0]); | |
std::wstring extraction_path(&temp_path_buffer[0]); | |
std::string extraction_path_arg = args.GetValue(EXTRACTPATH_COMMAND_LINE_ARG, ""); | |
if (extraction_path_arg.size() != 0) { | |
extraction_path = CA2W(extraction_path_arg.c_str(), CP_UTF8); | |
} | |
unsigned int error_code = ::GetTempFileName(extraction_path.c_str(), | |
TEMP_FILE_PREFIX, | |
0, | |
&temp_file_name_buffer[0]); | |
std::wstring temp_file_name(&temp_file_name_buffer[0]); | |
if (!ExtractResource(IDR_DRIVER_LIBRARY, temp_file_name)) { | |
std::wcout << L"Failed to extract the library to temp directory: " | |
<< temp_file_name; | |
return ERR_DLL_EXTRACT_FAIL; | |
} | |
HMODULE module_handle = ::LoadLibrary(temp_file_name.c_str()); | |
if (module_handle == NULL) { | |
std::wcout << L"Failed to load the library from temp directory: " | |
<< temp_file_name; | |
return ERR_DLL_LOAD_FAIL; | |
} | |
STARTSERVEREXPROC start_server_ex_proc = reinterpret_cast<STARTSERVEREXPROC>( | |
::GetProcAddress(module_handle, START_SERVER_EX_API_NAME)); | |
STOPSERVERPROC stop_server_proc = reinterpret_cast<STOPSERVERPROC>( | |
::GetProcAddress(module_handle, STOP_SERVER_API_NAME)); | |
if (start_server_ex_proc == NULL || stop_server_proc == NULL) { | |
std::wcout << L"Could not find entry point in extracted library: " | |
<< temp_file_name; | |
return ERR_FUNCTION_NOT_FOUND; | |
} | |
int port = atoi(args.GetValue(PORT_COMMAND_LINE_ARG, "5555").c_str()); | |
std::string host_address = args.GetValue(HOST_COMMAND_LINE_ARG, ""); | |
std::string log_level = args.GetValue(LOGLEVEL_COMMAND_LINE_ARG, ""); | |
std::string log_file = args.GetValue(LOGFILE_COMMAND_LINE_ARG, ""); | |
bool silent = args.GetValue(SILENT_COMMAND_LINE_ARG, | |
BOOLEAN_COMMAND_LINE_ARG_MISSING_VALUE).size() == 0; | |
std::string executable_version = GetExecutableVersion(); | |
void* server_value = start_server_ex_proc(port, | |
host_address, | |
log_level, | |
log_file, | |
executable_version); | |
if (server_value == NULL) { | |
std::cout << L"Failed to start the server with: " | |
<< L"port = '" << port << "', " | |
<< L"host = '" << host_address << "', " | |
<< L"log level = '" << log_level << "', " | |
<< L"log file = '" << log_file << "'"; | |
return ERR_SERVER_START; | |
} | |
if (!silent) { | |
std::cout << "Started InternetExplorerDriver server" | |
<< " (" << GetProcessArchitectureDescription() << ")" | |
<< std::endl; | |
std::cout << executable_version | |
<< std::endl; | |
std::cout << "Listening on port " << port << std::endl; | |
if (host_address.size() > 0) { | |
std::cout << "Bound to network adapter with IP address " | |
<< host_address | |
<< std::endl; | |
} | |
if (log_level.size() > 0) { | |
std::cout << "Log level is set to " | |
<< log_level | |
<< std::endl; | |
} | |
if (log_file.size() > 0) { | |
std::cout << "Log file is set to " | |
<< log_file | |
<< std::endl; | |
} | |
if (extraction_path_arg.size() > 0) { | |
std::cout << "Library extracted to " | |
<< extraction_path_arg | |
<< std::endl; | |
} | |
} | |
// Create the shutdown event and wait for it to be signaled. | |
DWORD process_id = ::GetCurrentProcessId(); | |
vector<wchar_t> process_id_buffer(10); | |
_ltow_s(process_id, &process_id_buffer[0], process_id_buffer.size(), 10); | |
std::wstring process_id_string(&process_id_buffer[0]); | |
std::wstring event_name = IESERVER_SHUTDOWN_EVENT_NAME + process_id_string; | |
HANDLE event_handle = ::CreateEvent(NULL, | |
TRUE, | |
FALSE, | |
event_name.c_str()); | |
::WaitForSingleObject(event_handle, INFINITE); | |
::CloseHandle(event_handle); | |
stop_server_proc(); | |
::FreeLibrary(module_handle); | |
::DeleteFile(temp_file_name.c_str()); | |
return 0; | |
} |