Give SetErrorMode the right argument to suppress crash dialogs.
[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 #define RESPONSE_TIMEOUT        30            /* FROM internet.c */
42
43
44 WINE_DEFAULT_DEBUG_CHANNEL(wininet);
45
46 /* FIXME
47  *     Cookies are currently memory only.
48  *     Cookies are NOT THREAD SAFE
49  *     Cookies could use ALOT OF MEMORY. We need some kind of memory management here!
50  *     Cookies should care about the expiry time
51  */
52
53 typedef struct _cookie_domain cookie_domain;
54 typedef struct _cookie cookie;
55
56 struct _cookie
57 {
58     struct _cookie *next;
59     struct _cookie *prev;
60
61     struct _cookie_domain *parent;
62
63     LPWSTR lpCookieName;
64     LPWSTR lpCookieData;
65     time_t expiry; /* FIXME: not used */
66 };
67
68 struct _cookie_domain
69 {
70     struct _cookie_domain *next;
71     struct _cookie_domain *prev;
72
73     LPWSTR lpCookieDomain;
74     LPWSTR lpCookiePath;
75     cookie *cookie_tail;
76 };
77
78 static cookie_domain *cookieDomainTail;
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 cookie_domain *COOKIE_addDomainFromUrl(LPCWSTR lpszUrl);
85 static cookie_domain *COOKIE_findNextDomain(LPCWSTR lpszCookieDomain, LPCWSTR lpszCookiePath,
86                                             cookie_domain *prev_domain, BOOL allow_partial);
87 static cookie_domain *COOKIE_findNextDomainFromUrl(LPCWSTR lpszUrl, cookie_domain *prev_domain,
88                                                    BOOL allow_partial);
89 static void COOKIE_deleteDomain(cookie_domain *deadDomain);
90
91
92 /* adds a cookie to the domain */
93 static cookie *COOKIE_addCookie(cookie_domain *domain, LPCWSTR name, LPCWSTR data)
94 {
95     cookie *newCookie = HeapAlloc(GetProcessHeap(), 0, sizeof(cookie));
96
97     newCookie->next = NULL;
98     newCookie->prev = NULL;
99     newCookie->lpCookieName = NULL;
100     newCookie->lpCookieData = NULL;
101
102     if (name)
103     {
104         newCookie->lpCookieName = HeapAlloc(GetProcessHeap(), 0, (strlenW(name) + 1)*sizeof(WCHAR));
105         lstrcpyW(newCookie->lpCookieName, name);
106     }
107     if (data)
108     {
109         newCookie->lpCookieData = HeapAlloc(GetProcessHeap(), 0, (strlenW(data) + 1)*sizeof(WCHAR));
110         lstrcpyW(newCookie->lpCookieData, data);
111     }
112
113     TRACE("added cookie %p (data is %s)\n", newCookie, debugstr_w(data) );
114
115     newCookie->prev = domain->cookie_tail;
116     newCookie->parent = domain;
117     domain->cookie_tail = newCookie;
118     return newCookie;
119 }
120
121
122 /* finds a cookie in the domain matching the cookie name */
123 static cookie *COOKIE_findCookie(cookie_domain *domain, LPCWSTR lpszCookieName)
124 {
125     cookie *searchCookie = domain->cookie_tail;
126     TRACE("(%p, %s)\n", domain, debugstr_w(lpszCookieName));
127
128     while (searchCookie)
129     {
130         BOOL candidate = TRUE;
131         if (candidate && lpszCookieName)
132         {
133             if (candidate && !searchCookie->lpCookieName)
134                 candidate = FALSE;
135             if (candidate && strcmpW(lpszCookieName, searchCookie->lpCookieName) != 0)
136                 candidate = FALSE;
137         }
138         if (candidate)
139             return searchCookie;
140         searchCookie = searchCookie->prev;
141     }
142     return NULL;
143 }
144
145 /* removes a cookie from the list, if its the last cookie we also remove the domain */
146 static void COOKIE_deleteCookie(cookie *deadCookie, BOOL deleteDomain)
147 {
148     if (deadCookie->lpCookieName)
149         HeapFree(GetProcessHeap(), 0, deadCookie->lpCookieName);
150     if (deadCookie->lpCookieData)
151         HeapFree(GetProcessHeap(), 0, deadCookie->lpCookieData);
152     if (deadCookie->prev)
153         deadCookie->prev->next = deadCookie->next;
154     if (deadCookie->next)
155         deadCookie->next->prev = deadCookie->prev;
156
157     if (deadCookie == deadCookie->parent->cookie_tail)
158     {
159         /* special case: last cookie, lets remove the domain to save memory */
160         deadCookie->parent->cookie_tail = deadCookie->prev;
161         if (!deadCookie->parent->cookie_tail && deleteDomain)
162             COOKIE_deleteDomain(deadCookie->parent);
163     }
164 }
165
166 /* allocates a domain and adds it to the end */
167 static cookie_domain *COOKIE_addDomain(LPCWSTR domain, LPCWSTR path)
168 {
169     cookie_domain *newDomain = HeapAlloc(GetProcessHeap(), 0, sizeof(cookie_domain));
170
171     newDomain->next = NULL;
172     newDomain->prev = NULL;
173     newDomain->cookie_tail = NULL;
174     newDomain->lpCookieDomain = NULL;
175     newDomain->lpCookiePath = NULL;
176
177     if (domain)
178     {
179         newDomain->lpCookieDomain = HeapAlloc(GetProcessHeap(), 0, (strlenW(domain) + 1)*sizeof(WCHAR));
180         strcpyW(newDomain->lpCookieDomain, domain);
181     }
182     if (path)
183     {
184         newDomain->lpCookiePath = HeapAlloc(GetProcessHeap(), 0, (strlenW(path) + 1)*sizeof(WCHAR));
185         lstrcpyW(newDomain->lpCookiePath, path);
186     }
187
188     newDomain->prev = cookieDomainTail;
189     cookieDomainTail = newDomain;
190     TRACE("Adding domain: %p\n", newDomain);
191     return newDomain;
192 }
193
194 static cookie_domain *COOKIE_addDomainFromUrl(LPCWSTR lpszUrl)
195 {
196     WCHAR hostName[2048], path[2048];
197     URL_COMPONENTSW UrlComponents;
198
199     UrlComponents.lpszExtraInfo = NULL;
200     UrlComponents.lpszPassword = NULL;
201     UrlComponents.lpszScheme = NULL;
202     UrlComponents.lpszUrlPath = path;
203     UrlComponents.lpszUserName = NULL;
204     UrlComponents.lpszHostName = hostName;
205     UrlComponents.dwHostNameLength = 2048;
206     UrlComponents.dwUrlPathLength = 2048;
207
208     InternetCrackUrlW(lpszUrl, 0, 0, &UrlComponents);
209
210     TRACE("Url cracked. Domain: %s, Path: %s.\n", debugstr_w(UrlComponents.lpszHostName),
211           debugstr_w(UrlComponents.lpszUrlPath));
212
213     /* hack for now - FIXME - There seems to be a bug in InternetCrackUrl?? */
214     UrlComponents.lpszUrlPath = NULL;
215
216     return COOKIE_addDomain(UrlComponents.lpszHostName, UrlComponents.lpszUrlPath);
217 }
218
219 /* find a domain. domain must match if its not NULL. path must match if its not NULL */
220 static cookie_domain *COOKIE_findNextDomain(LPCWSTR lpszCookieDomain, LPCWSTR lpszCookiePath,
221                                             cookie_domain *prev_domain, BOOL allow_partial)
222 {
223     cookie_domain *searchDomain;
224
225     if (prev_domain)
226     {
227         if(!prev_domain->prev)
228         {
229             TRACE("no more domains available, it would seem.\n");
230             return NULL;
231         }
232         searchDomain = prev_domain->prev;
233     }
234     else searchDomain = cookieDomainTail;
235
236     while (searchDomain)
237     {
238         BOOL candidate = TRUE;
239         TRACE("searching on domain %p\n", searchDomain);
240         if (candidate && lpszCookieDomain)
241         {
242             if (candidate && !searchDomain->lpCookieDomain)
243                 candidate = FALSE;
244             TRACE("candidate! (%p)\n", searchDomain->lpCookieDomain);
245             TRACE("comparing domain %s with %s\n", 
246                   debugstr_w(lpszCookieDomain), 
247                   debugstr_w(searchDomain->lpCookieDomain));
248             if (candidate && allow_partial && !strstrW(lpszCookieDomain, searchDomain->lpCookieDomain))
249                 candidate = FALSE;
250             else if (candidate && !allow_partial &&
251                      lstrcmpW(lpszCookieDomain, searchDomain->lpCookieDomain) != 0)
252                 candidate = FALSE;
253         }
254         if (candidate && lpszCookiePath)
255         {
256             TRACE("comparing paths\n");
257             if (candidate && !searchDomain->lpCookiePath)
258                 candidate = FALSE;
259             if (candidate && 
260                 strcmpW(lpszCookiePath, searchDomain->lpCookiePath))
261                 candidate = FALSE;
262         }
263         if (candidate)
264         {
265             TRACE("returning the domain %p\n", searchDomain);
266             return searchDomain;
267         }
268         searchDomain = searchDomain->prev;
269     }
270     TRACE("found no domain, returning NULL\n");
271     return NULL;
272 }
273
274 static cookie_domain *COOKIE_findNextDomainFromUrl(LPCWSTR lpszUrl, cookie_domain *previous_domain,
275                                                    BOOL allow_partial)
276 {
277     WCHAR hostName[2048], path[2048];
278     URL_COMPONENTSW UrlComponents;
279
280     UrlComponents.lpszExtraInfo = NULL;
281     UrlComponents.lpszPassword = NULL;
282     UrlComponents.lpszScheme = NULL;
283     UrlComponents.lpszUrlPath = path;
284     UrlComponents.lpszUserName = NULL;
285     UrlComponents.lpszHostName = hostName;
286     UrlComponents.dwHostNameLength = 2048;
287     UrlComponents.dwUrlPathLength = 2048;
288
289     InternetCrackUrlW(lpszUrl, 0, 0, &UrlComponents);
290
291     TRACE("Url cracked. Domain: %s, Path: %s.\n",
292           debugstr_w(UrlComponents.lpszHostName),
293           debugstr_w(UrlComponents.lpszUrlPath));
294
295     /* hack for now - FIXME - There seems to be a bug in InternetCrackUrl?? */
296     UrlComponents.lpszUrlPath = NULL;
297
298     return COOKIE_findNextDomain(UrlComponents.lpszHostName, UrlComponents.lpszUrlPath,
299                                  previous_domain, allow_partial);
300 }
301
302 /* remove a domain from the list and delete it */
303 static void COOKIE_deleteDomain(cookie_domain *deadDomain)
304 {
305     while (deadDomain->cookie_tail)
306         COOKIE_deleteCookie(deadDomain->cookie_tail, FALSE);
307     if (deadDomain->lpCookieDomain)
308         HeapFree(GetProcessHeap(), 0, deadDomain->lpCookieDomain);
309     if (deadDomain->lpCookiePath)
310         HeapFree(GetProcessHeap(), 0, deadDomain->lpCookiePath);
311     if (deadDomain->prev)
312         deadDomain->prev->next = deadDomain->next;
313     if (deadDomain->next)
314         deadDomain->next->prev = deadDomain->prev;
315
316     if (cookieDomainTail == deadDomain)
317         cookieDomainTail = deadDomain->prev;
318     HeapFree(GetProcessHeap(), 0, deadDomain);
319 }
320
321 /***********************************************************************
322  *           InternetGetCookieW (WININET.@)
323  *
324  * Retrieve cookie from the specified url
325  *
326  *  It should be noted that on windows the lpszCookieName parameter is "not implemented".
327  *    So it won't be implemented here.
328  *
329  * RETURNS
330  *    TRUE  on success
331  *    FALSE on failure
332  *
333  */
334 BOOL WINAPI InternetGetCookieW(LPCWSTR lpszUrl, LPCWSTR lpszCookieName,
335     LPWSTR lpCookieData, LPDWORD lpdwSize)
336 {
337     cookie_domain *cookiesDomain = NULL;
338     cookie *thisCookie;
339     int cnt = 0, domain_count = 0;
340     /* Ok, this is just ODD!. During my tests, it appears M$ like to send out
341      * a cookie called 'MtrxTracking' to some urls. Also returns it from InternetGetCookie.
342      * I'm not exactly sure what to make of this, so its here for now.
343      * It'd be nice to know what exactly is going on, M$ tracking users? Does this need
344      * to be unique? Should I generate a random number here? etc.
345      */
346     WCHAR TrackingString[] = {
347         'M','t','r','x','T','r','a','c','k','i','n','g','I','D','=',
348         '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5',
349         '6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1', 0 };
350     WCHAR szps[] = { '%','s',0 };
351
352     TRACE("(%s, %s, %p, %p)\n", debugstr_w(lpszUrl),debugstr_w(lpszCookieName),
353           lpCookieData, lpdwSize);
354
355     if (lpCookieData)
356         cnt += snprintfW(lpCookieData + cnt, *lpdwSize - cnt, szps, TrackingString);
357     else
358         cnt += strlenW(TrackingString);
359
360     while ((cookiesDomain = COOKIE_findNextDomainFromUrl(lpszUrl, cookiesDomain, TRUE)))
361     {
362         domain_count++;
363         TRACE("found domain %p\n", cookiesDomain);
364
365         thisCookie = cookiesDomain->cookie_tail;
366         if (lpCookieData == NULL) /* return the size of the buffer required to lpdwSize */
367         {
368             while (thisCookie)
369             {
370                 cnt += 2; /* '; ' */
371                 cnt += strlenW(thisCookie->lpCookieName);
372                 cnt += 1; /* = */
373                 cnt += strlenW(thisCookie->lpCookieData);
374
375                 thisCookie = thisCookie->prev;
376             }
377         }
378         while (thisCookie)
379         {
380             WCHAR szsc[] = { ';',' ',0 };
381             WCHAR szpseq[] = { '%','s','=','%','s',0 };
382             cnt += snprintfW(lpCookieData + cnt, *lpdwSize - cnt, szsc);
383             cnt += snprintfW(lpCookieData + cnt, *lpdwSize - cnt, szpseq,
384                             thisCookie->lpCookieName,
385                             thisCookie->lpCookieData);
386
387             thisCookie = thisCookie->prev;
388         }
389     }
390     if (lpCookieData == NULL)
391     {
392         cnt += 1; /* NULL */
393         *lpdwSize = cnt;
394         TRACE("returning\n");
395         return TRUE;
396     }
397
398     if (!domain_count)
399         return FALSE;
400
401     *lpdwSize = cnt + 1;
402
403     TRACE("Returning %i (from %i domains): %s\n", cnt, domain_count,
404            debugstr_w(lpCookieData));
405
406     return (cnt ? TRUE : FALSE);
407 }
408
409
410 /***********************************************************************
411  *           InternetGetCookieA (WININET.@)
412  *
413  * Retrieve cookie from the specified url
414  *
415  * RETURNS
416  *    TRUE  on success
417  *    FALSE on failure
418  *
419  */
420 BOOL WINAPI InternetGetCookieA(LPCSTR lpszUrl, LPCSTR lpszCookieName,
421     LPSTR lpCookieData, LPDWORD lpdwSize)
422 {
423     DWORD len;
424     LPWSTR szCookieData = NULL, szUrl = NULL, szCookieName = NULL;
425     BOOL r;
426
427     TRACE("(%s,%s,%p)\n", debugstr_a(lpszUrl), debugstr_a(lpszCookieName),
428         lpCookieData);
429
430     if( lpszUrl )
431     {
432         len = MultiByteToWideChar( CP_ACP, 0, lpszUrl, -1, NULL, 0 );
433         szUrl = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) );
434         MultiByteToWideChar( CP_ACP, 0, lpszUrl, -1, szUrl, len );
435     }
436
437     if( lpszCookieName )
438     {
439         len = MultiByteToWideChar( CP_ACP, 0, lpszCookieName, -1, NULL, 0 );
440         szCookieName = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) );
441         MultiByteToWideChar( CP_ACP, 0, lpszCookieName, -1, szCookieName, len );
442     }
443
444     r = InternetGetCookieW( szUrl, szCookieName, NULL, &len );
445     if( r )
446     {
447         szCookieData = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) );
448         if( !szCookieData )
449             return FALSE;
450
451         r = InternetGetCookieW( szUrl, szCookieName, szCookieData, &len );
452
453         *lpdwSize = WideCharToMultiByte( CP_ACP, 0, szCookieData, len,
454                                 lpCookieData, *lpdwSize, NULL, NULL );
455     }
456
457     if( szCookieData )
458         HeapFree( GetProcessHeap(), 0, szCookieData );
459     if( szCookieName )
460         HeapFree( GetProcessHeap(), 0, szCookieName );
461     if( szUrl )
462         HeapFree( GetProcessHeap(), 0, szUrl );
463
464     return r;
465 }
466
467
468 /***********************************************************************
469  *           InternetSetCookieW (WININET.@)
470  *
471  * Sets cookie for the specified url
472  *
473  * RETURNS
474  *    TRUE  on success
475  *    FALSE on failure
476  *
477  */
478 BOOL WINAPI InternetSetCookieW(LPCWSTR lpszUrl, LPCWSTR lpszCookieName,
479     LPCWSTR lpCookieData)
480 {
481     cookie *thisCookie;
482     cookie_domain *thisCookieDomain;
483
484     TRACE("(%s,%s,%s)\n", debugstr_w(lpszUrl),
485         debugstr_w(lpszCookieName), debugstr_w(lpCookieData));
486
487     if (!lpCookieData || !strlenW(lpCookieData))
488     {
489         TRACE("no cookie data, not adding\n");
490         return FALSE;
491     }
492     if (!lpszCookieName)
493     {
494         /* some apps (or is it us??) try to add a cookie with no cookie name, but
495          * the cookie data in the form of name=data. */
496         /* FIXME, probably a bug here, for now I don't care */
497         WCHAR *ourCookieName, *ourCookieData;
498         int ourCookieNameSize;
499         BOOL ret;
500         if (!(ourCookieData = strchrW(lpCookieData, '=')))
501         {
502             TRACE("something terribly wrong with cookie data %s\n", 
503                    debugstr_w(ourCookieData));
504             return FALSE;
505         }
506         ourCookieNameSize = ourCookieData - lpCookieData;
507         ourCookieData += 1;
508         ourCookieName = HeapAlloc(GetProcessHeap(), 0, 
509                               (ourCookieNameSize + 1)*sizeof(WCHAR));
510         strncpyW(ourCookieName, ourCookieData, ourCookieNameSize);
511         ourCookieName[ourCookieNameSize] = '\0';
512         TRACE("setting (hacked) cookie of %s, %s\n",
513                debugstr_w(ourCookieName), debugstr_w(ourCookieData));
514         ret = InternetSetCookieW(lpszUrl, ourCookieName, ourCookieData);
515         HeapFree(GetProcessHeap(), 0, ourCookieName);
516         return ret;
517     }
518
519     if (!(thisCookieDomain = COOKIE_findNextDomainFromUrl(lpszUrl, NULL, FALSE)))
520         thisCookieDomain = COOKIE_addDomainFromUrl(lpszUrl);
521
522     if ((thisCookie = COOKIE_findCookie(thisCookieDomain, lpszCookieName)))
523         COOKIE_deleteCookie(thisCookie, FALSE);
524
525     thisCookie = COOKIE_addCookie(thisCookieDomain, lpszCookieName, lpCookieData);
526     return TRUE;
527 }
528
529
530 /***********************************************************************
531  *           InternetSetCookieA (WININET.@)
532  *
533  * Sets cookie for the specified url
534  *
535  * RETURNS
536  *    TRUE  on success
537  *    FALSE on failure
538  *
539  */
540 BOOL WINAPI InternetSetCookieA(LPCSTR lpszUrl, LPCSTR lpszCookieName,
541     LPCSTR lpCookieData)
542 {
543     DWORD len;
544     LPWSTR szCookieData = NULL, szUrl = NULL, szCookieName = NULL;
545     BOOL r;
546
547     TRACE("(%s,%s,%s)\n", debugstr_a(lpszUrl),
548         debugstr_a(lpszCookieName), debugstr_a(lpCookieData));
549
550     if( lpszUrl )
551     {
552         len = MultiByteToWideChar( CP_ACP, 0, lpszUrl, -1, NULL, 0 );
553         szUrl = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) );
554         MultiByteToWideChar( CP_ACP, 0, lpszUrl, -1, szUrl, len );
555     }
556
557     if( lpszCookieName )
558     {
559         len = MultiByteToWideChar( CP_ACP, 0, lpszCookieName, -1, NULL, 0 );
560         szCookieName = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) );
561         MultiByteToWideChar( CP_ACP, 0, lpszCookieName, -1, szCookieName, len );
562     }
563
564     if( lpCookieData )
565     {
566         len = MultiByteToWideChar( CP_ACP, 0, lpCookieData, -1, NULL, 0 );
567         szCookieData = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) );
568         MultiByteToWideChar( CP_ACP, 0, lpCookieData, -1, szCookieData, len );
569     }
570
571     r = InternetSetCookieW( szUrl, szCookieName, szCookieData );
572
573     if( szCookieData )
574         HeapFree( GetProcessHeap(), 0, szCookieData );
575     if( szCookieName )
576         HeapFree( GetProcessHeap(), 0, szCookieName );
577     if( szUrl )
578         HeapFree( GetProcessHeap(), 0, szUrl );
579
580     return r;
581 }