Correct tooltips behavior.
[wine] / dlls / wininet / cookie.c
1 /*
2  * Wininet - cookie handling stuff
3  *
4  * Copyright 2002 TransGaming Technologies Inc.
5  *
6  * David Hammerton
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22
23 #include "config.h"
24
25 #include <stdarg.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #ifdef HAVE_UNISTD_H
30 # include <unistd.h>
31 #endif
32
33 #include "windef.h"
34 #include "winbase.h"
35 #include "wininet.h"
36 #include "winerror.h"
37
38 #include "wine/debug.h"
39 #include "internet.h"
40
41 #include "wine/list.h"
42
43 #define RESPONSE_TIMEOUT        30            /* FROM internet.c */
44
45
46 WINE_DEFAULT_DEBUG_CHANNEL(wininet);
47
48 /* FIXME
49  *     Cookies are currently memory only.
50  *     Cookies are NOT THREAD SAFE
51  *     Cookies could use ALOT OF MEMORY. We need some kind of memory management here!
52  *     Cookies should care about the expiry time
53  */
54
55 typedef struct _cookie_domain cookie_domain;
56 typedef struct _cookie cookie;
57
58 struct _cookie
59 {
60     struct list entry;
61
62     struct _cookie_domain *parent;
63
64     LPWSTR lpCookieName;
65     LPWSTR lpCookieData;
66     time_t expiry; /* FIXME: not used */
67 };
68
69 struct _cookie_domain
70 {
71     struct list entry;
72
73     LPWSTR lpCookieDomain;
74     LPWSTR lpCookiePath;
75     struct list cookie_list;
76 };
77
78 static struct list domain_list = LIST_INIT(domain_list);
79
80 static cookie *COOKIE_addCookie(cookie_domain *domain, LPCWSTR name, LPCWSTR data);
81 static cookie *COOKIE_findCookie(cookie_domain *domain, LPCWSTR lpszCookieName);
82 static void COOKIE_deleteCookie(cookie *deadCookie, BOOL deleteDomain);
83 static cookie_domain *COOKIE_addDomain(LPCWSTR domain, LPCWSTR path);
84 static void COOKIE_deleteDomain(cookie_domain *deadDomain);
85
86
87 /* adds a cookie to the domain */
88 static cookie *COOKIE_addCookie(cookie_domain *domain, LPCWSTR name, LPCWSTR data)
89 {
90     cookie *newCookie = HeapAlloc(GetProcessHeap(), 0, sizeof(cookie));
91
92     list_init(&newCookie->entry);
93     newCookie->lpCookieName = NULL;
94     newCookie->lpCookieData = NULL;
95
96     if (name)
97     {
98         newCookie->lpCookieName = HeapAlloc(GetProcessHeap(), 0, (strlenW(name) + 1)*sizeof(WCHAR));
99         lstrcpyW(newCookie->lpCookieName, name);
100     }
101     if (data)
102     {
103         newCookie->lpCookieData = HeapAlloc(GetProcessHeap(), 0, (strlenW(data) + 1)*sizeof(WCHAR));
104         lstrcpyW(newCookie->lpCookieData, data);
105     }
106
107     TRACE("added cookie %p (data is %s)\n", newCookie, debugstr_w(data) );
108
109     list_add_tail(&domain->cookie_list, &newCookie->entry);
110     newCookie->parent = domain;
111     return newCookie;
112 }
113
114
115 /* finds a cookie in the domain matching the cookie name */
116 static cookie *COOKIE_findCookie(cookie_domain *domain, LPCWSTR lpszCookieName)
117 {
118     struct list * cursor;
119     TRACE("(%p, %s)\n", domain, debugstr_w(lpszCookieName));
120
121     LIST_FOR_EACH(cursor, &domain->cookie_list)
122     {
123         cookie *searchCookie = LIST_ENTRY(cursor, cookie, entry);
124         BOOL candidate = TRUE;
125         if (candidate && lpszCookieName)
126         {
127             if (candidate && !searchCookie->lpCookieName)
128                 candidate = FALSE;
129             if (candidate && strcmpW(lpszCookieName, searchCookie->lpCookieName) != 0)
130                 candidate = FALSE;
131         }
132         if (candidate)
133             return searchCookie;
134     }
135     return NULL;
136 }
137
138 /* removes a cookie from the list, if its the last cookie we also remove the domain */
139 static void COOKIE_deleteCookie(cookie *deadCookie, BOOL deleteDomain)
140 {
141     if (deadCookie->lpCookieName)
142         HeapFree(GetProcessHeap(), 0, deadCookie->lpCookieName);
143     if (deadCookie->lpCookieData)
144         HeapFree(GetProcessHeap(), 0, deadCookie->lpCookieData);
145     list_remove(&deadCookie->entry);
146
147     /* special case: last cookie, lets remove the domain to save memory */
148     if (list_empty(&deadCookie->parent->cookie_list) && deleteDomain)
149         COOKIE_deleteDomain(deadCookie->parent);
150     HeapFree(GetProcessHeap(), 0, deadCookie);
151 }
152
153 /* allocates a domain and adds it to the end */
154 static cookie_domain *COOKIE_addDomain(LPCWSTR domain, LPCWSTR path)
155 {
156     cookie_domain *newDomain = HeapAlloc(GetProcessHeap(), 0, sizeof(cookie_domain));
157
158     list_init(&newDomain->entry);
159     list_init(&newDomain->cookie_list);
160     newDomain->lpCookieDomain = NULL;
161     newDomain->lpCookiePath = NULL;
162
163     if (domain)
164     {
165         newDomain->lpCookieDomain = HeapAlloc(GetProcessHeap(), 0, (strlenW(domain) + 1)*sizeof(WCHAR));
166         strcpyW(newDomain->lpCookieDomain, domain);
167     }
168     if (path)
169     {
170         newDomain->lpCookiePath = HeapAlloc(GetProcessHeap(), 0, (strlenW(path) + 1)*sizeof(WCHAR));
171         lstrcpyW(newDomain->lpCookiePath, path);
172     }
173
174     list_add_tail(&domain_list, &newDomain->entry);
175
176     TRACE("Adding domain: %p\n", newDomain);
177     return newDomain;
178 }
179
180 static void COOKIE_crackUrlSimple(LPCWSTR lpszUrl, LPWSTR hostName, int hostNameLen, LPWSTR path, int pathLen)
181 {
182     URL_COMPONENTSW UrlComponents;
183
184     UrlComponents.lpszExtraInfo = NULL;
185     UrlComponents.lpszPassword = NULL;
186     UrlComponents.lpszScheme = NULL;
187     UrlComponents.lpszUrlPath = path;
188     UrlComponents.lpszUserName = NULL;
189     UrlComponents.lpszHostName = hostName;
190     UrlComponents.dwHostNameLength = hostNameLen;
191     UrlComponents.dwUrlPathLength = pathLen;
192
193     InternetCrackUrlW(lpszUrl, 0, 0, &UrlComponents);
194 }
195
196 /* match a domain. domain must match if the domain is not NULL. path must match if the path is not NULL */
197 static BOOL COOKIE_matchDomain(LPCWSTR lpszCookieDomain, LPCWSTR lpszCookiePath,
198                                cookie_domain *searchDomain, BOOL allow_partial)
199 {
200     TRACE("searching on domain %p\n", searchDomain);
201         if (lpszCookieDomain)
202         {
203             if (!searchDomain->lpCookieDomain)
204             return FALSE;
205
206             TRACE("comparing domain %s with %s\n", 
207             debugstr_w(lpszCookieDomain), 
208             debugstr_w(searchDomain->lpCookieDomain));
209
210         if (allow_partial && !strstrW(lpszCookieDomain, searchDomain->lpCookieDomain))
211             return FALSE;
212         else if (!allow_partial && lstrcmpW(lpszCookieDomain, searchDomain->lpCookieDomain) != 0)
213             return FALSE;
214         }
215     if (lpszCookiePath)
216     {
217         TRACE("comparing paths: %s with %s\n", debugstr_w(lpszCookiePath), debugstr_w(searchDomain->lpCookiePath));
218         if (!searchDomain->lpCookiePath)
219             return FALSE;
220         if (strcmpW(lpszCookiePath, searchDomain->lpCookiePath))
221             return FALSE;
222         }
223         return TRUE;
224 }
225
226 /* remove a domain from the list and delete it */
227 static void COOKIE_deleteDomain(cookie_domain *deadDomain)
228 {
229     struct list * cursor;
230     while ((cursor = list_tail(&deadDomain->cookie_list)))
231     {
232         COOKIE_deleteCookie(LIST_ENTRY(cursor, cookie, entry), FALSE);
233         list_remove(cursor);
234     }
235
236     if (deadDomain->lpCookieDomain)
237         HeapFree(GetProcessHeap(), 0, deadDomain->lpCookieDomain);
238     if (deadDomain->lpCookiePath)
239         HeapFree(GetProcessHeap(), 0, deadDomain->lpCookiePath);
240
241     list_remove(&deadDomain->entry);
242
243     HeapFree(GetProcessHeap(), 0, deadDomain);
244 }
245
246 /***********************************************************************
247  *           InternetGetCookieW (WININET.@)
248  *
249  * Retrieve cookie from the specified url
250  *
251  *  It should be noted that on windows the lpszCookieName parameter is "not implemented".
252  *    So it won't be implemented here.
253  *
254  * RETURNS
255  *    TRUE  on success
256  *    FALSE on failure
257  *
258  */
259 BOOL WINAPI InternetGetCookieW(LPCWSTR lpszUrl, LPCWSTR lpszCookieName,
260     LPWSTR lpCookieData, LPDWORD lpdwSize)
261 {
262     struct list * cursor;
263     int cnt = 0, domain_count = 0;
264     int cookie_count = 0;
265     WCHAR hostName[2048], path[2048];
266
267     TRACE("(%s, %s, %p, %p)\n", debugstr_w(lpszUrl),debugstr_w(lpszCookieName),
268           lpCookieData, lpdwSize);
269
270     COOKIE_crackUrlSimple(lpszUrl, hostName, sizeof(hostName)/sizeof(hostName[0]), path, sizeof(path)/sizeof(path[0]));
271
272     LIST_FOR_EACH(cursor, &domain_list)
273     {
274         cookie_domain *cookiesDomain = LIST_ENTRY(cursor, cookie_domain, entry);
275         if (COOKIE_matchDomain(hostName, NULL /* FIXME: path */, cookiesDomain, TRUE))
276         {
277             struct list * cursor;
278             domain_count++;
279             TRACE("found domain %p\n", cookiesDomain);
280     
281             LIST_FOR_EACH(cursor, &cookiesDomain->cookie_list)
282             {
283                 cookie *thisCookie = LIST_ENTRY(cursor, cookie, entry);
284                 if (lpCookieData == NULL) /* return the size of the buffer required to lpdwSize */
285                 {
286                     if (cookie_count != 0)
287                         cnt += 2; /* '; ' */
288                     cnt += strlenW(thisCookie->lpCookieName);
289                     cnt += 1; /* = */
290                     cnt += strlenW(thisCookie->lpCookieData);
291                 }
292                 else
293                 {
294                     static const WCHAR szsc[] = { ';',' ',0 };
295                     static const WCHAR szpseq[] = { '%','s','=','%','s',0 };
296                     if (cookie_count != 0)
297                         cnt += snprintfW(lpCookieData + cnt, *lpdwSize - cnt, szsc);
298                     cnt += snprintfW(lpCookieData + cnt, *lpdwSize - cnt, szpseq,
299                                     thisCookie->lpCookieName,
300                                     thisCookie->lpCookieData);
301                     TRACE("Cookie: %s=%s\n", debugstr_w(thisCookie->lpCookieName), debugstr_w(thisCookie->lpCookieData));
302                 }
303                 cookie_count++;
304             }
305         }
306     }
307     if (lpCookieData == NULL)
308     {
309         cnt += 1; /* NULL */
310         *lpdwSize = cnt*sizeof(WCHAR);
311         TRACE("returning\n");
312         return TRUE;
313     }
314
315     if (!domain_count)
316         return FALSE;
317
318     *lpdwSize = (cnt + 1)*sizeof(WCHAR);
319
320     TRACE("Returning %i (from %i domains): %s\n", cnt, domain_count,
321            debugstr_w(lpCookieData));
322
323     return (cnt ? TRUE : FALSE);
324 }
325
326
327 /***********************************************************************
328  *           InternetGetCookieA (WININET.@)
329  *
330  * Retrieve cookie from the specified url
331  *
332  * RETURNS
333  *    TRUE  on success
334  *    FALSE on failure
335  *
336  */
337 BOOL WINAPI InternetGetCookieA(LPCSTR lpszUrl, LPCSTR lpszCookieName,
338     LPSTR lpCookieData, LPDWORD lpdwSize)
339 {
340     DWORD len;
341     LPWSTR szCookieData = NULL, szUrl = NULL, szCookieName = NULL;
342     BOOL r;
343
344     TRACE("(%s,%s,%p)\n", debugstr_a(lpszUrl), debugstr_a(lpszCookieName),
345         lpCookieData);
346
347     if( lpszUrl )
348     {
349         len = MultiByteToWideChar( CP_ACP, 0, lpszUrl, -1, NULL, 0 );
350         szUrl = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) );
351         MultiByteToWideChar( CP_ACP, 0, lpszUrl, -1, szUrl, len );
352     }
353
354     if( lpszCookieName )
355     {
356         len = MultiByteToWideChar( CP_ACP, 0, lpszCookieName, -1, NULL, 0 );
357         szCookieName = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) );
358         MultiByteToWideChar( CP_ACP, 0, lpszCookieName, -1, szCookieName, len );
359     }
360
361     r = InternetGetCookieW( szUrl, szCookieName, NULL, &len );
362     if( r )
363     {
364         szCookieData = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) );
365         if( !szCookieData )
366             return FALSE;
367
368         r = InternetGetCookieW( szUrl, szCookieName, szCookieData, &len );
369
370         *lpdwSize = WideCharToMultiByte( CP_ACP, 0, szCookieData, len,
371                                 lpCookieData, *lpdwSize, NULL, NULL );
372     }
373
374     if( szCookieData )
375         HeapFree( GetProcessHeap(), 0, szCookieData );
376     if( szCookieName )
377         HeapFree( GetProcessHeap(), 0, szCookieName );
378     if( szUrl )
379         HeapFree( GetProcessHeap(), 0, szUrl );
380
381     return r;
382 }
383
384
385 /***********************************************************************
386  *           InternetSetCookieW (WININET.@)
387  *
388  * Sets cookie for the specified url
389  *
390  * RETURNS
391  *    TRUE  on success
392  *    FALSE on failure
393  *
394  */
395 BOOL WINAPI InternetSetCookieW(LPCWSTR lpszUrl, LPCWSTR lpszCookieName,
396     LPCWSTR lpCookieData)
397 {
398     cookie_domain *thisCookieDomain = NULL;
399     cookie *thisCookie;
400     WCHAR hostName[2048], path[2048];
401     struct list * cursor;
402
403     TRACE("(%s,%s,%s)\n", debugstr_w(lpszUrl),
404         debugstr_w(lpszCookieName), debugstr_w(lpCookieData));
405
406     if (!lpCookieData || !strlenW(lpCookieData))
407     {
408         TRACE("no cookie data, not adding\n");
409         return FALSE;
410     }
411     if (!lpszCookieName)
412     {
413         /* some apps (or is it us??) try to add a cookie with no cookie name, but
414          * the cookie data in the form of name=data. */
415         /* FIXME, probably a bug here, for now I don't care */
416         WCHAR *ourCookieName, *ourCookieData;
417         int ourCookieNameSize;
418         BOOL ret;
419         if (!(ourCookieData = strchrW(lpCookieData, '=')))
420         {
421             TRACE("something terribly wrong with cookie data %s\n", 
422                    debugstr_w(ourCookieData));
423             return FALSE;
424         }
425         ourCookieNameSize = ourCookieData - lpCookieData;
426         ourCookieData += 1;
427         ourCookieName = HeapAlloc(GetProcessHeap(), 0, 
428                               (ourCookieNameSize + 1)*sizeof(WCHAR));
429         strncpyW(ourCookieName, ourCookieData, ourCookieNameSize);
430         ourCookieName[ourCookieNameSize] = '\0';
431         TRACE("setting (hacked) cookie of %s, %s\n",
432                debugstr_w(ourCookieName), debugstr_w(ourCookieData));
433         ret = InternetSetCookieW(lpszUrl, ourCookieName, ourCookieData);
434         HeapFree(GetProcessHeap(), 0, ourCookieName);
435         return ret;
436     }
437
438     COOKIE_crackUrlSimple(lpszUrl, hostName, sizeof(hostName)/sizeof(hostName[0]), path, sizeof(path)/sizeof(path[0]));
439
440     LIST_FOR_EACH(cursor, &domain_list)
441     {
442         thisCookieDomain = LIST_ENTRY(cursor, cookie_domain, entry);
443         if (COOKIE_matchDomain(hostName, NULL /* FIXME: path */, thisCookieDomain, FALSE))
444             break;
445         thisCookieDomain = NULL;
446     }
447     if (!thisCookieDomain)
448         thisCookieDomain = COOKIE_addDomain(hostName, path);
449
450     if ((thisCookie = COOKIE_findCookie(thisCookieDomain, lpszCookieName)))
451         COOKIE_deleteCookie(thisCookie, FALSE);
452
453     thisCookie = COOKIE_addCookie(thisCookieDomain, lpszCookieName, lpCookieData);
454     return TRUE;
455 }
456
457
458 /***********************************************************************
459  *           InternetSetCookieA (WININET.@)
460  *
461  * Sets cookie for the specified url
462  *
463  * RETURNS
464  *    TRUE  on success
465  *    FALSE on failure
466  *
467  */
468 BOOL WINAPI InternetSetCookieA(LPCSTR lpszUrl, LPCSTR lpszCookieName,
469     LPCSTR lpCookieData)
470 {
471     DWORD len;
472     LPWSTR szCookieData = NULL, szUrl = NULL, szCookieName = NULL;
473     BOOL r;
474
475     TRACE("(%s,%s,%s)\n", debugstr_a(lpszUrl),
476         debugstr_a(lpszCookieName), debugstr_a(lpCookieData));
477
478     if( lpszUrl )
479     {
480         len = MultiByteToWideChar( CP_ACP, 0, lpszUrl, -1, NULL, 0 );
481         szUrl = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) );
482         MultiByteToWideChar( CP_ACP, 0, lpszUrl, -1, szUrl, len );
483     }
484
485     if( lpszCookieName )
486     {
487         len = MultiByteToWideChar( CP_ACP, 0, lpszCookieName, -1, NULL, 0 );
488         szCookieName = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) );
489         MultiByteToWideChar( CP_ACP, 0, lpszCookieName, -1, szCookieName, len );
490     }
491
492     if( lpCookieData )
493     {
494         len = MultiByteToWideChar( CP_ACP, 0, lpCookieData, -1, NULL, 0 );
495         szCookieData = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) );
496         MultiByteToWideChar( CP_ACP, 0, lpCookieData, -1, szCookieData, len );
497     }
498
499     r = InternetSetCookieW( szUrl, szCookieName, szCookieData );
500
501     if( szCookieData )
502         HeapFree( GetProcessHeap(), 0, szCookieData );
503     if( szCookieName )
504         HeapFree( GetProcessHeap(), 0, szCookieName );
505     if( szUrl )
506         HeapFree( GetProcessHeap(), 0, szUrl );
507
508     return r;
509 }