// Licensed to the Software Freedom Conservancy (SFC) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The SFC licenses this file // to you 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 "ProxyManager.h" #include #include #include #include "json.h" #include "logging.h" #include "messages.h" #include "HookProcessor.h" #include "StringUtilities.h" #define WD_PROXY_TYPE_DIRECT "direct" #define WD_PROXY_TYPE_SYSTEM "system" #define WD_PROXY_TYPE_MANUAL "manual" #define WD_PROXY_TYPE_AUTOCONFIGURE "pac" #define WD_PROXY_TYPE_AUTODETECT "autodetect" #define HTTP_PROXY_MARKER "http=" #define HTTPS_PROXY_MARKER "https=" #define FTP_PROXY_MARKER "ftp=" #define SOCKS_PROXY_MARKER "socks=" #define BYPASS_PROXY_MARKER "|bypass=" namespace webdriver { ProxyManager::ProxyManager(void) { this->proxy_type_ = ""; this->http_proxy_ = ""; this->ftp_proxy_ = ""; this->ssl_proxy_ = ""; this->socks_proxy_ = ""; this->socks_user_name_ = ""; this->socks_password_ = ""; this->proxy_bypass_ = ""; this->proxy_autoconfigure_url_ = ""; this->is_proxy_modified_ = false; this->is_proxy_authorization_modified_ = false; this->use_per_process_proxy_ = false; this->current_autoconfig_url_ = L""; this->current_proxy_auto_detect_flags_ = 0; this->current_proxy_server_ = L""; this->current_socks_user_name_ = L""; this->current_socks_password_ = L""; this->current_proxy_type_ = 0; this->current_proxy_bypass_list_ = L""; } ProxyManager::~ProxyManager(void) { this->RestoreProxySettings(); } void ProxyManager::Initialize(ProxySettings settings) { LOG(TRACE) << "ProxyManager::Initialize"; // The wire protocol specifies lower case for the proxy type, but // language bindings have been sending upper case forever. Handle // both cases, thus we will normalize to a lower-case string for // proxy type. this->proxy_type_ = settings.proxy_type; std::transform(this->proxy_type_.begin(), this->proxy_type_.end(), this->proxy_type_.begin(), ::tolower); this->http_proxy_ = settings.http_proxy; this->ftp_proxy_ = settings.ftp_proxy; this->ssl_proxy_ = settings.ssl_proxy; this->socks_proxy_ = settings.socks_proxy; this->socks_user_name_ = settings.socks_user_name; this->socks_password_ = settings.socks_password; this->proxy_bypass_ = settings.proxy_bypass; this->proxy_autoconfigure_url_ = settings.proxy_autoconfig_url; if (this->proxy_type_ == WD_PROXY_TYPE_SYSTEM || this->proxy_type_ == WD_PROXY_TYPE_DIRECT || this->proxy_type_ == WD_PROXY_TYPE_MANUAL) { this->use_per_process_proxy_ = settings.use_per_process_proxy; } else { // By definition, per-process proxy settings can only be used with the // system proxy, direct connection, or with a manually specified proxy. this->use_per_process_proxy_ = false; } } void ProxyManager::SetProxySettings(HWND browser_window_handle) { LOG(TRACE) << "ProxyManager::SetProxySettings"; if (this->proxy_type_.size() > 0 && this->proxy_type_ != WD_PROXY_TYPE_SYSTEM) { if (this->use_per_process_proxy_) { LOG(DEBUG) << "Setting proxy for individual IE instance."; this->SetPerProcessProxySettings(browser_window_handle); } else { if (!this->is_proxy_modified_) { LOG(DEBUG) << "Setting system proxy."; this->GetCurrentProxySettings(); this->SetGlobalProxySettings(); } else { LOG(DEBUG) << "Proxy settings already set by IE driver."; } } this->is_proxy_modified_ = true; } else { LOG(DEBUG) << "Using existing system proxy settings."; } } Json::Value ProxyManager::GetProxyAsJson() { LOG(TRACE) << "ProxyManager::GetProxyAsJson"; Json::Value proxy_value; proxy_value["proxyType"] = this->proxy_type_; if (this->proxy_type_ == WD_PROXY_TYPE_MANUAL) { if (this->http_proxy_.size() > 0) { proxy_value["httpProxy"] = this->http_proxy_; } if (this->ftp_proxy_.size() > 0) { proxy_value["ftpProxy"] = this->ftp_proxy_; } if (this->ssl_proxy_.size() > 0) { proxy_value["sslProxy"] = this->ssl_proxy_; } if (this->socks_proxy_.size() > 0) { proxy_value["socksProxy"] = this->socks_proxy_; if (this->socks_user_name_.size() > 0) { proxy_value["socksUsername"] = this->socks_user_name_; } if (this->socks_password_.size() > 0) { proxy_value["socksPassword"] = this->socks_password_; } } } else if (this->proxy_type_ == WD_PROXY_TYPE_AUTOCONFIGURE) { proxy_value["proxyAutoconfigUrl"] = this->proxy_autoconfigure_url_; } return proxy_value; } std::wstring ProxyManager::BuildProxySettingsString() { LOG(TRACE) << "ProxyManager::BuildProxySettingsString"; std::string proxy_string = ""; if (this->proxy_type_ == WD_PROXY_TYPE_MANUAL) { if (this->http_proxy_.size() > 0) { proxy_string.append(HTTP_PROXY_MARKER).append(this->http_proxy_); } if (this->ftp_proxy_.size() > 0) { if (proxy_string.size() > 0) { proxy_string.append(" "); } proxy_string.append(FTP_PROXY_MARKER).append(this->ftp_proxy_); } if (this->ssl_proxy_.size() > 0) { if (proxy_string.size() > 0) { proxy_string.append(" "); } proxy_string.append(HTTPS_PROXY_MARKER).append(this->ssl_proxy_); } if (this->socks_proxy_.size() > 0) { if (proxy_string.size() > 0) { proxy_string.append(" "); } proxy_string.append(SOCKS_PROXY_MARKER).append(this->socks_proxy_); } if (this->proxy_bypass_.size() > 0) { proxy_string.append(BYPASS_PROXY_MARKER).append(this->proxy_bypass_); } } else if (this->proxy_type_ == WD_PROXY_TYPE_AUTOCONFIGURE) { proxy_string = this->proxy_autoconfigure_url_; } else { proxy_string = this->proxy_type_; } LOG(DEBUG) << "Built proxy settings string: '" << proxy_string << "'"; return StringUtilities::ToWString(proxy_string); } void ProxyManager::RestoreProxySettings() { LOG(TRACE) << "ProxyManager::RestoreProxySettings"; bool settings_restored = (!this->use_per_process_proxy_ && this->is_proxy_modified_) || this->is_proxy_authorization_modified_; if (!this->use_per_process_proxy_ && this->is_proxy_modified_) { INTERNET_PER_CONN_OPTION_LIST option_list; std::vector restore_options(5); unsigned long list_size = sizeof(INTERNET_PER_CONN_OPTION_LIST); std::vector autoconfig_url_buffer; StringUtilities::ToBuffer(this->current_autoconfig_url_, &autoconfig_url_buffer); restore_options[0].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL; restore_options[0].Value.pszValue = &autoconfig_url_buffer[0]; restore_options[1].dwOption = INTERNET_PER_CONN_AUTODISCOVERY_FLAGS; restore_options[1].Value.dwValue = this->current_proxy_auto_detect_flags_; restore_options[2].dwOption = INTERNET_PER_CONN_FLAGS; restore_options[2].Value.dwValue = this->current_proxy_type_; std::vector proxy_bypass_buffer; StringUtilities::ToBuffer(this->current_proxy_bypass_list_, &proxy_bypass_buffer); restore_options[3].dwOption = INTERNET_PER_CONN_PROXY_BYPASS; restore_options[3].Value.pszValue = &proxy_bypass_buffer[0]; std::vector proxy_server_buffer; StringUtilities::ToBuffer(this->current_proxy_server_, &proxy_server_buffer); restore_options[4].dwOption = INTERNET_PER_CONN_PROXY_SERVER; restore_options[4].Value.pszValue = &proxy_server_buffer[0]; option_list.dwSize = sizeof(INTERNET_PER_CONN_OPTION_LIST); option_list.pszConnection = NULL; option_list.dwOptionCount = static_cast(restore_options.size()); option_list.dwOptionError = 0; option_list.pOptions = &restore_options[0]; BOOL success = ::InternetSetOption(NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, &option_list, list_size); if (!success) { LOGERR(WARN) << "InternetSetOption failed setting INTERNET_OPTION_PER_CONNECTION_OPTION"; } this->is_proxy_modified_ = false; } if (this->is_proxy_authorization_modified_) { this->SetProxyAuthentication(this->current_socks_user_name_, this->current_socks_password_); this->is_proxy_authorization_modified_ = false; } if (settings_restored) { BOOL notify_success = ::InternetSetOption(NULL, INTERNET_OPTION_PROXY_SETTINGS_CHANGED, NULL, 0); if (!notify_success) { LOGERR(WARN) << "InternetSetOption failed setting INTERNET_OPTION_PROXY_SETTINGS_CHANGED"; } } } void ProxyManager::SetPerProcessProxySettings(HWND browser_window_handle) { LOG(TRACE) << "ProxyManager::SetPerProcessProxySettings"; std::wstring proxy = this->BuildProxySettingsString(); HookSettings hook_settings; hook_settings.window_handle = browser_window_handle; hook_settings.hook_procedure_name = "SetProxyWndProc"; hook_settings.hook_procedure_type = WH_CALLWNDPROC; hook_settings.communication_type = OneWay; HookProcessor hook; if (!hook.CanSetWindowsHook(browser_window_handle)) { LOG(WARN) << "Proxy will not be set! There is a mismatch in the " << "bitness between the driver and browser. In particular, " << "be sure you are not attempting to use a 64-bit " << "IEDriverServer.exe against IE 10 or 11, even on 64-bit " << "Windows."; } hook.Initialize(hook_settings); hook.PushData(proxy); LRESULT result = ::SendMessage(browser_window_handle, WD_CHANGE_PROXY, NULL, NULL); if (this->socks_proxy_.size() > 0 && (this->socks_user_name_.size() > 0 || this->socks_password_.size() > 0)) { LOG(WARN) << "Windows APIs provide no way to set proxy user name and " << "password on a per-process basis. Setting global setting."; this->SetProxyAuthentication(StringUtilities::ToWString(this->socks_user_name_), StringUtilities::ToWString(this->socks_password_)); this->is_proxy_authorization_modified_ = true; // Notify WinINet clients that the proxy options have changed. BOOL success = ::InternetSetOption(NULL, INTERNET_OPTION_PROXY_SETTINGS_CHANGED, NULL, 0); if (!success) { LOGERR(WARN) << "InternetSetOption failed setting INTERNET_OPTION_PROXY_SETTINGS_CHANGED"; } } } void ProxyManager::SetGlobalProxySettings() { LOG(TRACE) << "ProxyManager::SetGlobalProxySettings"; std::wstring proxy_settings_string = this->BuildProxySettingsString(); std::vector proxy_settings; StringUtilities::Split(proxy_settings_string, _TEXT(BYPASS_PROXY_MARKER), &proxy_settings); std::wstring proxy = proxy_settings[0]; std::wstring proxy_bypass = L""; if (proxy_settings.size() > 1) { proxy_bypass = proxy_settings[1]; } INTERNET_PER_CONN_OPTION_LIST option_list; unsigned long list_size = sizeof(INTERNET_PER_CONN_OPTION_LIST); option_list.dwSize = sizeof(INTERNET_PER_CONN_OPTION_LIST); INTERNET_PER_CONN_OPTION proxy_options[3]; if (this->proxy_type_ == WD_PROXY_TYPE_DIRECT) { proxy_options[0].dwOption = INTERNET_PER_CONN_FLAGS; proxy_options[0].Value.dwValue = PROXY_TYPE_DIRECT; option_list.dwOptionCount = 1; } else if (this->proxy_type_ == WD_PROXY_TYPE_AUTOCONFIGURE) { proxy_options[0].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL; proxy_options[0].Value.pszValue = const_cast(proxy.c_str()); proxy_options[1].dwOption = INTERNET_PER_CONN_FLAGS; proxy_options[1].Value.dwValue = PROXY_TYPE_AUTO_PROXY_URL; option_list.dwOptionCount = 2; } else if (this->proxy_type_ == WD_PROXY_TYPE_AUTODETECT) { proxy_options[0].dwOption = INTERNET_PER_CONN_AUTODISCOVERY_FLAGS; proxy_options[0].Value.dwValue = AUTO_PROXY_FLAG_ALWAYS_DETECT; proxy_options[1].dwOption = INTERNET_PER_CONN_FLAGS; proxy_options[1].Value.dwValue = PROXY_TYPE_AUTO_DETECT; option_list.dwOptionCount = 2; } else { proxy_options[0].dwOption = INTERNET_PER_CONN_PROXY_SERVER; proxy_options[0].Value.pszValue = const_cast(proxy.c_str()); proxy_options[1].dwOption = INTERNET_PER_CONN_FLAGS; proxy_options[1].Value.dwValue = PROXY_TYPE_PROXY | PROXY_TYPE_DIRECT; proxy_options[2].dwOption = INTERNET_PER_CONN_PROXY_BYPASS; proxy_options[2].Value.pszValue = const_cast(proxy_bypass.c_str());; option_list.dwOptionCount = 3; } option_list.pOptions = proxy_options; option_list.pszConnection = NULL; option_list.dwOptionError = 0; BOOL success = ::InternetSetOption(NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, &option_list, list_size); if (!success) { LOGERR(WARN) << "InternetSetOption failed setting INTERNET_OPTION_PER_CONNECTION_OPTION"; } // Only set proxy authentication for SOCKS proxies, and // where the user name and password have been specified. if (this->socks_proxy_.size() > 0 && (this->socks_user_name_.size() > 0 || this->socks_password_.size() > 0)) { this->SetProxyAuthentication(StringUtilities::ToWString(this->socks_user_name_), StringUtilities::ToWString(this->socks_password_)); this->is_proxy_authorization_modified_ = true; } success = ::InternetSetOption(NULL, INTERNET_OPTION_PROXY_SETTINGS_CHANGED, NULL, 0); if (!success) { LOGERR(WARN) << "InternetSetOption failed setting INTERNET_OPTION_PROXY_SETTINGS_CHANGED"; } } void ProxyManager::SetProxyAuthentication(const std::wstring& user_name, const std::wstring& password) { LOG(TRACE) << "ProxyManager::SetProxyAuthentication"; BOOL success = ::InternetSetOption(NULL, INTERNET_OPTION_PROXY_USERNAME, const_cast(user_name.c_str()), static_cast(user_name.size()) + 1); if (!success) { LOGERR(WARN) << "InternetSetOption failed setting INTERNET_OPTION_PROXY_USERNAME"; } success = ::InternetSetOption(NULL, INTERNET_OPTION_PROXY_PASSWORD, const_cast(password.c_str()), static_cast(password.size()) + 1); if (!success) { LOGERR(WARN) << "InternetSetOption failed setting INTERNET_OPTION_PROXY_PASSWORD"; } } void ProxyManager::GetCurrentProxySettings() { LOG(TRACE) << "ProxyManager::GetCurrentProxySettings"; this->GetCurrentProxyType(); INTERNET_PER_CONN_OPTION_LIST option_list; std::vector options_to_get(4); unsigned long list_size = sizeof(INTERNET_PER_CONN_OPTION_LIST); options_to_get[0].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL; options_to_get[1].dwOption = INTERNET_PER_CONN_AUTODISCOVERY_FLAGS; options_to_get[2].dwOption = INTERNET_PER_CONN_PROXY_BYPASS; options_to_get[3].dwOption = INTERNET_PER_CONN_PROXY_SERVER; option_list.dwSize = sizeof(INTERNET_PER_CONN_OPTION_LIST); option_list.pszConnection = NULL; option_list.dwOptionCount = static_cast(options_to_get.size()); option_list.dwOptionError = 0; option_list.pOptions = &options_to_get[0]; BOOL success = ::InternetQueryOption(NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, &option_list, &list_size); if (!success) { LOGERR(WARN) << "InternetQueryOption failed getting proxy settings"; } if(options_to_get[0].Value.pszValue != NULL) { this->current_autoconfig_url_ = options_to_get[0].Value.pszValue; ::GlobalFree(options_to_get[0].Value.pszValue); } this->current_proxy_auto_detect_flags_ = options_to_get[1].Value.dwValue; if(options_to_get[2].Value.pszValue != NULL) { this->current_proxy_bypass_list_ = options_to_get[2].Value.pszValue; ::GlobalFree(options_to_get[2].Value.pszValue); } if(options_to_get[3].Value.pszValue != NULL) { this->current_proxy_server_ = options_to_get[3].Value.pszValue; ::GlobalFree(options_to_get[3].Value.pszValue); } this->GetCurrentProxyAuthentication(); } void ProxyManager::GetCurrentProxyAuthentication() { LOG(TRACE) << "ProxyManager::GetCurrentProxyAuthentication"; DWORD user_name_length = 0; BOOL success = ::InternetQueryOption(NULL, INTERNET_OPTION_PROXY_USERNAME, NULL, &user_name_length); if (user_name_length > 0) { std::vector user_name(user_name_length); success = ::InternetQueryOption(NULL, INTERNET_OPTION_PROXY_USERNAME, &user_name[0], &user_name_length); this->current_socks_user_name_ = &user_name[0]; } DWORD password_length = 0; success = ::InternetQueryOption(NULL, INTERNET_OPTION_PROXY_PASSWORD, NULL, &password_length); if (password_length > 0) { std::vector password(password_length); success = ::InternetQueryOption(NULL, INTERNET_OPTION_PROXY_PASSWORD, &password[0], &password_length); this->current_socks_password_ = &password[0]; } } void ProxyManager::GetCurrentProxyType() { LOG(TRACE) << "ProxyManager::GetCurrentProxyType"; INTERNET_PER_CONN_OPTION_LIST option_list; std::vector proxy_type_options(1); unsigned long list_size = sizeof(INTERNET_PER_CONN_OPTION_LIST); proxy_type_options[0].dwOption = INTERNET_PER_CONN_FLAGS_UI; option_list.dwSize = sizeof(INTERNET_PER_CONN_OPTION_LIST); option_list.pszConnection = NULL; option_list.dwOptionCount = static_cast(proxy_type_options.size()); option_list.dwOptionError = 0; option_list.pOptions = &proxy_type_options[0]; // First check for INTERNET_PER_CONN_FLAGS_UI, then if that fails // check again using INTERNET_PER_CONN_FLAGS. This is documented at // http://msdn.microsoft.com/en-us/library/windows/desktop/aa385145%28v=vs.85%29.aspx BOOL success = ::InternetQueryOption(NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, &option_list, &list_size); if (success) { this->current_proxy_type_ = proxy_type_options[0].Value.dwValue; return; } proxy_type_options[0].dwOption = INTERNET_PER_CONN_FLAGS; success = ::InternetQueryOption(NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, &option_list, &list_size); if (success) { this->current_proxy_type_ = proxy_type_options[0].Value.dwValue; } } } // namespace webdriver #ifdef __cplusplus extern "C" { #endif LRESULT CALLBACK SetProxyWndProc(int nCode, WPARAM wParam, LPARAM lParam) { CWPSTRUCT* call_window_proc_struct = reinterpret_cast(lParam); if (WM_COPYDATA == call_window_proc_struct->message) { COPYDATASTRUCT* data = reinterpret_cast(call_window_proc_struct->lParam); webdriver::HookProcessor::CopyDataToBuffer(data->cbData, data->lpData); } else if (WD_CHANGE_PROXY == call_window_proc_struct->message) { // Allocate a buffer of the length of the data in the shared memory buffer, // plus one extra wide char, to account for the null terminator. int multibyte_buffer_size = webdriver::HookProcessor::GetDataBufferSize() + sizeof(wchar_t); std::vector multibyte_buffer(multibyte_buffer_size); std::vector multibyte_buffer_bypass(multibyte_buffer_size); std::wstring proxy_settings = webdriver::HookProcessor::CopyWStringFromBuffer(); std::wstring proxy = proxy_settings; std::wstring proxy_bypass = L""; // Use the _TEXT macro to convert to a wstring literal std::wstring bypass_marker = _TEXT(BYPASS_PROXY_MARKER); size_t bypass_marker_index = proxy_settings.find(bypass_marker); if (bypass_marker_index != std::wstring::npos) { proxy = proxy_settings.substr(0, bypass_marker_index); proxy_bypass = proxy_settings.substr(bypass_marker_index + bypass_marker.size()); } INTERNET_PROXY_INFO proxy_info; if (proxy == L"direct") { proxy_info.dwAccessType = INTERNET_OPEN_TYPE_DIRECT; proxy_info.lpszProxy = L""; } else { // UrlMkSetSessionOption only appears to work on either ASCII or // multi-byte strings, not Unicode strings. Since the INTERNET_PROXY_INFO // struct hard-codes to LPCTSTR, and that translates into LPCWSTR for the // compiler settings we use, we must use the multi-byte version here. // Note that for the count of input characters, we can use -1, since // we've forced the string to be null-terminated. ::WideCharToMultiByte(CP_UTF8, 0, proxy.c_str(), -1, &multibyte_buffer[0], static_cast(multibyte_buffer.size()), NULL, NULL); proxy_info.dwAccessType = INTERNET_OPEN_TYPE_PROXY; proxy_info.lpszProxy = reinterpret_cast(&multibyte_buffer[0]); } ::WideCharToMultiByte(CP_UTF8, 0, proxy_bypass.c_str(), -1, &multibyte_buffer_bypass[0], static_cast(multibyte_buffer_bypass.size()), NULL, NULL); proxy_info.lpszProxyBypass = reinterpret_cast(&multibyte_buffer_bypass[0]); DWORD proxy_info_size = sizeof(proxy_info); HRESULT hr = ::UrlMkSetSessionOption(INTERNET_OPTION_PROXY, reinterpret_cast(&proxy_info), proxy_info_size, 0); } return ::CallNextHookEx(NULL, nCode, wParam, lParam); } #ifdef __cplusplus } #endif