Fixed header dependencies to be fully compatible with the Windows
[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     LPSTR lpCookieName;
64     LPSTR 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     LPSTR lpCookieDomain;
74     LPSTR lpCookiePath;
75     cookie *cookie_tail;
76 };
77
78 static cookie_domain *cookieDomainTail;
79
80 static cookie *COOKIE_addCookie(cookie_domain *domain, LPCSTR name, LPCSTR data);
81 static cookie *COOKIE_findCookie(cookie_domain *domain, LPCSTR lpszCookieName);
82 static void COOKIE_deleteCookie(cookie *deadCookie, BOOL deleteDomain);
83 static cookie_domain *COOKIE_addDomain(LPCSTR domain, LPCSTR path);
84 static cookie_domain *COOKIE_addDomainFromUrl(LPCSTR lpszUrl);
85 static cookie_domain *COOKIE_findNextDomain(LPCSTR lpszCookieDomain, LPCSTR lpszCookiePath,
86                                             cookie_domain *prev_domain, BOOL allow_partial);
87 static cookie_domain *COOKIE_findNextDomainFromUrl(LPCSTR 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, LPCSTR name, LPCSTR 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, strlen(name) + 1);
105         strcpy(newCookie->lpCookieName, name);
106     }
107     if (data)
108     {
109         newCookie->lpCookieData = HeapAlloc(GetProcessHeap(), 0, strlen(data) + 1);
110         strcpy(newCookie->lpCookieData, data);
111     }
112
113     TRACE("added cookie %p (data is %s)\n", newCookie, 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, LPCSTR lpszCookieName)
124 {
125     cookie *searchCookie = domain->cookie_tail;
126     TRACE("(%p, %s)\n", domain, debugstr_a(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 && strcmp(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(LPCSTR domain, LPCSTR 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, strlen(domain) + 1);
180         strcpy(newDomain->lpCookieDomain, domain);
181     }
182     if (path)
183     {
184         newDomain->lpCookiePath = HeapAlloc(GetProcessHeap(), 0, strlen(path) + 1);
185         strcpy(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(LPCSTR lpszUrl)
195 {
196     char hostName[2048], path[2048];
197     URL_COMPONENTSA 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     InternetCrackUrlA(lpszUrl, 0, 0, &UrlComponents);
209
210     TRACE("Url cracked. Domain: %s, Path: %s.\n", debugstr_a(UrlComponents.lpszHostName),
211           debugstr_a(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(LPCSTR lpszCookieDomain, LPCSTR 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", lpszCookieDomain, searchDomain->lpCookieDomain);
246             if (candidate && allow_partial && !strstr(lpszCookieDomain, searchDomain->lpCookieDomain))
247                 candidate = FALSE;
248             else if (candidate && !allow_partial &&
249                      strcmp(lpszCookieDomain, searchDomain->lpCookieDomain) != 0)
250                 candidate = FALSE;
251         }
252         if (candidate && lpszCookiePath)
253         {                           TRACE("comparing paths\n");
254             if (candidate && !searchDomain->lpCookiePath)
255                 candidate = FALSE;
256             if (candidate && strcmp(lpszCookiePath, searchDomain->lpCookiePath) != 0)
257                 candidate = FALSE;
258         }
259         if (candidate)
260         {
261             TRACE("returning the domain %p\n", searchDomain);
262             return searchDomain;
263         }
264         searchDomain = searchDomain->prev;
265     }
266     TRACE("found no domain, returning NULL\n");
267     return NULL;
268 }
269
270 static cookie_domain *COOKIE_findNextDomainFromUrl(LPCSTR lpszUrl, cookie_domain *previous_domain,
271                                                    BOOL allow_partial)
272 {
273     char hostName[2048], path[2048];
274     URL_COMPONENTSA UrlComponents;
275
276     UrlComponents.lpszExtraInfo = NULL;
277     UrlComponents.lpszPassword = NULL;
278     UrlComponents.lpszScheme = NULL;
279     UrlComponents.lpszUrlPath = path;
280     UrlComponents.lpszUserName = NULL;
281     UrlComponents.lpszHostName = hostName;
282     UrlComponents.dwHostNameLength = 2048;
283     UrlComponents.dwUrlPathLength = 2048;
284
285     InternetCrackUrlA(lpszUrl, 0, 0, &UrlComponents);
286
287     TRACE("Url cracked. Domain: %s, Path: %s.\n", debugstr_a(UrlComponents.lpszHostName),
288           debugstr_a(UrlComponents.lpszUrlPath));
289
290     /* hack for now - FIXME - There seems to be a bug in InternetCrackUrl?? */
291     UrlComponents.lpszUrlPath = NULL;
292
293     return COOKIE_findNextDomain(UrlComponents.lpszHostName, UrlComponents.lpszUrlPath,
294                                  previous_domain, allow_partial);
295 }
296
297 /* remove a domain from the list and delete it */
298 static void COOKIE_deleteDomain(cookie_domain *deadDomain)
299 {
300     while (deadDomain->cookie_tail)
301         COOKIE_deleteCookie(deadDomain->cookie_tail, FALSE);
302     if (deadDomain->lpCookieDomain)
303         HeapFree(GetProcessHeap(), 0, deadDomain->lpCookieDomain);
304     if (deadDomain->lpCookiePath)
305         HeapFree(GetProcessHeap(), 0, deadDomain->lpCookiePath);
306     if (deadDomain->prev)
307         deadDomain->prev->next = deadDomain->next;
308     if (deadDomain->next)
309         deadDomain->next->prev = deadDomain->prev;
310
311     if (cookieDomainTail == deadDomain)
312         cookieDomainTail = deadDomain->prev;
313     HeapFree(GetProcessHeap(), 0, deadDomain);
314 }
315
316 /***********************************************************************
317  *           InternetGetCookieA (WININET.@)
318  *
319  * Retrieve cookie from the specified url
320  *
321  *  It should be noted that on windows the lpszCookieName parameter is "not implemented".
322  *    So it won't be implemented here.
323  *
324  * RETURNS
325  *    TRUE  on success
326  *    FALSE on failure
327  *
328  */
329 BOOL WINAPI InternetGetCookieA(LPCSTR lpszUrl, LPCSTR lpszCookieName,
330     LPSTR lpCookieData, LPDWORD lpdwSize)
331 {
332     cookie_domain *cookiesDomain = NULL;
333     cookie *thisCookie;
334     int cnt = 0, domain_count = 0;
335     /* Ok, this is just ODD!. During my tests, it appears M$ like to send out
336      * a cookie called 'MtrxTracking' to some urls. Also returns it from InternetGetCookie.
337      * I'm not exactly sure what to make of this, so its here for now.
338      * It'd be nice to know what exactly is going on, M$ tracking users? Does this need
339      * to be unique? Should I generate a random number here? etc.
340      */
341     char *TrackingString = "MtrxTrackingID=01234567890123456789012345678901";
342
343     TRACE("(%s, %s, %p, %p)\n", debugstr_a(lpszUrl),debugstr_a(lpszCookieName),
344           lpCookieData, lpdwSize);
345
346     if (lpCookieData)
347         cnt += snprintf(lpCookieData + cnt, *lpdwSize - cnt, "%s", TrackingString);
348     else
349         cnt += strlen(TrackingString);
350
351     while ((cookiesDomain = COOKIE_findNextDomainFromUrl(lpszUrl, cookiesDomain, TRUE)))
352     {
353         domain_count++;
354         TRACE("found domain %p\n", cookiesDomain);
355
356         thisCookie = cookiesDomain->cookie_tail;
357         if (lpCookieData == NULL) /* return the size of the buffer required to lpdwSize */
358         {
359             while (thisCookie)
360             {
361                 cnt += 2; /* '; ' */
362                 cnt += strlen(thisCookie->lpCookieName);
363                 cnt += 1; /* = */
364                 cnt += strlen(thisCookie->lpCookieData);
365
366                 thisCookie = thisCookie->prev;
367             }
368         }
369         while (thisCookie)
370         {
371             cnt += snprintf(lpCookieData + cnt, *lpdwSize - cnt, "; ");
372             cnt += snprintf(lpCookieData + cnt, *lpdwSize - cnt, "%s=%s", thisCookie->lpCookieName,
373                             thisCookie->lpCookieData);
374
375             thisCookie = thisCookie->prev;
376         }
377     }
378     if (lpCookieData == NULL)
379     {
380         cnt += 1; /* NULL */
381         *lpdwSize = cnt;
382         TRACE("returning\n");
383         return TRUE;
384     }
385
386     if (!domain_count)
387         return FALSE;
388
389     *lpdwSize = cnt + 1;
390
391     TRACE("Returning %i (from %i domains): %s\n", cnt, domain_count, lpCookieData);
392
393     return (cnt ? TRUE : FALSE);
394 }
395
396
397 /***********************************************************************
398  *           InternetGetCookieW (WININET.@)
399  *
400  * Retrieve cookie from the specified url
401  *
402  * RETURNS
403  *    TRUE  on success
404  *    FALSE on failure
405  *
406  */
407 BOOL WINAPI InternetGetCookieW(LPCSTR lpszUrl, LPCWSTR lpszCookieName,
408     LPWSTR lpCookieData, LPDWORD lpdwSize)
409 {
410     FIXME("STUB\n");
411     TRACE("(%s,%s,%p)\n", debugstr_a(lpszUrl), debugstr_w(lpszCookieName),
412         lpCookieData);
413     return FALSE;
414 }
415
416
417 /***********************************************************************
418  *           InternetSetCookieA (WININET.@)
419  *
420  * Sets cookie for the specified url
421  *
422  * RETURNS
423  *    TRUE  on success
424  *    FALSE on failure
425  *
426  */
427 BOOL WINAPI InternetSetCookieA(LPCSTR lpszUrl, LPCSTR lpszCookieName,
428     LPCSTR lpCookieData)
429 {
430     cookie *thisCookie;
431     cookie_domain *thisCookieDomain;
432
433     TRACE("(%s,%s,%s)\n", debugstr_a(lpszUrl),
434         debugstr_a(lpszCookieName), lpCookieData);
435
436     if (!lpCookieData || !strlen(lpCookieData))
437     {
438         TRACE("no cookie data, not adding\n");
439         return FALSE;
440     }
441     if (!lpszCookieName)
442     {
443         /* some apps (or is it us??) try to add a cookie with no cookie name, but
444          * the cookie data in the form of name=data. */
445         /* FIXME, probably a bug here, for now I don't care */
446         char *ourCookieName, *ourCookieData;
447         int ourCookieNameSize;
448         BOOL ret;
449         if (!(ourCookieData = strchr(lpCookieData, '=')))
450         {
451             TRACE("something terribly wrong with cookie data %s\n", ourCookieData);
452             return FALSE;
453         }
454         ourCookieNameSize = ourCookieData - lpCookieData;
455         ourCookieData += 1;
456         ourCookieName = HeapAlloc(GetProcessHeap(), 0, ourCookieNameSize + 1);
457         strncpy(ourCookieName, ourCookieData, ourCookieNameSize);
458         ourCookieName[ourCookieNameSize] = '\0';
459         TRACE("setting (hacked) cookie of %s, %s\n", ourCookieName, ourCookieData);
460         ret = InternetSetCookieA(lpszUrl, ourCookieName, ourCookieData);
461         HeapFree(GetProcessHeap(), 0, ourCookieName);
462         return ret;
463     }
464
465     if (!(thisCookieDomain = COOKIE_findNextDomainFromUrl(lpszUrl, NULL, FALSE)))
466         thisCookieDomain = COOKIE_addDomainFromUrl(lpszUrl);
467
468     if ((thisCookie = COOKIE_findCookie(thisCookieDomain, lpszCookieName)))
469         COOKIE_deleteCookie(thisCookie, FALSE);
470
471     thisCookie = COOKIE_addCookie(thisCookieDomain, lpszCookieName, lpCookieData);
472     return TRUE;
473 }
474
475
476 /***********************************************************************
477  *           InternetSetCookieW (WININET.@)
478  *
479  * Sets cookie for the specified url
480  *
481  * RETURNS
482  *    TRUE  on success
483  *    FALSE on failure
484  *
485  */
486 BOOL WINAPI InternetSetCookieW(LPCSTR lpszUrl, LPCWSTR lpszCookieName,
487     LPCWSTR lpCookieData)
488 {
489     FIXME("STUB\n");
490     TRACE("(%s,%s,%s)\n", debugstr_a(lpszUrl),
491         debugstr_w(lpszCookieName), debugstr_w(lpCookieData));
492     return FALSE;
493 }