2013-01-11 22:18:32 +01:00
|
|
|
// Copyright 2011 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 "BrowserFactory.h"
|
2013-02-27 12:58:36 -05:00
|
|
|
#include <ctime>
|
2014-08-19 21:11:00 +00:00
|
|
|
#include <vector>
|
|
|
|
|
#include <exdispid.h>
|
|
|
|
|
#include <iepmapi.h>
|
|
|
|
|
#include <oleacc.h>
|
|
|
|
|
#include <sddl.h>
|
|
|
|
|
#include <shlguid.h>
|
2013-07-12 15:00:57 -07:00
|
|
|
#include <shlobj.h>
|
2014-08-19 21:11:00 +00:00
|
|
|
#include <WinInet.h>
|
2013-01-11 22:18:32 +01:00
|
|
|
#include "logging.h"
|
2013-02-27 12:58:36 -05:00
|
|
|
#include "psapi.h"
|
2013-07-10 15:53:29 -04:00
|
|
|
#include "RegistryUtilities.h"
|
2013-01-11 22:18:32 +01:00
|
|
|
|
|
|
|
|
namespace webdriver {
|
|
|
|
|
|
|
|
|
|
BrowserFactory::BrowserFactory(void) {
|
2013-07-16 19:48:11 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BrowserFactory::~BrowserFactory(void) {
|
|
|
|
|
if (this->oleacc_instance_handle_) {
|
|
|
|
|
::FreeLibrary(this->oleacc_instance_handle_);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BrowserFactory::Initialize(BrowserFactorySettings settings) {
|
|
|
|
|
LOG(TRACE) << "Entering BrowserFactory::Initialize";
|
|
|
|
|
this->ignore_protected_mode_settings_ = settings.ignore_protected_mode_settings;
|
|
|
|
|
this->ignore_zoom_setting_ = settings.ignore_zoom_setting;
|
|
|
|
|
this->browser_attach_timeout_ = settings.browser_attach_timeout;
|
|
|
|
|
this->force_createprocess_api_ = settings.force_create_process_api;
|
2013-07-12 15:00:57 -07:00
|
|
|
this->force_shell_windows_api_ = settings.force_shell_windows_api;
|
2013-07-30 15:28:15 -04:00
|
|
|
this->clear_cache_ = settings.clear_cache_before_launch;
|
2013-07-16 19:48:11 -04:00
|
|
|
this->browser_command_line_switches_ = StringUtilities::ToWString(settings.browser_command_line_switches);
|
|
|
|
|
this->initial_browser_url_ = StringUtilities::ToWString(settings.initial_browser_url);
|
2013-01-11 22:18:32 +01:00
|
|
|
|
|
|
|
|
this->GetExecutableLocation();
|
|
|
|
|
this->GetIEVersion();
|
|
|
|
|
this->GetOSVersion();
|
|
|
|
|
this->html_getobject_msg_ = ::RegisterWindowMessage(HTML_GETOBJECT_MSG);
|
|
|
|
|
|
|
|
|
|
// Explicitly load MSAA so we know if it's installed
|
|
|
|
|
this->oleacc_instance_handle_ = ::LoadLibrary(OLEACC_LIBRARY_NAME);
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-30 15:28:15 -04:00
|
|
|
void BrowserFactory::ClearCache() {
|
|
|
|
|
if (this->clear_cache_) {
|
|
|
|
|
if (this->windows_major_version_ >= 6) {
|
|
|
|
|
this->InvokeClearCacheUtility(true);
|
|
|
|
|
}
|
|
|
|
|
this->InvokeClearCacheUtility(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-16 19:48:11 -04:00
|
|
|
DWORD BrowserFactory::LaunchBrowserProcess(std::string* error_message) {
|
2013-01-11 22:18:32 +01:00
|
|
|
LOG(TRACE) << "Entering BrowserFactory::LaunchBrowserProcess";
|
|
|
|
|
|
|
|
|
|
DWORD process_id = NULL;
|
|
|
|
|
bool has_valid_protected_mode_settings = false;
|
|
|
|
|
LOG(DEBUG) << "Ignoring Protected Mode Settings: "
|
2013-07-16 19:48:11 -04:00
|
|
|
<< this->ignore_protected_mode_settings_;
|
|
|
|
|
if (!this->ignore_protected_mode_settings_) {
|
2013-01-11 22:18:32 +01:00
|
|
|
LOG(DEBUG) << "Checking validity of Protected Mode settings.";
|
|
|
|
|
has_valid_protected_mode_settings = this->ProtectedModeSettingsAreValid();
|
|
|
|
|
}
|
|
|
|
|
LOG(DEBUG) << "Has Valid Protected Mode Settings: "
|
|
|
|
|
<< has_valid_protected_mode_settings;
|
2013-07-16 19:48:11 -04:00
|
|
|
if (this->ignore_protected_mode_settings_ || has_valid_protected_mode_settings) {
|
2013-04-10 00:02:15 +04:00
|
|
|
// Determine which launch API to use.
|
|
|
|
|
bool use_createprocess_api = false;
|
2013-07-16 19:48:11 -04:00
|
|
|
if (this->force_createprocess_api_) {
|
2013-04-10 00:02:15 +04:00
|
|
|
if (this->IsCreateProcessApiAvailable()) {
|
|
|
|
|
use_createprocess_api = true;
|
|
|
|
|
} else {
|
|
|
|
|
// The only time IsCreateProcessApiAvailable will return false
|
|
|
|
|
// is when the user is using IE 8 or higher, and does not have
|
|
|
|
|
// the correct registry key setting to force the same process
|
|
|
|
|
// for the enclosing window and tab processes.
|
|
|
|
|
*error_message = CREATEPROCESS_REGISTRY_ERROR_MESSAGE;
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// If we have the IELaunchURL API, expressly use it. Otherwise,
|
|
|
|
|
// fall back to using CreateProcess().
|
|
|
|
|
if (!this->IsIELaunchURLAvailable()) {
|
|
|
|
|
use_createprocess_api = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-01-11 22:18:32 +01:00
|
|
|
|
2013-07-30 15:28:15 -04:00
|
|
|
this->ClearCache();
|
|
|
|
|
|
2013-04-10 00:02:15 +04:00
|
|
|
PROCESS_INFORMATION proc_info;
|
2013-01-11 22:18:32 +01:00
|
|
|
::ZeroMemory(&proc_info, sizeof(proc_info));
|
|
|
|
|
|
2013-04-10 00:02:15 +04:00
|
|
|
if (!use_createprocess_api) {
|
2013-07-16 19:48:11 -04:00
|
|
|
this->LaunchBrowserUsingIELaunchURL(&proc_info, error_message);
|
2013-01-11 22:18:32 +01:00
|
|
|
} else {
|
2013-07-16 19:48:11 -04:00
|
|
|
this->LaunchBrowserUsingCreateProcess(&proc_info, error_message);
|
2013-01-11 22:18:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
process_id = proc_info.dwProcessId;
|
|
|
|
|
if (process_id == NULL) {
|
|
|
|
|
// If whatever API we are using failed to launch the browser, we should
|
|
|
|
|
// have a NULL value in the dwProcessId member of the PROCESS_INFORMATION
|
|
|
|
|
// structure. In that case, we will have already set the approprate error
|
|
|
|
|
// message. On the off chance that we haven't yet set the appropriate
|
|
|
|
|
// error message, that means we successfully launched the browser (i.e.,
|
|
|
|
|
// the browser launch API returned a success code), but we still have a
|
|
|
|
|
// NULL process ID.
|
2013-04-10 00:02:15 +04:00
|
|
|
if (error_message->size() == 0) {
|
2013-07-11 17:46:24 -04:00
|
|
|
std::string launch_api_name = use_createprocess_api ? "The CreateProcess API" : "The IELaunchURL API";
|
2013-04-10 00:02:15 +04:00
|
|
|
*error_message = launch_api_name + NULL_PROCESS_ID_ERROR_MESSAGE;
|
2013-01-11 22:18:32 +01:00
|
|
|
}
|
2013-02-27 12:58:36 -05:00
|
|
|
} else {
|
|
|
|
|
::WaitForInputIdle(proc_info.hProcess, 2000);
|
|
|
|
|
LOG(DEBUG) << "IE launched successfully with process ID " << process_id;
|
2013-07-11 17:46:24 -04:00
|
|
|
std::vector<wchar_t> image_buffer(MAX_PATH);
|
2013-02-27 12:58:36 -05:00
|
|
|
int buffer_count = ::GetProcessImageFileName(proc_info.hProcess, &image_buffer[0], MAX_PATH);
|
|
|
|
|
std::wstring full_image_path = &image_buffer[0];
|
|
|
|
|
size_t last_delimiter = full_image_path.find_last_of('\\');
|
2013-03-21 13:58:47 -04:00
|
|
|
std::string image_name = StringUtilities::ToString(full_image_path.substr(last_delimiter + 1, buffer_count - last_delimiter));
|
2013-02-27 12:58:36 -05:00
|
|
|
LOG(DEBUG) << "Process with ID " << process_id << " is executing " << image_name;
|
2013-01-11 22:18:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (proc_info.hThread != NULL) {
|
|
|
|
|
::CloseHandle(proc_info.hThread);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (proc_info.hProcess != NULL) {
|
|
|
|
|
::CloseHandle(proc_info.hProcess);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
*error_message = PROTECTED_MODE_SETTING_ERROR_MESSAGE;
|
|
|
|
|
}
|
|
|
|
|
return process_id;
|
|
|
|
|
}
|
|
|
|
|
|
2013-04-10 00:02:15 +04:00
|
|
|
bool BrowserFactory::IsIELaunchURLAvailable() {
|
|
|
|
|
LOG(TRACE) << "Entering BrowserFactory::IsIELaunchURLAvailable";
|
|
|
|
|
bool api_is_available = false;
|
|
|
|
|
HMODULE library_handle = ::LoadLibrary(IEFRAME_LIBRARY_NAME);
|
|
|
|
|
if (library_handle != NULL) {
|
|
|
|
|
FARPROC proc_address = 0;
|
|
|
|
|
proc_address = ::GetProcAddress(library_handle, IELAUNCHURL_FUNCTION_NAME);
|
|
|
|
|
if (proc_address == NULL || proc_address == 0) {
|
|
|
|
|
LOGERR(DEBUG) << "Unable to get address of " << IELAUNCHURL_FUNCTION_NAME
|
|
|
|
|
<< " method in " << IEFRAME_LIBRARY_NAME;
|
|
|
|
|
} else {
|
|
|
|
|
api_is_available = true;
|
|
|
|
|
}
|
|
|
|
|
::FreeLibrary(library_handle);
|
|
|
|
|
} else {
|
|
|
|
|
LOGERR(DEBUG) << "Unable to load library " << IEFRAME_LIBRARY_NAME;
|
|
|
|
|
}
|
|
|
|
|
return api_is_available;
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-16 19:48:11 -04:00
|
|
|
void BrowserFactory::LaunchBrowserUsingIELaunchURL(PROCESS_INFORMATION* proc_info,
|
2013-04-10 00:02:15 +04:00
|
|
|
std::string* error_message) {
|
2013-07-18 07:29:13 +04:00
|
|
|
LOG(TRACE) << "Entering BrowserFactory::LaunchBrowserUsingIELaunchURL";
|
2013-04-10 00:02:15 +04:00
|
|
|
LOG(DEBUG) << "Starting IE using the IELaunchURL API";
|
2013-07-16 19:48:11 -04:00
|
|
|
HRESULT launch_result = ::IELaunchURL(this->initial_browser_url_.c_str(),
|
2013-04-10 00:02:15 +04:00
|
|
|
proc_info,
|
|
|
|
|
NULL);
|
|
|
|
|
if (FAILED(launch_result)) {
|
2013-07-11 17:17:50 -04:00
|
|
|
LOGHR(WARN, launch_result) << "Error using IELaunchURL to start IE";
|
2013-12-17 14:24:58 -05:00
|
|
|
std::wstring hresult_msg = _com_error(launch_result).ErrorMessage();
|
2013-07-11 17:17:50 -04:00
|
|
|
*error_message = StringUtilities::Format(IELAUNCHURL_ERROR_MESSAGE,
|
|
|
|
|
launch_result,
|
2013-12-17 14:24:58 -05:00
|
|
|
StringUtilities::ToString(hresult_msg).c_str(),
|
2013-07-16 19:48:11 -04:00
|
|
|
this->initial_browser_url().c_str());
|
2013-04-10 00:02:15 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BrowserFactory::IsCreateProcessApiAvailable() {
|
|
|
|
|
LOG(TRACE) << "Entering BrowserFactory::IsCreateProcessApiAvailable";
|
|
|
|
|
if (this->ie_major_version_ >= 8) {
|
|
|
|
|
// According to http://blogs.msdn.com/b/askie/archive/2009/03/09/opening-a-new-tab-may-launch-a-new-process-with-internet-explorer-8-0.aspx
|
|
|
|
|
// If CreateProcess() is used and TabProcGrowth != 0 IE will use different tab and frame processes.
|
|
|
|
|
// Such behaviour is not supported by AttachToBrowser().
|
|
|
|
|
// FYI, IELaunchURL() returns correct 'frame' process (but sometimes not).
|
|
|
|
|
std::wstring tab_proc_growth;
|
2013-07-10 15:53:29 -04:00
|
|
|
if (RegistryUtilities::GetRegistryValue(HKEY_CURRENT_USER,
|
|
|
|
|
IE_TABPROCGROWTH_REGISTRY_KEY,
|
|
|
|
|
L"TabProcGrowth",
|
|
|
|
|
&tab_proc_growth)) {
|
2013-04-10 00:02:15 +04:00
|
|
|
if (tab_proc_growth != L"0") {
|
|
|
|
|
// Registry value has wrong value, return false
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Registry key or value not found, or another error condition getting the value.
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-16 19:48:11 -04:00
|
|
|
void BrowserFactory::LaunchBrowserUsingCreateProcess(PROCESS_INFORMATION* proc_info,
|
2013-04-10 00:02:15 +04:00
|
|
|
std::string* error_message) {
|
|
|
|
|
LOG(TRACE) << "Entering BrowserFactory::LaunchBrowserUsingCreateProcess";
|
|
|
|
|
LOG(DEBUG) << "Starting IE using the CreateProcess API";
|
|
|
|
|
|
|
|
|
|
STARTUPINFO start_info;
|
|
|
|
|
::ZeroMemory(&start_info, sizeof(start_info));
|
|
|
|
|
start_info.cb = sizeof(start_info);
|
|
|
|
|
|
|
|
|
|
std::wstring executable_and_url = this->ie_executable_location_;
|
2013-07-16 19:48:11 -04:00
|
|
|
if (this->browser_command_line_switches_.size() != 0) {
|
2013-04-10 00:02:15 +04:00
|
|
|
executable_and_url.append(L" ");
|
2013-07-16 19:48:11 -04:00
|
|
|
executable_and_url.append(this->browser_command_line_switches_);
|
2013-04-10 00:02:15 +04:00
|
|
|
}
|
|
|
|
|
executable_and_url.append(L" ");
|
2013-07-16 19:48:11 -04:00
|
|
|
executable_and_url.append(this->initial_browser_url_);
|
2013-04-10 00:02:15 +04:00
|
|
|
|
|
|
|
|
LOG(TRACE) << "IE starting command line is: '" << LOGWSTRING(executable_and_url) << "'.";
|
|
|
|
|
|
|
|
|
|
LPWSTR command_line = new WCHAR[executable_and_url.size() + 1];
|
|
|
|
|
wcscpy_s(command_line,
|
|
|
|
|
executable_and_url.size() + 1,
|
|
|
|
|
executable_and_url.c_str());
|
|
|
|
|
command_line[executable_and_url.size()] = L'\0';
|
|
|
|
|
BOOL create_process_result = ::CreateProcess(NULL,
|
|
|
|
|
command_line,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
FALSE,
|
|
|
|
|
0,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
&start_info,
|
|
|
|
|
proc_info);
|
|
|
|
|
if (!create_process_result) {
|
2013-07-11 17:17:50 -04:00
|
|
|
*error_message = StringUtilities::Format(CREATEPROCESS_ERROR_MESSAGE,
|
|
|
|
|
StringUtilities::ToString(command_line));
|
2013-04-10 00:02:15 +04:00
|
|
|
}
|
|
|
|
|
delete[] command_line;
|
|
|
|
|
}
|
|
|
|
|
|
2013-01-11 22:18:32 +01:00
|
|
|
bool BrowserFactory::GetDocumentFromWindowHandle(HWND window_handle,
|
|
|
|
|
IHTMLDocument2** document) {
|
|
|
|
|
LOG(TRACE) << "Entering BrowserFactory::GetDocumentFromWindowHandle";
|
|
|
|
|
|
|
|
|
|
if (window_handle != NULL && this->oleacc_instance_handle_) {
|
|
|
|
|
LRESULT result;
|
2013-07-12 15:00:57 -07:00
|
|
|
|
2013-01-11 22:18:32 +01:00
|
|
|
::SendMessageTimeout(window_handle,
|
|
|
|
|
this->html_getobject_msg_,
|
|
|
|
|
0L,
|
|
|
|
|
0L,
|
|
|
|
|
SMTO_ABORTIFHUNG,
|
|
|
|
|
1000,
|
|
|
|
|
reinterpret_cast<PDWORD_PTR>(&result));
|
|
|
|
|
|
|
|
|
|
LPFNOBJECTFROMLRESULT object_pointer = reinterpret_cast<LPFNOBJECTFROMLRESULT>(::GetProcAddress(this->oleacc_instance_handle_, "ObjectFromLresult"));
|
|
|
|
|
if (object_pointer != NULL) {
|
|
|
|
|
HRESULT hr;
|
|
|
|
|
hr = (*object_pointer)(result,
|
|
|
|
|
IID_IHTMLDocument2,
|
|
|
|
|
0,
|
|
|
|
|
reinterpret_cast<void**>(document));
|
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
LOGHR(WARN, hr) << "Unable to convert document object pointer to IHTMLDocument2 object via ObjectFromLresult";
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
LOG(WARN) << "Unable to get address of ObjectFromLresult method from library; GetProcAddress() for ObjectFromLresult returned NULL";
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
LOG(WARN) << "Window handle is invalid or OLEACC.DLL is not loaded properly";
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BrowserFactory::AttachToBrowser(ProcessWindowInfo* process_window_info,
|
|
|
|
|
std::string* error_message) {
|
|
|
|
|
LOG(TRACE) << "Entering BrowserFactory::AttachToBrowser";
|
2013-07-12 15:00:57 -07:00
|
|
|
bool attached = false;
|
|
|
|
|
|
|
|
|
|
// Attempt to attach to the browser using ActiveAccessibility API
|
|
|
|
|
// first, if this fails fallback to using ShellWindows API.
|
|
|
|
|
// ActiveAccessibility fails if the Windows Desktop runs out of
|
|
|
|
|
// free space for GlobalAtoms.
|
|
|
|
|
// ShellWindows might fail if there is an IE modal dialog blocking
|
|
|
|
|
// execution (unverified).
|
|
|
|
|
if (!this->force_shell_windows_api_) {
|
|
|
|
|
attached = this->AttachToBrowserUsingActiveAccessibility(process_window_info,
|
|
|
|
|
error_message);
|
|
|
|
|
if (!attached) {
|
|
|
|
|
LOG(DEBUG) << "Failed to find IWebBrowser2 using ActiveAccessibility: " << *error_message;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!attached) {
|
|
|
|
|
LOG(DEBUG) << "Using IShellWindows to find IWebBrowser2 interface";
|
|
|
|
|
attached = this->AttachToBrowserUsingShellWindows(process_window_info,
|
|
|
|
|
error_message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (attached) {
|
|
|
|
|
// Test for zoom level = 100%
|
|
|
|
|
int zoom_level = 100;
|
|
|
|
|
LOG(DEBUG) << "Ignoring zoom setting: " << this->ignore_zoom_setting_;
|
|
|
|
|
if (!this->ignore_zoom_setting_) {
|
|
|
|
|
zoom_level = this->GetBrowserZoomLevel(process_window_info->pBrowser);
|
|
|
|
|
}
|
|
|
|
|
if (zoom_level != 100) {
|
|
|
|
|
std::string zoom_level_error =
|
|
|
|
|
StringUtilities::Format(ZOOM_SETTING_ERROR_MESSAGE, zoom_level);
|
|
|
|
|
LOG(WARN) << zoom_level_error;
|
|
|
|
|
*error_message = zoom_level_error;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return attached;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BrowserFactory::AttachToBrowserUsingActiveAccessibility
|
|
|
|
|
(ProcessWindowInfo* process_window_info,
|
|
|
|
|
std::string* error_message) {
|
|
|
|
|
LOG(TRACE) << "Entering BrowserFactory::AttachToBrowserUsingActiveAccessibility";
|
|
|
|
|
|
2013-07-16 19:48:11 -04:00
|
|
|
clock_t end = clock() + (this->browser_attach_timeout_ / 1000 * CLOCKS_PER_SEC);
|
2013-01-11 22:18:32 +01:00
|
|
|
while (process_window_info->hwndBrowser == NULL) {
|
2013-07-16 19:48:11 -04:00
|
|
|
if (this->browser_attach_timeout_ > 0 && (clock() > end)) {
|
2013-02-27 12:58:36 -05:00
|
|
|
break;
|
|
|
|
|
}
|
2013-01-11 22:18:32 +01:00
|
|
|
::EnumWindows(&BrowserFactory::FindBrowserWindow,
|
|
|
|
|
reinterpret_cast<LPARAM>(process_window_info));
|
|
|
|
|
if (process_window_info->hwndBrowser == NULL) {
|
|
|
|
|
::Sleep(250);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-27 12:58:36 -05:00
|
|
|
if (process_window_info->hwndBrowser == NULL) {
|
2013-07-11 17:17:50 -04:00
|
|
|
*error_message = StringUtilities::Format(ATTACH_TIMEOUT_ERROR_MESSAGE,
|
|
|
|
|
process_window_info->dwProcessId,
|
2013-07-16 19:48:11 -04:00
|
|
|
this->browser_attach_timeout_);
|
2013-02-27 12:58:36 -05:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2013-01-11 22:18:32 +01:00
|
|
|
CComPtr<IHTMLDocument2> document;
|
|
|
|
|
if (this->GetDocumentFromWindowHandle(process_window_info->hwndBrowser,
|
|
|
|
|
&document)) {
|
|
|
|
|
CComPtr<IHTMLWindow2> window;
|
|
|
|
|
HRESULT hr = document->get_parentWindow(&window);
|
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
|
// http://support.microsoft.com/kb/257717
|
2013-03-07 17:06:31 -05:00
|
|
|
CComPtr<IServiceProvider> provider;
|
|
|
|
|
window->QueryInterface<IServiceProvider>(&provider);
|
2013-01-11 22:18:32 +01:00
|
|
|
if (provider) {
|
|
|
|
|
CComPtr<IServiceProvider> child_provider;
|
|
|
|
|
hr = provider->QueryService(SID_STopLevelBrowser,
|
|
|
|
|
IID_IServiceProvider,
|
|
|
|
|
reinterpret_cast<void**>(&child_provider));
|
|
|
|
|
if (SUCCEEDED(hr)) {
|
2013-07-12 15:00:57 -07:00
|
|
|
CComPtr<IWebBrowser2> browser;
|
2013-01-11 22:18:32 +01:00
|
|
|
hr = child_provider->QueryService(SID_SWebBrowserApp,
|
|
|
|
|
IID_IWebBrowser2,
|
|
|
|
|
reinterpret_cast<void**>(&browser));
|
|
|
|
|
if (SUCCEEDED(hr)) {
|
2013-07-12 15:00:57 -07:00
|
|
|
process_window_info->pBrowser = browser.Detach();
|
2013-01-11 22:18:32 +01:00
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
LOGHR(WARN, hr) << "IServiceProvider::QueryService for SID_SWebBrowserApp failed";
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
LOGHR(WARN, hr) << "IServiceProvider::QueryService for SID_STopLevelBrowser failed";
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
LOG(WARN) << "QueryInterface for IServiceProvider failed";
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
LOGHR(WARN, hr) << "Call to IHTMLDocument2::get_parentWindow failed";
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
*error_message = "Could not get document from window handle";
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-12 15:00:57 -07:00
|
|
|
bool BrowserFactory::AttachToBrowserUsingShellWindows(
|
|
|
|
|
ProcessWindowInfo* process_window_info,
|
|
|
|
|
std::string* error_message) {
|
|
|
|
|
LOG(TRACE) << "Entering BrowserFactory::AttachToBrowserUsingShellWindows";
|
|
|
|
|
|
|
|
|
|
CComPtr<IShellWindows> shell_windows;
|
|
|
|
|
shell_windows.CoCreateInstance(CLSID_ShellWindows);
|
|
|
|
|
|
|
|
|
|
CComPtr<IUnknown> enumerator_unknown;
|
|
|
|
|
HRESULT hr = shell_windows->_NewEnum(&enumerator_unknown);
|
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
|
LOGHR(WARN, hr) << "Unable to get enumerator from IShellWindows interface";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HWND hwnd_browser = NULL;
|
|
|
|
|
CComPtr<IWebBrowser2> browser;
|
|
|
|
|
|
|
|
|
|
clock_t end = clock() + (this->browser_attach_timeout_ / 1000 * CLOCKS_PER_SEC);
|
|
|
|
|
|
|
|
|
|
CComPtr<IEnumVARIANT> enumerator;
|
|
|
|
|
enumerator_unknown->QueryInterface<IEnumVARIANT>(&enumerator);
|
|
|
|
|
while (process_window_info->hwndBrowser == NULL) {
|
|
|
|
|
if (this->browser_attach_timeout_ > 0 && (clock() > end)) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
enumerator->Reset();
|
|
|
|
|
for (CComVariant shell_window_variant;
|
|
|
|
|
enumerator->Next(1, &shell_window_variant, nullptr) == S_OK;
|
|
|
|
|
shell_window_variant.Clear()) {
|
|
|
|
|
|
|
|
|
|
if (shell_window_variant.vt != VT_DISPATCH) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CComPtr<IShellBrowser> shell_browser;
|
|
|
|
|
hr = IUnknown_QueryService(shell_window_variant.pdispVal,
|
|
|
|
|
SID_STopLevelBrowser,
|
|
|
|
|
IID_PPV_ARGS(&shell_browser));
|
|
|
|
|
if (shell_browser) {
|
|
|
|
|
HWND hwnd;
|
|
|
|
|
hr = shell_browser->GetWindow(&hwnd);
|
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
|
::EnumChildWindows(hwnd,
|
|
|
|
|
&BrowserFactory::FindChildWindowForProcess,
|
|
|
|
|
reinterpret_cast<LPARAM>(process_window_info));
|
|
|
|
|
if (process_window_info->hwndBrowser != NULL) {
|
|
|
|
|
hr = shell_window_variant.pdispVal->QueryInterface<IWebBrowser2>(&browser);
|
|
|
|
|
process_window_info->pBrowser = browser.Detach();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (process_window_info->hwndBrowser == NULL) {
|
|
|
|
|
::Sleep(250);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (process_window_info->hwndBrowser == NULL) {
|
|
|
|
|
*error_message = StringUtilities::Format(ATTACH_TIMEOUT_ERROR_MESSAGE,
|
|
|
|
|
process_window_info->dwProcessId,
|
|
|
|
|
this->browser_attach_timeout_);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int BrowserFactory::GetBrowserZoomLevel(IWebBrowser2* browser) {
|
|
|
|
|
LOG(TRACE) << "Entering BrowserFactory::GetBrowserZoomLevel";
|
|
|
|
|
clock_t end = clock() + (this->browser_attach_timeout_ / 1000 * CLOCKS_PER_SEC);
|
|
|
|
|
CComPtr<IDispatch> document_dispatch;
|
|
|
|
|
while (!document_dispatch) {
|
|
|
|
|
if (this->browser_attach_timeout_ > 0 && (clock() > end)) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
browser->get_Document(&document_dispatch);
|
|
|
|
|
|
|
|
|
|
if (!document_dispatch) {
|
|
|
|
|
::Sleep(250);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!document_dispatch) {
|
|
|
|
|
LOG(WARN) << "Call to IWebBrowser2::get_Document failed";
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CComPtr<IHTMLDocument2> document;
|
|
|
|
|
document_dispatch->QueryInterface(&document);
|
|
|
|
|
if (!document) {
|
|
|
|
|
LOG(WARN) << "QueryInterface for IHTMLDocument2 failed.";
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CComPtr<IHTMLWindow2> window;
|
|
|
|
|
HRESULT hr = document->get_parentWindow(&window);
|
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
|
LOGHR(WARN, hr) << "Call to IHTMLDocument2::get_parentWindow failed";
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Test for zoom level = 100%
|
|
|
|
|
int zoom_level = this->GetZoomLevel(document, window);
|
|
|
|
|
return zoom_level;
|
|
|
|
|
}
|
|
|
|
|
|
2013-01-11 22:18:32 +01:00
|
|
|
int BrowserFactory::GetZoomLevel(IHTMLDocument2* document, IHTMLWindow2* window) {
|
|
|
|
|
LOG(TRACE) << "Entering BrowserFactory::GetZoomLevel";
|
|
|
|
|
int zoom = 100; // Chances are the zoom level hasn't been modified....
|
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
|
if (this->ie_major_version_ == 7) {
|
|
|
|
|
CComPtr<IHTMLElement> body;
|
|
|
|
|
hr = document->get_body(&body);
|
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
|
LOGHR(WARN, hr) << "Call to IHTMLDocument2::get_body failed";
|
|
|
|
|
return zoom;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
long offset_width = 0;
|
|
|
|
|
hr = body->get_offsetWidth(&offset_width);
|
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
|
LOGHR(WARN, hr) << "Call to IHTMLElement::get_offsetWidth failed";
|
|
|
|
|
return zoom;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CComPtr<IHTMLElement2> body2;
|
|
|
|
|
hr = body.QueryInterface<IHTMLElement2>(&body2);
|
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
|
LOGHR(WARN, hr) << "Attempt to QueryInterface for IHTMLElement2 failed";
|
|
|
|
|
return zoom;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CComPtr<IHTMLRect> rect;
|
|
|
|
|
hr = body2->getBoundingClientRect(&rect);
|
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
|
LOGHR(WARN, hr) << "Call to IHTMLElement2::getBoundingClientRect failed";
|
|
|
|
|
return zoom;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
long left = 0, right = 0;
|
|
|
|
|
hr = rect->get_left(&left);
|
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
|
LOGHR(WARN, hr) << "Call to IHTMLRect::get_left failed";
|
|
|
|
|
return zoom;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hr = rect->get_right(&right);
|
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
|
LOGHR(WARN, hr) << "Call to IHTMLRect::get_right failed";
|
|
|
|
|
return zoom;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
zoom = static_cast<int>((static_cast<double>(right - left) / offset_width) * 100.0);
|
|
|
|
|
} else if (this->ie_major_version_ >= 8) {
|
|
|
|
|
CComPtr<IHTMLScreen> screen;
|
|
|
|
|
hr = window->get_screen(&screen);
|
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
|
LOGHR(WARN, hr) << "Call to IHTMLWindow2::get_screen failed";
|
|
|
|
|
return zoom;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CComPtr<IHTMLScreen2> screen2;
|
|
|
|
|
hr = screen.QueryInterface<IHTMLScreen2>(&screen2);
|
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
|
LOGHR(WARN, hr) << "Attempt to QueryInterface for IHTMLScreen2 failed";
|
|
|
|
|
return zoom;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
long device_xdpi=0, logical_xdpi = 0;
|
|
|
|
|
hr = screen2->get_deviceXDPI(&device_xdpi);
|
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
|
LOGHR(WARN, hr) << "Call to IHTMLScreen2::get_deviceXDPI failed";
|
|
|
|
|
return zoom;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hr = screen2->get_logicalXDPI(&logical_xdpi);
|
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
|
LOGHR(WARN, hr) << "Call to IHTMLScreen2::get_logicalXDPI failed";
|
|
|
|
|
return zoom;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
zoom = static_cast<int>((static_cast<double>(device_xdpi) / logical_xdpi) * 100.0);
|
|
|
|
|
} else {
|
|
|
|
|
// IE6 case
|
|
|
|
|
zoom = 100;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG(DEBUG) << "Browser zoom level is " << zoom << "%";
|
|
|
|
|
return zoom;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IWebBrowser2* BrowserFactory::CreateBrowser() {
|
|
|
|
|
LOG(TRACE) << "Entering BrowserFactory::CreateBrowser";
|
|
|
|
|
|
Fixing IE driver crash when clicking on link that opens a new window.
When IWebBrowser2::Quit() is called, the wrapper process doesn't exit
right away. When that happens, CoCreateInstance can fail while the
abandoned iexplore.exe instance is still valid. The "right" way to do
this would be to call ::EnumProcesses before calling CoCreateInstance,
finding all of the iexplore.exe processes, waiting for one to exit,
and then proceed. However, there is no way to tell if a process ID
belongs to an Internet Explorer instance, particularly when a 32-bit
process tries to enumerate 64-bit processes on 64-bit Windows. So, we
take the brute force way out, just retrying the call to
CoCreateInstance until it succeeds (the old iexplore.exe process has
exited), or we get a different error code. We also set a 45-second
timeout, with 45 seconds being chosen because it's below the default
60 second HTTP request timeout of most language bindings.
Fixes issue #5848. Fixes issue #7021.
2014-05-08 11:17:09 -04:00
|
|
|
IWebBrowser2* browser = NULL;
|
2013-01-11 22:18:32 +01:00
|
|
|
DWORD context = CLSCTX_LOCAL_SERVER;
|
|
|
|
|
if (this->ie_major_version_ == 7 && this->windows_major_version_ >= 6) {
|
|
|
|
|
// ONLY for IE 7 on Windows Vista. XP and below do not have Protected Mode;
|
|
|
|
|
// Windows 7 shipped with IE8.
|
|
|
|
|
context = context | CLSCTX_ENABLE_CLOAKING;
|
|
|
|
|
}
|
|
|
|
|
|
Fixing IE driver crash when clicking on link that opens a new window.
When IWebBrowser2::Quit() is called, the wrapper process doesn't exit
right away. When that happens, CoCreateInstance can fail while the
abandoned iexplore.exe instance is still valid. The "right" way to do
this would be to call ::EnumProcesses before calling CoCreateInstance,
finding all of the iexplore.exe processes, waiting for one to exit,
and then proceed. However, there is no way to tell if a process ID
belongs to an Internet Explorer instance, particularly when a 32-bit
process tries to enumerate 64-bit processes on 64-bit Windows. So, we
take the brute force way out, just retrying the call to
CoCreateInstance until it succeeds (the old iexplore.exe process has
exited), or we get a different error code. We also set a 45-second
timeout, with 45 seconds being chosen because it's below the default
60 second HTTP request timeout of most language bindings.
Fixes issue #5848. Fixes issue #7021.
2014-05-08 11:17:09 -04:00
|
|
|
HRESULT hr = ::CoCreateInstance(CLSID_InternetExplorer,
|
|
|
|
|
NULL,
|
|
|
|
|
context,
|
|
|
|
|
IID_IWebBrowser2,
|
|
|
|
|
reinterpret_cast<void**>(&browser));
|
|
|
|
|
// When IWebBrowser2::Quit() is called, the wrapper process doesn't
|
|
|
|
|
// exit right away. When that happens, CoCreateInstance can fail while
|
|
|
|
|
// the abandoned iexplore.exe instance is still valid. The "right" way
|
|
|
|
|
// to do this would be to call ::EnumProcesses before calling
|
|
|
|
|
// CoCreateInstance, finding all of the iexplore.exe processes, waiting
|
|
|
|
|
// for one to exit, and then proceed. However, there is no way to tell
|
|
|
|
|
// if a process ID belongs to an Internet Explorer instance, particularly
|
|
|
|
|
// when a 32-bit process tries to enumerate 64-bit processes on 64-bit
|
|
|
|
|
// Windows. So, we'll take the brute force way out, just retrying the call
|
|
|
|
|
// to CoCreateInstance until it succeeds (the old iexplore.exe process has
|
|
|
|
|
// exited), or we get a different error code. We'll also set a 45-second
|
|
|
|
|
// timeout, with 45 seconds being chosen because it's below the default
|
|
|
|
|
// 60 second HTTP request timeout of most language bindings.
|
|
|
|
|
if (FAILED(hr) && HRESULT_CODE(hr) == ERROR_SHUTDOWN_IS_SCHEDULED) {
|
|
|
|
|
LOG(DEBUG) << "CoCreateInstance for IWebBrowser2 failed due to a "
|
|
|
|
|
<< "browser process that has not yet fully exited. Retrying "
|
|
|
|
|
<< "until the browser process exits and a new instance can "
|
|
|
|
|
<< "be successfully created.";
|
|
|
|
|
}
|
|
|
|
|
clock_t timeout = clock() + (45 * CLOCKS_PER_SEC);
|
|
|
|
|
while (FAILED(hr) &&
|
|
|
|
|
HRESULT_CODE(hr) == ERROR_SHUTDOWN_IS_SCHEDULED &&
|
|
|
|
|
clock() < timeout) {
|
|
|
|
|
::Sleep(500);
|
|
|
|
|
hr = ::CoCreateInstance(CLSID_InternetExplorer,
|
|
|
|
|
NULL,
|
|
|
|
|
context,
|
|
|
|
|
IID_IWebBrowser2,
|
|
|
|
|
reinterpret_cast<void**>(&browser));
|
|
|
|
|
}
|
|
|
|
|
if (FAILED(hr) && HRESULT_CODE(hr) != ERROR_SHUTDOWN_IS_SCHEDULED) {
|
|
|
|
|
// If we hit this branch, the CoCreateInstance failed due to an unexpected
|
|
|
|
|
// error, either before we looped, or at some point during the loop. In
|
|
|
|
|
// in either case, there's not much else we can do except log the failure.
|
|
|
|
|
LOGHR(WARN, hr) << "CoCreateInstance for IWebBrowser2 failed.";
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-25 11:32:30 -05:00
|
|
|
if (browser != NULL) {
|
|
|
|
|
browser->put_Visible(VARIANT_TRUE);
|
|
|
|
|
}
|
2013-01-11 22:18:32 +01:00
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-30 15:28:15 -04:00
|
|
|
bool BrowserFactory::CreateLowIntegrityLevelToken(HANDLE* process_token_handle,
|
|
|
|
|
HANDLE* mic_token_handle,
|
|
|
|
|
PSID* sid) {
|
|
|
|
|
LOG(TRACE) << "Entering BrowserFactory::CreateLowIntegrityLevelToken";
|
|
|
|
|
BOOL result = TRUE;
|
|
|
|
|
TOKEN_MANDATORY_LABEL tml = {0};
|
|
|
|
|
|
|
|
|
|
HANDLE process_handle = ::GetCurrentProcess();
|
|
|
|
|
result = ::OpenProcessToken(process_handle,
|
|
|
|
|
MAXIMUM_ALLOWED,
|
|
|
|
|
process_token_handle);
|
|
|
|
|
|
|
|
|
|
if (result) {
|
|
|
|
|
result = ::DuplicateTokenEx(*process_token_handle,
|
|
|
|
|
MAXIMUM_ALLOWED,
|
|
|
|
|
NULL,
|
|
|
|
|
SecurityImpersonation,
|
|
|
|
|
TokenPrimary,
|
|
|
|
|
mic_token_handle);
|
|
|
|
|
if (!result) {
|
|
|
|
|
::CloseHandle(*process_token_handle);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (result) {
|
|
|
|
|
result = ::ConvertStringSidToSid(SDDL_ML_LOW, sid);
|
|
|
|
|
if (result) {
|
|
|
|
|
tml.Label.Attributes = SE_GROUP_INTEGRITY;
|
|
|
|
|
tml.Label.Sid = *sid;
|
|
|
|
|
} else {
|
|
|
|
|
::CloseHandle(*process_token_handle);
|
|
|
|
|
::CloseHandle(*mic_token_handle);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(result) {
|
|
|
|
|
result = ::SetTokenInformation(*mic_token_handle,
|
|
|
|
|
TokenIntegrityLevel,
|
|
|
|
|
&tml,
|
|
|
|
|
sizeof(tml) + ::GetLengthSid(*sid));
|
|
|
|
|
if (!result) {
|
|
|
|
|
::CloseHandle(*process_token_handle);
|
|
|
|
|
::CloseHandle(*mic_token_handle);
|
|
|
|
|
::LocalFree(*sid);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
::CloseHandle(process_handle);
|
|
|
|
|
return result == TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BrowserFactory::InvokeClearCacheUtility(bool use_low_integrity_level) {
|
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
|
std::vector<wchar_t> system_path_buffer(MAX_PATH);
|
|
|
|
|
std::vector<wchar_t> rundll_exe_path_buffer(MAX_PATH);
|
|
|
|
|
std::vector<wchar_t> inetcpl_path_buffer(MAX_PATH);
|
|
|
|
|
std::wstring args = L"";
|
|
|
|
|
|
|
|
|
|
UINT system_path_size = ::GetSystemDirectory(&system_path_buffer[0], MAX_PATH);
|
|
|
|
|
|
|
|
|
|
HANDLE process_token = NULL;
|
|
|
|
|
HANDLE mic_token = NULL;
|
|
|
|
|
PSID sid = NULL;
|
|
|
|
|
|
|
|
|
|
bool can_create_process = true;
|
|
|
|
|
if (!use_low_integrity_level ||
|
|
|
|
|
this->CreateLowIntegrityLevelToken(&process_token, &mic_token, &sid)) {
|
|
|
|
|
if (0 != system_path_size &&
|
|
|
|
|
system_path_size <= static_cast<int>(system_path_buffer.size())) {
|
|
|
|
|
if (::PathCombine(&rundll_exe_path_buffer[0],
|
|
|
|
|
&system_path_buffer[0],
|
|
|
|
|
RUNDLL_EXE_NAME) &&
|
|
|
|
|
::PathCombine(&inetcpl_path_buffer[0],
|
|
|
|
|
&system_path_buffer[0],
|
|
|
|
|
INTERNET_CONTROL_PANEL_APPLET_NAME)) {
|
|
|
|
|
// PathCombine will return NULL if the buffer would be exceeded.
|
|
|
|
|
::PathQuoteSpaces(&rundll_exe_path_buffer[0]);
|
|
|
|
|
::PathQuoteSpaces(&inetcpl_path_buffer[0]);
|
|
|
|
|
args = StringUtilities::Format(CLEAR_CACHE_COMMAND_LINE_ARGS,
|
|
|
|
|
&inetcpl_path_buffer[0],
|
|
|
|
|
CLEAR_CACHE_OPTIONS);
|
|
|
|
|
} else {
|
|
|
|
|
can_create_process = false;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
can_create_process = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (can_create_process) {
|
|
|
|
|
STARTUPINFO start_info;
|
|
|
|
|
::ZeroMemory(&start_info, sizeof(start_info));
|
|
|
|
|
start_info.cb = sizeof(start_info);
|
|
|
|
|
|
|
|
|
|
PROCESS_INFORMATION process_info;
|
|
|
|
|
BOOL is_process_created = FALSE;
|
|
|
|
|
start_info.dwFlags = STARTF_USESHOWWINDOW;
|
|
|
|
|
start_info.wShowWindow = SW_SHOWNORMAL;
|
|
|
|
|
|
|
|
|
|
std::vector<wchar_t> args_buffer(0);
|
|
|
|
|
StringUtilities::ToBuffer(args, &args_buffer);
|
|
|
|
|
// Create the process to run with low or medium rights
|
|
|
|
|
if (use_low_integrity_level) {
|
|
|
|
|
is_process_created = CreateProcessAsUser(mic_token,
|
|
|
|
|
&rundll_exe_path_buffer[0],
|
|
|
|
|
&args_buffer[0],
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
FALSE,
|
|
|
|
|
0,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
&start_info,
|
|
|
|
|
&process_info);
|
|
|
|
|
} else {
|
|
|
|
|
is_process_created = CreateProcess(&rundll_exe_path_buffer[0],
|
|
|
|
|
&args_buffer[0],
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
FALSE,
|
|
|
|
|
0,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
&start_info,
|
|
|
|
|
&process_info);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_process_created) {
|
|
|
|
|
// Wait for the rundll32.exe process to exit.
|
|
|
|
|
::WaitForInputIdle(process_info.hProcess, 5000);
|
|
|
|
|
::WaitForSingleObject(process_info.hProcess, 30000);
|
|
|
|
|
::CloseHandle(process_info.hProcess);
|
|
|
|
|
::CloseHandle(process_info.hThread);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Close the handles opened when creating the
|
|
|
|
|
// low integrity level token
|
|
|
|
|
if (use_low_integrity_level) {
|
|
|
|
|
::CloseHandle(process_token);
|
|
|
|
|
::CloseHandle(mic_token);
|
|
|
|
|
::LocalFree(sid);
|
|
|
|
|
}
|
2013-07-12 15:00:57 -07:00
|
|
|
}
|
2013-07-30 15:28:15 -04:00
|
|
|
}
|
|
|
|
|
|
2013-01-11 22:18:32 +01:00
|
|
|
BOOL CALLBACK BrowserFactory::FindBrowserWindow(HWND hwnd, LPARAM arg) {
|
|
|
|
|
// Could this be an IE instance?
|
|
|
|
|
// 8 == "IeFrame\0"
|
|
|
|
|
// 21 == "Shell DocObject View\0";
|
|
|
|
|
char name[21];
|
|
|
|
|
if (::GetClassNameA(hwnd, name, 21) == 0) {
|
|
|
|
|
// No match found. Skip
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
2013-07-12 15:00:57 -07:00
|
|
|
|
2013-01-11 22:18:32 +01:00
|
|
|
if (strcmp(IE_FRAME_WINDOW_CLASS, name) != 0 &&
|
|
|
|
|
strcmp(SHELL_DOCOBJECT_VIEW_WINDOW_CLASS, name) != 0) {
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return EnumChildWindows(hwnd, FindChildWindowForProcess, arg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BOOL CALLBACK BrowserFactory::FindChildWindowForProcess(HWND hwnd, LPARAM arg) {
|
|
|
|
|
ProcessWindowInfo *process_window_info = reinterpret_cast<ProcessWindowInfo*>(arg);
|
|
|
|
|
|
|
|
|
|
// Could this be an Internet Explorer Server window?
|
|
|
|
|
// 25 == "Internet Explorer_Server\0"
|
|
|
|
|
char name[25];
|
|
|
|
|
if (::GetClassNameA(hwnd, name, 25) == 0) {
|
|
|
|
|
// No match found. Skip
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
2013-07-12 15:00:57 -07:00
|
|
|
|
2013-01-11 22:18:32 +01:00
|
|
|
if (strcmp(IE_SERVER_CHILD_WINDOW_CLASS, name) != 0) {
|
|
|
|
|
return TRUE;
|
|
|
|
|
} else {
|
|
|
|
|
DWORD process_id = NULL;
|
|
|
|
|
::GetWindowThreadProcessId(hwnd, &process_id);
|
|
|
|
|
if (process_window_info->dwProcessId == process_id) {
|
|
|
|
|
// Once we've found the first Internet Explorer_Server window
|
|
|
|
|
// for the process we want, we can stop.
|
|
|
|
|
process_window_info->hwndBrowser = hwnd;
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BOOL CALLBACK BrowserFactory::FindDialogWindowForProcess(HWND hwnd, LPARAM arg) {
|
|
|
|
|
ProcessWindowInfo* process_win_info = reinterpret_cast<ProcessWindowInfo*>(arg);
|
|
|
|
|
|
|
|
|
|
// Could this be an dialog window?
|
|
|
|
|
// 7 == "#32770\0"
|
|
|
|
|
// 34 == "Internet Explorer_TridentDlgFrame\0"
|
|
|
|
|
char name[34];
|
|
|
|
|
if (::GetClassNameA(hwnd, name, 34) == 0) {
|
|
|
|
|
// No match found. Skip
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strcmp(ALERT_WINDOW_CLASS, name) != 0 &&
|
|
|
|
|
strcmp(HTML_DIALOG_WINDOW_CLASS, name) != 0) {
|
|
|
|
|
return TRUE;
|
|
|
|
|
} else {
|
|
|
|
|
// If the window style has the WS_DISABLED bit set or the
|
2013-02-21 11:28:42 -05:00
|
|
|
// WS_VISIBLE bit unset, it can't be handled via the UI,
|
|
|
|
|
// and must not be a visible dialog. Furthermore, if the
|
|
|
|
|
// window style does not display a caption bar, it's not a
|
|
|
|
|
// dialog displayed by the browser, but likely by an add-on
|
|
|
|
|
// (like an antivirus toolbar). Note that checking the caption
|
|
|
|
|
// window style is a hack, and may begin to fail if IE ever
|
|
|
|
|
// changes the style of its alert windows.
|
|
|
|
|
long window_long_style = ::GetWindowLong(hwnd, GWL_STYLE);
|
|
|
|
|
if ((window_long_style & WS_DISABLED) != 0 ||
|
|
|
|
|
(window_long_style & WS_VISIBLE) == 0 ||
|
|
|
|
|
(window_long_style & WS_CAPTION) == 0) {
|
2013-01-11 22:18:32 +01:00
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
DWORD process_id = NULL;
|
|
|
|
|
::GetWindowThreadProcessId(hwnd, &process_id);
|
|
|
|
|
if (process_win_info->dwProcessId == process_id) {
|
|
|
|
|
// Once we've found the first dialog (#32770) window
|
|
|
|
|
// for the process we want, we can stop.
|
|
|
|
|
process_win_info->hwndBrowser = hwnd;
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BrowserFactory::GetExecutableLocation() {
|
|
|
|
|
LOG(TRACE) << "Entering BrowserFactory::GetExecutableLocation";
|
|
|
|
|
|
|
|
|
|
std::wstring class_id;
|
2013-07-10 15:53:29 -04:00
|
|
|
if (RegistryUtilities::GetRegistryValue(HKEY_LOCAL_MACHINE,
|
|
|
|
|
IE_CLSID_REGISTRY_KEY,
|
|
|
|
|
L"",
|
|
|
|
|
&class_id)) {
|
2013-01-11 22:18:32 +01:00
|
|
|
std::wstring location_key = L"SOFTWARE\\Classes\\CLSID\\" +
|
|
|
|
|
class_id +
|
|
|
|
|
L"\\LocalServer32";
|
|
|
|
|
std::wstring executable_location;
|
|
|
|
|
|
2013-07-10 15:53:29 -04:00
|
|
|
if (RegistryUtilities::GetRegistryValue(HKEY_LOCAL_MACHINE,
|
|
|
|
|
location_key,
|
|
|
|
|
L"",
|
|
|
|
|
&executable_location)) {
|
2013-01-11 22:18:32 +01:00
|
|
|
// If the executable location in the registry has an environment
|
|
|
|
|
// variable in it, expand the environment variable to an absolute
|
|
|
|
|
// path.
|
|
|
|
|
DWORD expanded_location_size = ::ExpandEnvironmentStrings(executable_location.c_str(), NULL, 0);
|
2013-07-11 17:46:24 -04:00
|
|
|
std::vector<wchar_t> expanded_location(expanded_location_size);
|
2013-01-11 22:18:32 +01:00
|
|
|
::ExpandEnvironmentStrings(executable_location.c_str(), &expanded_location[0], expanded_location_size);
|
|
|
|
|
executable_location = &expanded_location[0];
|
|
|
|
|
this->ie_executable_location_ = executable_location;
|
|
|
|
|
size_t arg_start_pos = executable_location.find(L" -");
|
|
|
|
|
if (arg_start_pos != std::string::npos) {
|
|
|
|
|
this->ie_executable_location_ = executable_location.substr(0, arg_start_pos);
|
|
|
|
|
}
|
|
|
|
|
if (this->ie_executable_location_.substr(0, 1) == L"\"") {
|
|
|
|
|
this->ie_executable_location_.erase(0, 1);
|
|
|
|
|
this->ie_executable_location_.erase(this->ie_executable_location_.size() - 1, 1);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
LOG(WARN) << "Unable to get IE executable location from registry";
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
LOG(WARN) << "Unable to get IE class id from registry";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BrowserFactory::GetIEVersion() {
|
|
|
|
|
LOG(TRACE) << "Entering BrowserFactory::GetIEVersion";
|
|
|
|
|
|
|
|
|
|
struct LANGANDCODEPAGE {
|
|
|
|
|
WORD language;
|
|
|
|
|
WORD code_page;
|
|
|
|
|
} *lpTranslate;
|
|
|
|
|
|
2013-02-25 11:32:30 -05:00
|
|
|
DWORD dummy = 0;
|
2013-01-11 22:18:32 +01:00
|
|
|
DWORD length = ::GetFileVersionInfoSize(this->ie_executable_location_.c_str(),
|
|
|
|
|
&dummy);
|
|
|
|
|
if (length == 0) {
|
|
|
|
|
// 64-bit Windows 8 has a bug where it does not return the executable location properly
|
|
|
|
|
this->ie_major_version_ = -1;
|
|
|
|
|
LOG(WARN) << "Couldn't find IE version for executable "
|
2013-04-10 00:02:15 +04:00
|
|
|
<< LOGWSTRING(this->ie_executable_location_)
|
2013-01-11 22:18:32 +01:00
|
|
|
<< ", falling back to "
|
|
|
|
|
<< this->ie_major_version_;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
std::vector<BYTE> version_buffer(length);
|
|
|
|
|
::GetFileVersionInfo(this->ie_executable_location_.c_str(),
|
2013-02-25 11:32:30 -05:00
|
|
|
0, /* ignored */
|
2013-01-11 22:18:32 +01:00
|
|
|
length,
|
|
|
|
|
&version_buffer[0]);
|
|
|
|
|
|
|
|
|
|
UINT page_count;
|
|
|
|
|
BOOL query_result = ::VerQueryValue(&version_buffer[0],
|
|
|
|
|
FILE_LANGUAGE_INFO,
|
|
|
|
|
reinterpret_cast<void**>(&lpTranslate),
|
|
|
|
|
&page_count);
|
|
|
|
|
|
|
|
|
|
wchar_t sub_block[MAX_PATH];
|
|
|
|
|
_snwprintf_s(sub_block,
|
|
|
|
|
MAX_PATH,
|
|
|
|
|
MAX_PATH,
|
|
|
|
|
FILE_VERSION_INFO,
|
|
|
|
|
lpTranslate->language,
|
|
|
|
|
lpTranslate->code_page);
|
|
|
|
|
LPVOID value = NULL;
|
|
|
|
|
UINT size;
|
|
|
|
|
query_result = ::VerQueryValue(&version_buffer[0],
|
|
|
|
|
sub_block,
|
|
|
|
|
&value,
|
|
|
|
|
&size);
|
|
|
|
|
std::wstring ie_version;
|
|
|
|
|
ie_version.assign(static_cast<wchar_t*>(value));
|
|
|
|
|
std::wstringstream version_stream(ie_version);
|
|
|
|
|
version_stream >> this->ie_major_version_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BrowserFactory::GetOSVersion() {
|
|
|
|
|
LOG(TRACE) << "Entering BrowserFactory::GetOSVersion";
|
|
|
|
|
|
|
|
|
|
OSVERSIONINFO osVersion;
|
|
|
|
|
osVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
|
|
|
|
|
::GetVersionEx(&osVersion);
|
|
|
|
|
this->windows_major_version_ = osVersion.dwMajorVersion;
|
2013-03-08 16:12:06 -05:00
|
|
|
this->windows_minor_version_ = osVersion.dwMinorVersion;
|
2013-01-11 22:18:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BrowserFactory::ProtectedModeSettingsAreValid() {
|
|
|
|
|
LOG(TRACE) << "Entering BrowserFactory::ProtectedModeSettingsAreValid";
|
|
|
|
|
|
|
|
|
|
bool settings_are_valid = true;
|
|
|
|
|
LOG(DEBUG) << "Detected IE version: " << this->ie_major_version_
|
|
|
|
|
<< ", detected Windows version: " << this->windows_major_version_;
|
|
|
|
|
// Only need to check Protected Mode settings on IE 7 or higher
|
|
|
|
|
// and on Windows Vista or higher. Otherwise, Protected Mode
|
|
|
|
|
// doesn't come into play, and are valid.
|
|
|
|
|
// Documentation of registry settings can be found at the following
|
|
|
|
|
// Microsoft KnowledgeBase article:
|
|
|
|
|
// http://support.microsoft.com/kb/182569
|
|
|
|
|
if (this->ie_major_version_ >= 7 && this->windows_major_version_ >= 6) {
|
|
|
|
|
HKEY key_handle;
|
|
|
|
|
if (ERROR_SUCCESS == ::RegOpenKeyEx(HKEY_CURRENT_USER,
|
|
|
|
|
IE_SECURITY_ZONES_REGISTRY_KEY,
|
|
|
|
|
0,
|
|
|
|
|
KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS,
|
|
|
|
|
&key_handle)) {
|
|
|
|
|
DWORD subkey_count = 0;
|
|
|
|
|
DWORD max_subkey_name_length = 0;
|
|
|
|
|
if (ERROR_SUCCESS == ::RegQueryInfoKey(key_handle,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
&subkey_count,
|
|
|
|
|
&max_subkey_name_length,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL)) {
|
|
|
|
|
int protected_mode_value = -1;
|
2013-07-11 17:17:50 -04:00
|
|
|
std::vector<wchar_t> subkey_name_buffer(max_subkey_name_length + 1);
|
2013-01-11 22:18:32 +01:00
|
|
|
for (size_t index = 0; index < subkey_count; ++index) {
|
|
|
|
|
DWORD number_of_characters_copied = max_subkey_name_length + 1;
|
|
|
|
|
::RegEnumKeyEx(key_handle,
|
|
|
|
|
static_cast<DWORD>(index),
|
|
|
|
|
&subkey_name_buffer[0],
|
|
|
|
|
&number_of_characters_copied,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL);
|
|
|
|
|
std::wstring subkey_name = &subkey_name_buffer[0];
|
|
|
|
|
// Ignore the "My Computer" zone, since it's not displayed
|
|
|
|
|
// in the UI.
|
|
|
|
|
if (subkey_name != ZONE_MY_COMPUTER) {
|
|
|
|
|
int value = this->GetZoneProtectedModeSetting(key_handle,
|
|
|
|
|
subkey_name);
|
|
|
|
|
if (protected_mode_value == -1) {
|
|
|
|
|
protected_mode_value = value;
|
|
|
|
|
} else {
|
|
|
|
|
if (value != protected_mode_value) {
|
|
|
|
|
settings_are_valid = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
LOG(WARN) << "RegQueryInfoKey to get count of zone setting subkeys failed";
|
|
|
|
|
}
|
|
|
|
|
::RegCloseKey(key_handle);
|
|
|
|
|
} else {
|
2013-04-10 00:02:15 +04:00
|
|
|
std::wstring registry_key_string = IE_SECURITY_ZONES_REGISTRY_KEY;
|
2013-01-11 22:18:32 +01:00
|
|
|
LOG(WARN) << "RegOpenKeyEx for zone settings registry key "
|
2013-04-10 00:02:15 +04:00
|
|
|
<< LOGWSTRING(registry_key_string)
|
2013-01-11 22:18:32 +01:00
|
|
|
<< " in HKEY_CURRENT_USER failed";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return settings_are_valid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int BrowserFactory::GetZoneProtectedModeSetting(const HKEY key_handle,
|
|
|
|
|
const std::wstring& zone_subkey_name) {
|
|
|
|
|
LOG(TRACE) << "Entering BrowserFactory::GetZoneProtectedModeSetting";
|
|
|
|
|
|
|
|
|
|
int protected_mode_value = 3;
|
|
|
|
|
HKEY subkey_handle;
|
|
|
|
|
if (ERROR_SUCCESS == ::RegOpenKeyEx(key_handle,
|
|
|
|
|
zone_subkey_name.c_str(),
|
|
|
|
|
0,
|
|
|
|
|
KEY_QUERY_VALUE,
|
|
|
|
|
&subkey_handle)) {
|
|
|
|
|
DWORD value = 0;
|
|
|
|
|
DWORD value_length = sizeof(DWORD);
|
|
|
|
|
if (ERROR_SUCCESS == ::RegQueryValueEx(subkey_handle,
|
|
|
|
|
IE_PROTECTED_MODE_SETTING_VALUE_NAME,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
reinterpret_cast<LPBYTE>(&value),
|
|
|
|
|
&value_length)) {
|
|
|
|
|
LOG(DEBUG) << "Found Protected Mode setting value of "
|
2013-04-10 00:02:15 +04:00
|
|
|
<< value << " for zone " << LOGWSTRING(zone_subkey_name);
|
2013-01-11 22:18:32 +01:00
|
|
|
protected_mode_value = value;
|
|
|
|
|
} else {
|
|
|
|
|
LOG(DEBUG) << "RegQueryValueEx failed for getting Protected Mode setting for a zone: "
|
2013-04-10 00:02:15 +04:00
|
|
|
<< LOGWSTRING(zone_subkey_name);
|
2013-01-11 22:18:32 +01:00
|
|
|
}
|
|
|
|
|
::RegCloseKey(subkey_handle);
|
|
|
|
|
} else {
|
|
|
|
|
// The REG_DWORD value doesn't exist, so we have to return the default
|
|
|
|
|
// value, which is "on" for the Internet and Restricted Sites zones and
|
|
|
|
|
// is "on" for the Local Intranet zone in IE7 only (the default was
|
|
|
|
|
// changed to "off" for Local Intranet in IE8), and "off" everywhere
|
|
|
|
|
// else.
|
|
|
|
|
// Note that a value of 0 in the registry value indicates that Protected
|
|
|
|
|
// Mode is "on" for that zone; a value of 3 indicates that Protected Mode
|
|
|
|
|
// is "off" for that zone.
|
|
|
|
|
if (zone_subkey_name == ZONE_INTERNET ||
|
|
|
|
|
zone_subkey_name == ZONE_RESTRICTED_SITES ||
|
|
|
|
|
(zone_subkey_name == ZONE_LOCAL_INTRANET && this->ie_major_version_ == 7)) {
|
|
|
|
|
protected_mode_value = 0;
|
|
|
|
|
}
|
|
|
|
|
LOG(DEBUG) << "Protected Mode zone setting value does not exist for zone "
|
2013-04-10 00:02:15 +04:00
|
|
|
<< LOGWSTRING(zone_subkey_name) << ". Using default value of "
|
2013-01-11 22:18:32 +01:00
|
|
|
<< protected_mode_value;
|
|
|
|
|
}
|
|
|
|
|
return protected_mode_value;
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-12 15:00:57 -07:00
|
|
|
} // namespace webdriver
|