Release 1.4.1.
[wine] / dlls / urlmon / session.c
1 /*
2  * Copyright 2005-2006 Jacek Caban for CodeWeavers
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18
19 #include "urlmon_main.h"
20 #include "winreg.h"
21
22 #include "wine/debug.h"
23
24 WINE_DEFAULT_DEBUG_CHANNEL(urlmon);
25
26 typedef struct name_space {
27     LPWSTR protocol;
28     IClassFactory *cf;
29     CLSID clsid;
30     BOOL urlmon;
31
32     struct name_space *next;
33 } name_space;
34
35 typedef struct mime_filter {
36     IClassFactory *cf;
37     CLSID clsid;
38     LPWSTR mime;
39
40     struct mime_filter *next;
41 } mime_filter;
42
43 static name_space *name_space_list = NULL;
44 static mime_filter *mime_filter_list = NULL;
45
46 static CRITICAL_SECTION session_cs;
47 static CRITICAL_SECTION_DEBUG session_cs_dbg =
48 {
49     0, 0, &session_cs,
50     { &session_cs_dbg.ProcessLocksList, &session_cs_dbg.ProcessLocksList },
51       0, 0, { (DWORD_PTR)(__FILE__ ": session") }
52 };
53 static CRITICAL_SECTION session_cs = { &session_cs_dbg, -1, 0, 0, 0, 0 };
54
55 static const WCHAR internet_settings_keyW[] =
56     {'S','O','F','T','W','A','R','E',
57      '\\','M','i','c','r','o','s','o','f','t',
58      '\\','W','i','n','d','o','w','s',
59      '\\','C','u','r','r','e','n','t','V','e','r','s','i','o','n',
60      '\\','I','n','t','e','r','n','e','t',' ','S','e','t','t','i','n','g','s',0};
61
62 static name_space *find_name_space(LPCWSTR protocol)
63 {
64     name_space *iter;
65
66     for(iter = name_space_list; iter; iter = iter->next) {
67         if(!strcmpW(iter->protocol, protocol))
68             return iter;
69     }
70
71     return NULL;
72 }
73
74 static HRESULT get_protocol_cf(LPCWSTR schema, DWORD schema_len, CLSID *pclsid, IClassFactory **ret)
75 {
76     WCHAR str_clsid[64];
77     HKEY hkey = NULL;
78     DWORD res, type, size;
79     CLSID clsid;
80     LPWSTR wszKey;
81     HRESULT hres;
82
83     static const WCHAR wszProtocolsKey[] =
84         {'P','R','O','T','O','C','O','L','S','\\','H','a','n','d','l','e','r','\\'};
85     static const WCHAR wszCLSID[] = {'C','L','S','I','D',0};
86
87     wszKey = heap_alloc(sizeof(wszProtocolsKey)+(schema_len+1)*sizeof(WCHAR));
88     memcpy(wszKey, wszProtocolsKey, sizeof(wszProtocolsKey));
89     memcpy(wszKey + sizeof(wszProtocolsKey)/sizeof(WCHAR), schema, (schema_len+1)*sizeof(WCHAR));
90
91     res = RegOpenKeyW(HKEY_CLASSES_ROOT, wszKey, &hkey);
92     heap_free(wszKey);
93     if(res != ERROR_SUCCESS) {
94         TRACE("Could not open protocol handler key\n");
95         return MK_E_SYNTAX;
96     }
97     
98     size = sizeof(str_clsid);
99     res = RegQueryValueExW(hkey, wszCLSID, NULL, &type, (LPBYTE)str_clsid, &size);
100     RegCloseKey(hkey);
101     if(res != ERROR_SUCCESS || type != REG_SZ) {
102         WARN("Could not get protocol CLSID res=%d\n", res);
103         return MK_E_SYNTAX;
104     }
105
106     hres = CLSIDFromString(str_clsid, &clsid);
107     if(FAILED(hres)) {
108         WARN("CLSIDFromString failed: %08x\n", hres);
109         return hres;
110     }
111
112     if(pclsid)
113         *pclsid = clsid;
114
115     if(!ret)
116         return S_OK;
117
118     hres = CoGetClassObject(&clsid, CLSCTX_INPROC_SERVER, NULL, &IID_IClassFactory, (void**)ret);
119     return SUCCEEDED(hres) ? S_OK : MK_E_SYNTAX;
120 }
121
122 static HRESULT register_namespace(IClassFactory *cf, REFIID clsid, LPCWSTR protocol, BOOL urlmon_protocol)
123 {
124     name_space *new_name_space;
125
126     new_name_space = heap_alloc(sizeof(name_space));
127
128     if(!urlmon_protocol)
129         IClassFactory_AddRef(cf);
130     new_name_space->cf = cf;
131     new_name_space->clsid = *clsid;
132     new_name_space->urlmon = urlmon_protocol;
133     new_name_space->protocol = heap_strdupW(protocol);
134
135     EnterCriticalSection(&session_cs);
136
137     new_name_space->next = name_space_list;
138     name_space_list = new_name_space;
139
140     LeaveCriticalSection(&session_cs);
141
142     return S_OK;
143 }
144
145 static HRESULT unregister_namespace(IClassFactory *cf, LPCWSTR protocol)
146 {
147     name_space *iter, *last = NULL;
148
149     EnterCriticalSection(&session_cs);
150
151     for(iter = name_space_list; iter; iter = iter->next) {
152         if(iter->cf == cf && !strcmpW(iter->protocol, protocol))
153             break;
154         last = iter;
155     }
156
157     if(iter) {
158         if(last)
159             last->next = iter->next;
160         else
161             name_space_list = iter->next;
162     }
163
164     LeaveCriticalSection(&session_cs);
165
166     if(iter) {
167         if(!iter->urlmon)
168             IClassFactory_Release(iter->cf);
169         heap_free(iter->protocol);
170         heap_free(iter);
171     }
172
173     return S_OK;
174 }
175
176
177 void register_urlmon_namespace(IClassFactory *cf, REFIID clsid, LPCWSTR protocol, BOOL do_register)
178 {
179     if(do_register)
180         register_namespace(cf, clsid, protocol, TRUE);
181     else
182         unregister_namespace(cf, protocol);
183 }
184
185 BOOL is_registered_protocol(LPCWSTR url)
186 {
187     DWORD schema_len;
188     WCHAR schema[64];
189     HRESULT hres;
190
191     hres = CoInternetParseUrl(url, PARSE_SCHEMA, 0, schema, sizeof(schema)/sizeof(schema[0]),
192             &schema_len, 0);
193     if(FAILED(hres))
194         return FALSE;
195
196     return get_protocol_cf(schema, schema_len, NULL, NULL) == S_OK;
197 }
198
199 IInternetProtocolInfo *get_protocol_info(LPCWSTR url)
200 {
201     IInternetProtocolInfo *ret = NULL;
202     IClassFactory *cf;
203     name_space *ns;
204     WCHAR schema[64];
205     DWORD schema_len;
206     HRESULT hres;
207
208     hres = CoInternetParseUrl(url, PARSE_SCHEMA, 0, schema, sizeof(schema)/sizeof(schema[0]),
209             &schema_len, 0);
210     if(FAILED(hres) || !schema_len)
211         return NULL;
212
213     EnterCriticalSection(&session_cs);
214
215     ns = find_name_space(schema);
216     if(ns && !ns->urlmon) {
217         hres = IClassFactory_QueryInterface(ns->cf, &IID_IInternetProtocolInfo, (void**)&ret);
218         if(FAILED(hres))
219             hres = IClassFactory_CreateInstance(ns->cf, NULL, &IID_IInternetProtocolInfo, (void**)&ret);
220     }
221
222     LeaveCriticalSection(&session_cs);
223
224     if(ns && SUCCEEDED(hres))
225         return ret;
226
227     hres = get_protocol_cf(schema, schema_len, NULL, &cf);
228     if(FAILED(hres))
229         return NULL;
230
231     hres = IClassFactory_QueryInterface(cf, &IID_IInternetProtocolInfo, (void**)&ret);
232     if(FAILED(hres))
233         IClassFactory_CreateInstance(cf, NULL, &IID_IInternetProtocolInfo, (void**)&ret);
234     IClassFactory_Release(cf);
235
236     return ret;
237 }
238
239 HRESULT get_protocol_handler(IUri *uri, CLSID *clsid, BOOL *urlmon_protocol, IClassFactory **ret)
240 {
241     name_space *ns;
242     BSTR scheme;
243     HRESULT hres;
244
245     *ret = NULL;
246
247     /* FIXME: Avoid GetSchemeName call for known schemes */
248     hres = IUri_GetSchemeName(uri, &scheme);
249     if(FAILED(hres))
250         return hres;
251
252     EnterCriticalSection(&session_cs);
253
254     ns = find_name_space(scheme);
255     if(ns) {
256         *ret = ns->cf;
257         IClassFactory_AddRef(*ret);
258         if(clsid)
259             *clsid = ns->clsid;
260         if(urlmon_protocol)
261             *urlmon_protocol = ns->urlmon;
262     }
263
264     LeaveCriticalSection(&session_cs);
265
266     if(*ret) {
267         hres = S_OK;
268     }else {
269         if(urlmon_protocol)
270             *urlmon_protocol = FALSE;
271         hres = get_protocol_cf(scheme, SysStringLen(scheme), clsid, ret);
272     }
273
274     SysFreeString(scheme);
275     return hres;
276 }
277
278 IInternetProtocol *get_mime_filter(LPCWSTR mime)
279 {
280     IClassFactory *cf = NULL;
281     IInternetProtocol *ret;
282     mime_filter *iter;
283     HRESULT hres;
284
285     EnterCriticalSection(&session_cs);
286
287     for(iter = mime_filter_list; iter; iter = iter->next) {
288         if(!strcmpW(iter->mime, mime)) {
289             cf = iter->cf;
290             break;
291         }
292     }
293
294     LeaveCriticalSection(&session_cs);
295
296     if(!cf)
297         return NULL;
298
299     hres = IClassFactory_CreateInstance(cf, NULL, &IID_IInternetProtocol, (void**)&ret);
300     if(FAILED(hres)) {
301         WARN("CreateInstance failed: %08x\n", hres);
302         return NULL;
303     }
304
305     return ret;
306 }
307
308 static HRESULT WINAPI InternetSession_QueryInterface(IInternetSession *iface,
309         REFIID riid, void **ppv)
310 {
311     TRACE("(%s %p)\n", debugstr_guid(riid), ppv);
312
313     if(IsEqualGUID(&IID_IUnknown, riid) || IsEqualGUID(&IID_IInternetSession, riid)) {
314         *ppv = iface;
315         IInternetSession_AddRef(iface);
316         return S_OK;
317     }
318
319     *ppv = NULL;
320     return E_NOINTERFACE;
321 }
322
323 static ULONG WINAPI InternetSession_AddRef(IInternetSession *iface)
324 {
325     TRACE("()\n");
326     URLMON_LockModule();
327     return 2;
328 }
329
330 static ULONG WINAPI InternetSession_Release(IInternetSession *iface)
331 {
332     TRACE("()\n");
333     URLMON_UnlockModule();
334     return 1;
335 }
336
337 static HRESULT WINAPI InternetSession_RegisterNameSpace(IInternetSession *iface,
338         IClassFactory *pCF, REFCLSID rclsid, LPCWSTR pwzProtocol, ULONG cPatterns,
339         const LPCWSTR *ppwzPatterns, DWORD dwReserved)
340 {
341     TRACE("(%p %s %s %d %p %d)\n", pCF, debugstr_guid(rclsid), debugstr_w(pwzProtocol),
342           cPatterns, ppwzPatterns, dwReserved);
343
344     if(cPatterns || ppwzPatterns)
345         FIXME("patterns not supported\n");
346     if(dwReserved)
347         WARN("dwReserved = %d\n", dwReserved);
348
349     if(!pCF || !pwzProtocol)
350         return E_INVALIDARG;
351
352     return register_namespace(pCF, rclsid, pwzProtocol, FALSE);
353 }
354
355 static HRESULT WINAPI InternetSession_UnregisterNameSpace(IInternetSession *iface,
356         IClassFactory *pCF, LPCWSTR pszProtocol)
357 {
358     TRACE("(%p %s)\n", pCF, debugstr_w(pszProtocol));
359
360     if(!pCF || !pszProtocol)
361         return E_INVALIDARG;
362
363     return unregister_namespace(pCF, pszProtocol);
364 }
365
366 static HRESULT WINAPI InternetSession_RegisterMimeFilter(IInternetSession *iface,
367         IClassFactory *pCF, REFCLSID rclsid, LPCWSTR pwzType)
368 {
369     mime_filter *filter;
370
371     TRACE("(%p %s %s)\n", pCF, debugstr_guid(rclsid), debugstr_w(pwzType));
372
373     filter = heap_alloc(sizeof(mime_filter));
374
375     IClassFactory_AddRef(pCF);
376     filter->cf = pCF;
377     filter->clsid = *rclsid;
378     filter->mime = heap_strdupW(pwzType);
379
380     EnterCriticalSection(&session_cs);
381
382     filter->next = mime_filter_list;
383     mime_filter_list = filter;
384
385     LeaveCriticalSection(&session_cs);
386
387     return S_OK;
388 }
389
390 static HRESULT WINAPI InternetSession_UnregisterMimeFilter(IInternetSession *iface,
391         IClassFactory *pCF, LPCWSTR pwzType)
392 {
393     mime_filter *iter, *prev = NULL;
394
395     TRACE("(%p %s)\n", pCF, debugstr_w(pwzType));
396
397     EnterCriticalSection(&session_cs);
398
399     for(iter = mime_filter_list; iter; iter = iter->next) {
400         if(iter->cf == pCF && !strcmpW(iter->mime, pwzType))
401             break;
402         prev = iter;
403     }
404
405     if(iter) {
406         if(prev)
407             prev->next = iter->next;
408         else
409             mime_filter_list = iter->next;
410     }
411
412     LeaveCriticalSection(&session_cs);
413
414     if(iter) {
415         IClassFactory_Release(iter->cf);
416         heap_free(iter->mime);
417         heap_free(iter);
418     }
419
420     return S_OK;
421 }
422
423 static HRESULT WINAPI InternetSession_CreateBinding(IInternetSession *iface,
424         LPBC pBC, LPCWSTR szUrl, IUnknown *pUnkOuter, IUnknown **ppUnk,
425         IInternetProtocol **ppOInetProt, DWORD dwOption)
426 {
427     BindProtocol *protocol;
428     HRESULT hres;
429
430     TRACE("(%p %s %p %p %p %08x)\n", pBC, debugstr_w(szUrl), pUnkOuter, ppUnk,
431             ppOInetProt, dwOption);
432
433     if(pBC || pUnkOuter || ppUnk || dwOption)
434         FIXME("Unsupported arguments\n");
435
436     hres = create_binding_protocol(FALSE, &protocol);
437     if(FAILED(hres))
438         return hres;
439
440     *ppOInetProt = (IInternetProtocol*)&protocol->IInternetProtocolEx_iface;
441     return S_OK;
442 }
443
444 static HRESULT WINAPI InternetSession_SetSessionOption(IInternetSession *iface,
445         DWORD dwOption, LPVOID pBuffer, DWORD dwBufferLength, DWORD dwReserved)
446 {
447     FIXME("(%08x %p %d %d)\n", dwOption, pBuffer, dwBufferLength, dwReserved);
448     return E_NOTIMPL;
449 }
450
451 static const IInternetSessionVtbl InternetSessionVtbl = {
452     InternetSession_QueryInterface,
453     InternetSession_AddRef,
454     InternetSession_Release,
455     InternetSession_RegisterNameSpace,
456     InternetSession_UnregisterNameSpace,
457     InternetSession_RegisterMimeFilter,
458     InternetSession_UnregisterMimeFilter,
459     InternetSession_CreateBinding,
460     InternetSession_SetSessionOption
461 };
462
463 static IInternetSession InternetSession = { &InternetSessionVtbl };
464
465 /***********************************************************************
466  *           CoInternetGetSession (URLMON.@)
467  *
468  * Create a new internet session and return an IInternetSession interface
469  * representing it.
470  *
471  * PARAMS
472  *    dwSessionMode      [I] Mode for the internet session
473  *    ppIInternetSession [O] Destination for creates IInternetSession object
474  *    dwReserved         [I] Reserved, must be 0.
475  *
476  * RETURNS
477  *    Success: S_OK. ppIInternetSession contains the IInternetSession interface.
478  *    Failure: E_INVALIDARG, if any argument is invalid, or
479  *             E_OUTOFMEMORY if memory allocation fails.
480  */
481 HRESULT WINAPI CoInternetGetSession(DWORD dwSessionMode, IInternetSession **ppIInternetSession,
482         DWORD dwReserved)
483 {
484     TRACE("(%d %p %d)\n", dwSessionMode, ppIInternetSession, dwReserved);
485
486     if(dwSessionMode)
487         ERR("dwSessionMode=%d\n", dwSessionMode);
488     if(dwReserved)
489         ERR("dwReserved=%d\n", dwReserved);
490
491     IInternetSession_AddRef(&InternetSession);
492     *ppIInternetSession = &InternetSession;
493     return S_OK;
494 }
495
496 /**************************************************************************
497  *                 UrlMkGetSessionOption (URLMON.@)
498  */
499 static BOOL get_url_encoding(HKEY root, DWORD *encoding)
500 {
501     DWORD size = sizeof(DWORD), res, type;
502     HKEY hkey;
503
504     static const WCHAR wszUrlEncoding[] = {'U','r','l','E','n','c','o','d','i','n','g',0};
505
506     res = RegOpenKeyW(root, internet_settings_keyW, &hkey);
507     if(res != ERROR_SUCCESS)
508         return FALSE;
509
510     res = RegQueryValueExW(hkey, wszUrlEncoding, NULL, &type, (LPBYTE)encoding, &size);
511     RegCloseKey(hkey);
512
513     return res == ERROR_SUCCESS;
514 }
515
516 static LPWSTR user_agent;
517
518 static void ensure_useragent(void)
519 {
520     OSVERSIONINFOW info = {sizeof(info)};
521     const WCHAR *os_type, *is_nt;
522     WCHAR buf[512];
523     BOOL is_wow;
524
525     static const WCHAR formatW[] =
526         {'M','o','z','i','l','l','a','/','4','.','0',
527          ' ','(','c','o','m','p','a','t','i','b','l','e',';',
528          ' ','M','S','I','E',' ','8','.','0',';',
529          ' ','W','i','n','d','o','w','s',' ','%','s','%','d','.','%','d',';',
530          ' ','%','s',';',' ','T','r','i','d','e','n','t','/','5','.','0',')',0};
531     static const WCHAR ntW[] = {'N','T',' ',0};
532     static const WCHAR win32W[] = {'W','i','n','3','2',0};
533     static const WCHAR win64W[] = {'W','i','n','6','4',0};
534     static const WCHAR wow64W[] = {'W','O','W','6','4',0};
535     static const WCHAR emptyW[] = {0};
536
537     if(user_agent)
538         return;
539
540     GetVersionExW(&info);
541     is_nt = info.dwPlatformId == VER_PLATFORM_WIN32_NT ? ntW : emptyW;
542
543     if(sizeof(void*) == 8)
544         os_type = win64W;
545     else if(IsWow64Process(GetCurrentProcess(), &is_wow) && is_wow)
546         os_type = wow64W;
547     else
548         os_type = win32W;
549
550     sprintfW(buf, formatW, is_nt, info.dwMajorVersion, info.dwMinorVersion, os_type);
551     user_agent = heap_strdupW(buf);
552 }
553
554 LPWSTR get_useragent(void)
555 {
556     LPWSTR ret;
557
558     ensure_useragent();
559
560     EnterCriticalSection(&session_cs);
561     ret = heap_strdupW(user_agent);
562     LeaveCriticalSection(&session_cs);
563
564     return ret;
565 }
566
567 HRESULT WINAPI UrlMkGetSessionOption(DWORD dwOption, LPVOID pBuffer, DWORD dwBufferLength,
568                                      DWORD* pdwBufferLength, DWORD dwReserved)
569 {
570     TRACE("(%x, %p, %d, %p)\n", dwOption, pBuffer, dwBufferLength, pdwBufferLength);
571
572     if(dwReserved)
573         WARN("dwReserved = %d\n", dwReserved);
574
575     switch(dwOption) {
576     case URLMON_OPTION_USERAGENT: {
577         HRESULT hres = E_OUTOFMEMORY;
578         DWORD size;
579
580         if(!pdwBufferLength)
581             return E_INVALIDARG;
582
583         EnterCriticalSection(&session_cs);
584
585         ensure_useragent();
586         if(user_agent) {
587             size = WideCharToMultiByte(CP_ACP, 0, user_agent, -1, NULL, 0, NULL, NULL);
588             *pdwBufferLength = size;
589             if(size <= dwBufferLength) {
590                 if(pBuffer)
591                     WideCharToMultiByte(CP_ACP, 0, user_agent, -1, pBuffer, size, NULL, NULL);
592                 else
593                     hres = E_INVALIDARG;
594             }
595         }
596
597         LeaveCriticalSection(&session_cs);
598
599         /* Tests prove that we have to return E_OUTOFMEMORY on success. */
600         return hres;
601     }
602     case URLMON_OPTION_URL_ENCODING: {
603         DWORD encoding = 0;
604
605         if(!pBuffer || dwBufferLength < sizeof(DWORD) || !pdwBufferLength)
606             return E_INVALIDARG;
607
608         if(!get_url_encoding(HKEY_CURRENT_USER, &encoding))
609             get_url_encoding(HKEY_LOCAL_MACHINE, &encoding);
610
611         *pdwBufferLength = sizeof(DWORD);
612         *(DWORD*)pBuffer = encoding ? URL_ENCODING_DISABLE_UTF8 : URL_ENCODING_ENABLE_UTF8;
613         return S_OK;
614     }
615     default:
616         FIXME("unsupported option %x\n", dwOption);
617     }
618
619     return E_INVALIDARG;
620 }
621
622 /**************************************************************************
623  *                 UrlMkSetSessionOption (URLMON.@)
624  */
625 HRESULT WINAPI UrlMkSetSessionOption(DWORD dwOption, LPVOID pBuffer, DWORD dwBufferLength,
626         DWORD Reserved)
627 {
628     TRACE("(%x %p %x)\n", dwOption, pBuffer, dwBufferLength);
629
630     switch(dwOption) {
631     case URLMON_OPTION_USERAGENT: {
632         LPWSTR new_user_agent;
633         char *buf = pBuffer;
634         DWORD len, size;
635
636         if(!pBuffer || !dwBufferLength)
637             return E_INVALIDARG;
638
639         for(len=0; len<dwBufferLength && buf[len]; len++);
640
641         TRACE("Setting user agent %s\n", debugstr_an(buf, len));
642
643         size = MultiByteToWideChar(CP_ACP, 0, buf, len, NULL, 0);
644         new_user_agent = heap_alloc((size+1)*sizeof(WCHAR));
645         if(!new_user_agent)
646             return E_OUTOFMEMORY;
647         MultiByteToWideChar(CP_ACP, 0, buf, len, new_user_agent, size);
648         new_user_agent[size] = 0;
649
650         EnterCriticalSection(&session_cs);
651
652         heap_free(user_agent);
653         user_agent = new_user_agent;
654
655         LeaveCriticalSection(&session_cs);
656         break;
657     }
658     default:
659         FIXME("Unknown option %x\n", dwOption);
660         return E_INVALIDARG;
661     }
662
663     return S_OK;
664 }
665
666 /**************************************************************************
667  *                 ObtainUserAgentString (URLMON.@)
668  */
669 HRESULT WINAPI ObtainUserAgentString(DWORD dwOption, LPSTR pcszUAOut, DWORD *cbSize)
670 {
671     DWORD size;
672     HRESULT hres = E_FAIL;
673
674     TRACE("(%d %p %p)\n", dwOption, pcszUAOut, cbSize);
675
676     if(!pcszUAOut || !cbSize)
677         return E_INVALIDARG;
678
679     EnterCriticalSection(&session_cs);
680
681     ensure_useragent();
682     if(user_agent) {
683         size = WideCharToMultiByte(CP_ACP, 0, user_agent, -1, NULL, 0, NULL, NULL);
684
685         if(size <= *cbSize) {
686             WideCharToMultiByte(CP_ACP, 0, user_agent, -1, pcszUAOut, *cbSize, NULL, NULL);
687             hres = S_OK;
688         }else {
689             hres = E_OUTOFMEMORY;
690         }
691
692         *cbSize = size;
693     }
694
695     LeaveCriticalSection(&session_cs);
696     return hres;
697 }
698
699 void free_session(void)
700 {
701     heap_free(user_agent);
702 }