Skip to content

Commit 766a4cd

Browse files
committed
Fixing IE cookie retrieval for IE 10 and 11
Internet Explorer relies on a Windows component called WinINet to communicate over the Internet. This includes WinINet APIs for getting and setting cookies. Prior to Windows 10, persistent cookies were stored in files on the hard drive. A change in WinINet moved the persistent cookie storage from files on the disk to an Extensible Storage Engine (ESE or "Jet Blue") database, which rendered them unreadable by the IE driver. This commit restores the functionality of getting cookies by using an API function called InternetGetCookieEx2. This API function was introduced in the Windows 8.1 SDK, which means that it won't be available to older versions of WinINet. In that case, we fall back to the original behavior of attempting to parse cookie files from the disk. There is a chance that the detection of the availability of this API is flawed in the IE driver.
1 parent a66bdf5 commit 766a4cd

File tree

4 files changed

+218
-101
lines changed

4 files changed

+218
-101
lines changed

cpp/iedriver/CookieManager.cpp

Lines changed: 200 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ void CookieManager::Initialize(HWND window_handle) {
5050
this->window_handle_ = window_handle;
5151
}
5252

53+
bool CookieManager::IsAdvancedCookiesApi() {
54+
FARPROC address = NULL;
55+
HMODULE wininet_handle = ::GetModuleHandle(L"wininet");
56+
if (wininet_handle) {
57+
address = ::GetProcAddress(wininet_handle, "InternetGetCookieEx2");
58+
}
59+
return address != NULL;
60+
}
61+
5362
int CookieManager::SetCookie(const std::string& url,
5463
const BrowserCookie& cookie) {
5564
std::string full_data = url + "|" + cookie.ToString();
@@ -105,70 +114,93 @@ int CookieManager::GetCookies(const std::string& url,
105114
}
106115
hook.Initialize(hook_settings);
107116

108-
// Get all cookies for the current URL visible to JavaScript.
109-
std::wstring scriptable_cookie_string =
110-
this->SendGetCookieMessage(wide_url,
111-
WD_GET_SCRIPTABLE_COOKIES,
112-
&hook);
113-
std::map<std::string, std::string> scriptable_cookies;
114-
this->ParseCookieString(scriptable_cookie_string, &scriptable_cookies);
115-
116-
// Get all cookies for the insecure version of the current URL,
117-
// which will include HttpOnly cookies.
118-
std::wstring insecure_cookie_string =
119-
this->SendGetCookieMessage(wide_url,
120-
WD_GET_HTTPONLY_COOKIES,
121-
&hook);
122-
std::map<std::string, std::string> insecure_cookies;
123-
this->ParseCookieString(insecure_cookie_string, &insecure_cookies);
124-
125-
// Get all cookies for the current secure URL. This will include
126-
// HttpOnly cookies.
127-
std::wstring secure_cookie_string =
128-
this->SendGetCookieMessage(wide_url,
129-
WD_GET_SECURE_COOKIES,
130-
&hook);
131-
std::map<std::string, std::string> secure_cookies;
132-
this->ParseCookieString(secure_cookie_string, &secure_cookies);
133-
134-
// Get all of the persistent cookie files in the cache for the
135-
// URL currently being browsed.
136-
std::wstring file_list = this->SendGetCookieMessage(
137-
wide_url,
138-
WD_GET_COOKIE_CACHE_FILES,
139-
&hook);
140-
std::vector<std::wstring> files;
141-
StringUtilities::Split(file_list, L"|", &files);
142-
143-
// Parse the persistent cookie files to produce a list of
144-
// cookies.
145-
std::map<std::string, BrowserCookie> persistent_cookies;
146-
for (std::vector<std::wstring>::const_iterator file_iterator = files.begin();
147-
file_iterator != files.end();
148-
++file_iterator) {
149-
this->ReadPersistentCookieFile(*file_iterator,
150-
is_secure_url,
151-
&persistent_cookies);
152-
}
117+
bool supports_advanced_api = this->IsAdvancedCookiesApi();
118+
if (supports_advanced_api) {
119+
// The version of WinINet installed supports the InternetGetCookieEx2
120+
// API, which gets all cookies (session and persistent) at once.
121+
std::wstring raw_cookie_data =
122+
this->SendGetCookieMessage(wide_url,
123+
WD_GET_ALL_COOKIES,
124+
&hook);
125+
std::string all_cookies_list = StringUtilities::ToString(raw_cookie_data);
126+
std::map<std::string, BrowserCookie> cookies;
127+
this->ParseCookieList(all_cookies_list,
128+
is_secure_url,
129+
&cookies);
130+
std::map<std::string, BrowserCookie>::const_iterator cookie_iterator;
131+
for (cookie_iterator = cookies.begin();
132+
cookie_iterator != cookies.end();
133+
++cookie_iterator) {
134+
all_cookies->push_back(cookie_iterator->second);
135+
}
136+
} else {
137+
// Get all cookies for the current URL visible to JavaScript.
138+
std::wstring scriptable_cookie_string =
139+
this->SendGetCookieMessage(wide_url,
140+
WD_GET_SCRIPTABLE_COOKIES,
141+
&hook);
142+
std::map<std::string, std::string> scriptable_cookies;
143+
this->ParseCookieString(scriptable_cookie_string, &scriptable_cookies);
144+
145+
// Get all cookies for the insecure version of the current URL,
146+
// which will include HttpOnly cookies.
147+
std::wstring insecure_cookie_string =
148+
this->SendGetCookieMessage(wide_url,
149+
WD_GET_HTTPONLY_COOKIES,
150+
&hook);
151+
std::map<std::string, std::string> insecure_cookies;
152+
this->ParseCookieString(insecure_cookie_string, &insecure_cookies);
153+
154+
// Get all cookies for the current secure URL. This will include
155+
// HttpOnly cookies.
156+
std::wstring secure_cookie_string =
157+
this->SendGetCookieMessage(wide_url,
158+
WD_GET_SECURE_COOKIES,
159+
&hook);
160+
std::map<std::string, std::string> secure_cookies;
161+
this->ParseCookieString(secure_cookie_string, &secure_cookies);
162+
163+
// Get all of the persistent cookie files in the cache for the
164+
// URL currently being browsed.
165+
std::wstring file_list =
166+
this->SendGetCookieMessage(wide_url,
167+
WD_GET_COOKIE_CACHE_FILES,
168+
&hook);
169+
std::vector<std::wstring> files;
170+
StringUtilities::Split(file_list, L"|", &files);
171+
172+
// Parse the persistent cookie files to produce a list of
173+
// cookies.
174+
std::map<std::string, BrowserCookie> persistent_cookies;
175+
std::vector<std::wstring>::const_iterator file_iterator;
176+
for (file_iterator = files.begin();
177+
file_iterator != files.end();
178+
++file_iterator) {
179+
std::string cookie_file_contents = this->ReadCookieFile(*file_iterator);
180+
this->ParseCookieList(cookie_file_contents,
181+
is_secure_url,
182+
&persistent_cookies);
183+
}
153184

154-
// Loop through the entire list of cookies, including HttpOnly and secure
155-
// cookies. If the cookie exists as a persistent cookie, use its data from
156-
// the cache. If the cookie is found in the list of cookies visible to
157-
// JavaScript, set the HttpOnly property of the cookie to false. If the
158-
// cookie is found in the list of cookies set on the insecure version of
159-
// the URL, set the Secure property of the cookie to false.
160-
std::map<std::string, std::string>::const_iterator it = secure_cookies.begin();
161-
for (; it != secure_cookies.end(); ++it) {
162-
BrowserCookie browser_cookie;
163-
if (persistent_cookies.find(it->first) != persistent_cookies.end()) {
164-
browser_cookie = persistent_cookies[it->first];
165-
} else {
166-
browser_cookie.set_name(it->first);
167-
browser_cookie.set_value(it->second);
168-
browser_cookie.set_is_httponly(scriptable_cookies.find(it->first) == scriptable_cookies.end());
169-
browser_cookie.set_is_secure(insecure_cookies.find(it->first) == insecure_cookies.end());
185+
// Loop through the entire list of cookies, including HttpOnly and secure
186+
// cookies. If the cookie exists as a persistent cookie, use its data from
187+
// the cache. If the cookie is found in the list of cookies visible to
188+
// JavaScript, set the HttpOnly property of the cookie to false. If the
189+
// cookie is found in the list of cookies set on the insecure version of
190+
// the URL, set the Secure property of the cookie to false.
191+
std::map<std::string, std::string>::const_iterator it = secure_cookies.begin();
192+
for (; it != secure_cookies.end(); ++it) {
193+
BrowserCookie browser_cookie;
194+
if (persistent_cookies.find(it->first) != persistent_cookies.end()) {
195+
browser_cookie = persistent_cookies[it->first];
196+
} else {
197+
browser_cookie.set_name(it->first);
198+
browser_cookie.set_value(it->second);
199+
browser_cookie.set_is_httponly(scriptable_cookies.find(it->first) == scriptable_cookies.end());
200+
browser_cookie.set_is_secure(insecure_cookies.find(it->first) == insecure_cookies.end());
201+
}
202+
all_cookies->push_back(browser_cookie);
170203
}
171-
all_cookies->push_back(browser_cookie);
172204
}
173205
return WD_SUCCESS;
174206
}
@@ -252,10 +284,8 @@ bool CookieManager::RecurseCookieDomain(const std::string& url,
252284
return status == WD_SUCCESS;
253285
}
254286

255-
void CookieManager::ReadPersistentCookieFile(const std::wstring& file_name,
256-
const bool include_secure_cookies,
257-
std::map<std::string, BrowserCookie>* cookies) {
258-
LOG(TRACE) << "Entering CookieManager::ReadPersistentCookieFile";
287+
std::string CookieManager::ReadCookieFile(const std::wstring& file_name) {
288+
LOG(TRACE) << "Entering CookieManager::ReadCookieFile";
259289
HANDLE file_handle = ::CreateFile(file_name.c_str(),
260290
GENERIC_READ,
261291
FILE_SHARE_READ | FILE_SHARE_WRITE,
@@ -274,6 +304,13 @@ void CookieManager::ReadPersistentCookieFile(const std::wstring& file_name,
274304
// Null-terminate and convert to a string for easier manipulation.
275305
file_content[bytes_read - 1] = '\0';
276306
std::string cookie_file_contents = &file_content[0];
307+
return cookie_file_contents;
308+
}
309+
310+
void CookieManager::ParseCookieList(const std::string& cookie_file_contents,
311+
const bool include_secure_cookies,
312+
std::map<std::string, BrowserCookie>* cookies) {
313+
LOG(TRACE) << "Entering CookieManager::ParseCookieList";
277314

278315
// Each cookie in the file is a record structure separated by
279316
// a line containing a single asterisk ('*'). Split the file
@@ -282,12 +319,12 @@ void CookieManager::ReadPersistentCookieFile(const std::wstring& file_name,
282319
StringUtilities::Split(cookie_file_contents,
283320
"\n*\n",
284321
&persistent_cookie_strings);
285-
std::vector<std::string>::const_iterator cookie_string_iterator = persistent_cookie_strings.begin();
286-
for (;
322+
std::vector<std::string>::const_iterator cookie_string_iterator;
323+
for (cookie_string_iterator = persistent_cookie_strings.begin();
287324
cookie_string_iterator != persistent_cookie_strings.end();
288325
++cookie_string_iterator) {
289326
BrowserCookie persistent_cookie =
290-
this->ParsePersistentCookieInfo(*cookie_string_iterator);
327+
this->ParseSingleCookie(*cookie_string_iterator);
291328
if (include_secure_cookies || !persistent_cookie.is_secure()) {
292329
// Omit the cookie if it's 'secure' flag is set and we are *not*
293330
// browsing using SSL.
@@ -298,12 +335,13 @@ void CookieManager::ReadPersistentCookieFile(const std::wstring& file_name,
298335
}
299336
}
300337

301-
BrowserCookie CookieManager::ParsePersistentCookieInfo(const std::string& cookie) {
338+
BrowserCookie CookieManager::ParseSingleCookie(const std::string& cookie) {
302339
LOG(TRACE) << "Entering CookieManager::ParsePersistentCookieInfo";
303-
// Persistent cookies are read from out of the cached
304-
// files on disk. Each cookie is represented by 8 lines
305-
// in the file separated by line feed (0xA) characters,
306-
// with the following format:
340+
// Cookies represented by a structured string record type.
341+
// This structure is modeled after how some versions of IE
342+
// stored perisitent cookeis as files on disk. Each cookie
343+
// is represented by 8 lines in the file separated by line
344+
// feed (0xA) characters, with the following format:
307345
//
308346
// cookie_name
309347
// cookie_value
@@ -331,16 +369,18 @@ BrowserCookie CookieManager::ParsePersistentCookieInfo(const std::string& cookie
331369
cookie_to_return.set_is_secure(INTERNET_COOKIE_IS_SECURE == (INTERNET_COOKIE_IS_SECURE & flags));
332370
cookie_to_return.set_is_httponly(INTERNET_COOKIE_HTTPONLY == (INTERNET_COOKIE_HTTPONLY & flags));
333371

334-
unsigned long expiry_time_low = strtoul(cookie_parts[4].c_str(), NULL, 10);
335-
unsigned long expiry_time_high = strtoul(cookie_parts[5].c_str(), NULL, 10);
336-
unsigned long long expiration_time = (expiry_time_high * static_cast<long long>(pow(2.0, 32))) + expiry_time_low;
337-
338-
// Cookie expiration time is stored in the file as the number
339-
// of 100-nanosecond ticks since 1 January 1601 12:00:00 AM GMT.
340-
// We need the number of seconds since 1 January 1970 12:00:00 AM GMT.
341-
// This is the conversion.
342-
unsigned long cookie_expiration_time = static_cast<unsigned long>((expiration_time / TICKS_PER_SECOND) - UNIX_TIME_OFFSET_SECONDS);
343-
cookie_to_return.set_expiration_time(cookie_expiration_time);
372+
if (cookie_parts[4].size() > 0 && cookie_parts[5].size() > 0) {
373+
unsigned long expiry_time_low = strtoul(cookie_parts[4].c_str(), NULL, 10);
374+
unsigned long expiry_time_high = strtoul(cookie_parts[5].c_str(), NULL, 10);
375+
unsigned long long expiration_time = (expiry_time_high * static_cast<long long>(pow(2.0, 32))) + expiry_time_low;
376+
377+
// Cookie expiration time is stored in the file as the number
378+
// of 100-nanosecond ticks since 1 January 1601 12:00:00 AM GMT.
379+
// We need the number of seconds since 1 January 1970 12:00:00 AM GMT.
380+
// This is the conversion.
381+
unsigned long cookie_expiration_time = static_cast<unsigned long>((expiration_time / TICKS_PER_SECOND) - UNIX_TIME_OFFSET_SECONDS);
382+
cookie_to_return.set_expiration_time(cookie_expiration_time);
383+
}
344384
return cookie_to_return;
345385
}
346386

@@ -417,6 +457,79 @@ LRESULT CALLBACK CookieWndProc(int nCode, WPARAM wParam, LPARAM lParam) {
417457
if (WM_COPYDATA == call_window_proc_struct->message) {
418458
COPYDATASTRUCT* data = reinterpret_cast<COPYDATASTRUCT*>(call_window_proc_struct->lParam);
419459
webdriver::HookProcessor::CopyDataToBuffer(data->cbData, data->lpData);
460+
} else if (WD_GET_ALL_COOKIES == call_window_proc_struct->message) {
461+
std::wstring url = webdriver::HookProcessor::CopyWStringFromBuffer();
462+
int driver_process_id = static_cast<int>(call_window_proc_struct->wParam);
463+
464+
CComPtr<IUri> uri_pointer;
465+
HRESULT hr = ::CreateUri(url.c_str(), Uri_CREATE_ALLOW_RELATIVE, 0, &uri_pointer);
466+
DWORD scheme = 0;
467+
uri_pointer->GetScheme(&scheme);
468+
CComBSTR scheme_bstr;
469+
uri_pointer->GetSchemeName(&scheme_bstr);
470+
CComBSTR host_bstr;
471+
uri_pointer->GetHost(&host_bstr);
472+
CComBSTR path_bstr;
473+
uri_pointer->GetPath(&path_bstr);
474+
475+
// Get only the cookies for the base URL, omitting port, if there is one.
476+
// N.B., we only return cookies secure cookies when browsing a site using
477+
// SSL. The browser won't see cookies with the 'secure' flag for sites
478+
// visited using plain http.
479+
std::wstring parsed_uri = L"http";
480+
if ((WD_GET_SECURE_COOKIES == call_window_proc_struct->message ||
481+
WD_GET_SCRIPTABLE_COOKIES == call_window_proc_struct->message) &&
482+
URL_SCHEME_HTTPS == scheme) {
483+
parsed_uri.append(L"s");
484+
}
485+
parsed_uri.append(L"://");
486+
parsed_uri.append(host_bstr);
487+
parsed_uri.append(path_bstr);
488+
489+
// Allocate a static array for cookies, since IE is documented
490+
// to limit the number of cookies to 50.
491+
DWORD cookie_count = 0;
492+
INTERNET_COOKIE2* cookie_pointer;
493+
DWORD success = ::InternetGetCookieEx2(parsed_uri.c_str(),
494+
NULL,
495+
INTERNET_COOKIE_NON_SCRIPT,
496+
&cookie_pointer,
497+
&cookie_count);
498+
if (success == 0) {
499+
// Mimic the format of the old persistent cookie files for ease of
500+
// transmission back to the driver and parsing.
501+
std::wstring all_cookies = L"";
502+
for (int cookie_index = 0; cookie_index < cookie_count; ++cookie_index) {
503+
if (all_cookies.size() > 0) {
504+
all_cookies.append(L"\n*\n");
505+
}
506+
INTERNET_COOKIE2* current_cookie = cookie_pointer + cookie_index;
507+
std::wstring cookie_name = current_cookie->pwszName;
508+
std::wstring cookie_value = current_cookie->pwszValue;
509+
std::wstring cookie_domain = current_cookie->pwszDomain;
510+
std::wstring cookie_path = current_cookie->pwszPath;
511+
DWORD flags = current_cookie->dwFlags;
512+
FILETIME expires = current_cookie->ftExpires;
513+
all_cookies.append(cookie_name).append(L"\n");
514+
all_cookies.append(cookie_value).append(L"\n");
515+
all_cookies.append(cookie_domain).append(L"/").append(cookie_path).append(L"\n");
516+
all_cookies.append(std::to_wstring(flags)).append(L"\n");
517+
// If the expiration time is set, add it to the string for the cookie.
518+
// If not, append empty fields to the record so subsequent parsing
519+
// of the string will still work.
520+
if (current_cookie->fExpiresSet) {
521+
all_cookies.append(std::to_wstring(expires.dwLowDateTime)).append(L"\n");
522+
all_cookies.append(std::to_wstring(expires.dwHighDateTime)).append(L"\n");
523+
} else {
524+
all_cookies.append(L"\n\n");
525+
}
526+
}
527+
::InternetFreeCookies(cookie_pointer, cookie_count);
528+
webdriver::HookProcessor::CopyWStringToBuffer(all_cookies);
529+
} else {
530+
webdriver::HookProcessor::SetDataBufferSize(sizeof(wchar_t));
531+
}
532+
webdriver::HookProcessor::WriteBufferToPipe(driver_process_id);
420533
} else if (WD_GET_HTTPONLY_COOKIES == call_window_proc_struct->message ||
421534
WD_GET_SCRIPTABLE_COOKIES == call_window_proc_struct->message ||
422535
WD_GET_SECURE_COOKIES == call_window_proc_struct->message) {
@@ -425,7 +538,7 @@ LRESULT CALLBACK CookieWndProc(int nCode, WPARAM wParam, LPARAM lParam) {
425538

426539
DWORD get_cookie_flags = 0;
427540
if (WD_GET_HTTPONLY_COOKIES == call_window_proc_struct->message ||
428-
WD_GET_SECURE_COOKIES == call_window_proc_struct->message) {
541+
WD_GET_SECURE_COOKIES == call_window_proc_struct->message) {
429542
get_cookie_flags = INTERNET_COOKIE_HTTPONLY;
430543
}
431544

@@ -439,14 +552,14 @@ LRESULT CALLBACK CookieWndProc(int nCode, WPARAM wParam, LPARAM lParam) {
439552
uri_pointer->GetHost(&host_bstr);
440553
CComBSTR path_bstr;
441554
uri_pointer->GetPath(&path_bstr);
442-
555+
443556
// Get only the cookies for the base URL, omitting port, if there is one.
444557
// N.B., we only return cookies secure cookies when browsing a site using
445558
// SSL. The browser won't see cookies with the 'secure' flag for sites
446559
// visited using plain http.
447560
std::wstring parsed_uri = L"http";
448561
if ((WD_GET_SECURE_COOKIES == call_window_proc_struct->message ||
449-
WD_GET_SCRIPTABLE_COOKIES == call_window_proc_struct->message) &&
562+
WD_GET_SCRIPTABLE_COOKIES == call_window_proc_struct->message) &&
450563
URL_SCHEME_HTTPS == scheme) {
451564
parsed_uri.append(L"s");
452565
}

cpp/iedriver/CookieManager.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,18 @@ class CookieManager {
4545
HookProcessor* hook);
4646
void ParseCookieString(const std::wstring& cookie_string,
4747
std::map<std::string, std::string>* cookies);
48-
BrowserCookie ParsePersistentCookieInfo(const std::string& cookie);
49-
void ReadPersistentCookieFile(const std::wstring& file_name,
50-
const bool include_secure_cookies,
51-
std::map<std::string, BrowserCookie>* cookies);
48+
BrowserCookie ParseSingleCookie(const std::string& cookie);
49+
void ParseCookieList(const std::string& cookie_file_contents,
50+
const bool include_secure_cookies,
51+
std::map<std::string, BrowserCookie>* cookies);
52+
std::string ReadCookieFile(const std::wstring& file_name);
5253

5354
bool RecursivelyDeleteCookie(const std::string& url, const BrowserCookie& cookie);
5455
bool RecurseCookiePath(const std::string& url, const BrowserCookie& cookie);
5556
bool RecurseCookieDomain(const std::string& url, const BrowserCookie& cookie);
5657

58+
bool IsAdvancedCookiesApi(void);
59+
5760
HWND window_handle_;
5861
};
5962

0 commit comments

Comments
 (0)