xref: /reactos/dll/win32/wininet/cookie.c (revision 02e84521)
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21  */
22 
23 #include "ws2tcpip.h"
24 
25 #include <stdarg.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <assert.h>
30 
31 #include "windef.h"
32 #include "winbase.h"
33 #include "wininet.h"
34 #include "lmcons.h"
35 #include "winerror.h"
36 
37 #include "wine/debug.h"
38 #include "internet.h"
39 
40 #define RESPONSE_TIMEOUT        30            /* FROM internet.c */
41 
42 
43 WINE_DEFAULT_DEBUG_CHANNEL(wininet);
44 
45 /* FIXME
46  *     Cookies could use A LOT OF MEMORY. We need some kind of memory management here!
47  */
48 
49 struct _cookie_domain_t;
50 struct _cookie_container_t;
51 
52 typedef struct _cookie_t {
53     struct list entry;
54 
55     struct _cookie_container_t *container;
56 
57     WCHAR *name;
58     WCHAR *data;
59     DWORD flags;
60     FILETIME expiry;
61     FILETIME create;
62 } cookie_t;
63 
64 typedef struct _cookie_container_t {
65     struct list entry;
66 
67     WCHAR *cookie_url;
68     substr_t path;
69     struct _cookie_domain_t *domain;
70 
71     struct list cookie_list;
72 } cookie_container_t;
73 
74 typedef struct _cookie_domain_t {
75     struct list entry;
76 
77     WCHAR *domain;
78     unsigned subdomain_len;
79 
80     struct _cookie_domain_t *parent;
81     struct list subdomain_list;
82 
83     /* List of stored paths sorted by length of the path. */
84     struct list path_list;
85 } cookie_domain_t;
86 
87 static CRITICAL_SECTION cookie_cs;
88 static CRITICAL_SECTION_DEBUG cookie_cs_debug =
89 {
90     0, 0, &cookie_cs,
91     { &cookie_cs_debug.ProcessLocksList, &cookie_cs_debug.ProcessLocksList },
92     0, 0, { (DWORD_PTR)(__FILE__ ": cookie_cs") }
93 };
94 static CRITICAL_SECTION cookie_cs = { &cookie_cs_debug, -1, 0, 0, 0, 0 };
95 static struct list domain_list = LIST_INIT(domain_list);
96 
97 static cookie_domain_t *get_cookie_domain(substr_t domain, BOOL create)
98 {
99     const WCHAR *ptr = domain.str + domain.len, *ptr_end, *subdomain_ptr;
100     cookie_domain_t *iter, *current_domain, *prev_domain = NULL;
101     struct list *current_list = &domain_list;
102 
103     while(1) {
104         for(ptr_end = ptr--; ptr > domain.str && *ptr != '.'; ptr--);
105         subdomain_ptr = *ptr == '.' ? ptr+1 : ptr;
106 
107         current_domain = NULL;
108         LIST_FOR_EACH_ENTRY(iter, current_list, cookie_domain_t, entry) {
109             if(ptr_end-subdomain_ptr == iter->subdomain_len
110                     && !memcmp(subdomain_ptr, iter->domain, iter->subdomain_len*sizeof(WCHAR))) {
111                 current_domain = iter;
112                 break;
113             }
114         }
115 
116         if(!current_domain) {
117             if(!create)
118                 return prev_domain;
119 
120             current_domain = heap_alloc(sizeof(*current_domain));
121             if(!current_domain)
122                 return NULL;
123 
124             current_domain->domain = heap_strndupW(subdomain_ptr, domain.str + domain.len - subdomain_ptr);
125             if(!current_domain->domain) {
126                 heap_free(current_domain);
127                 return NULL;
128             }
129 
130             current_domain->subdomain_len = ptr_end-subdomain_ptr;
131 
132             current_domain->parent = prev_domain;
133             list_init(&current_domain->path_list);
134             list_init(&current_domain->subdomain_list);
135 
136             list_add_tail(current_list, &current_domain->entry);
137         }
138 
139         if(ptr == domain.str)
140             return current_domain;
141 
142         prev_domain = current_domain;
143         current_list = &current_domain->subdomain_list;
144     }
145 }
146 
147 static WCHAR *create_cookie_url(substr_t domain, substr_t path, substr_t *ret_path)
148 {
149     WCHAR user[UNLEN], *p, *url;
150     DWORD len, user_len, i;
151 
152     static const WCHAR cookie_prefix[] = {'C','o','o','k','i','e',':'};
153 
154     user_len = sizeof(user)/sizeof(WCHAR);
155     if(!GetUserNameW(user, &user_len))
156         return FALSE;
157     user_len--;
158 
159     len = sizeof(cookie_prefix)/sizeof(WCHAR) + user_len + 1 /* @ */ + domain.len + path.len;
160     url = heap_alloc((len+1) * sizeof(WCHAR));
161     if(!url)
162         return NULL;
163 
164     memcpy(url, cookie_prefix, sizeof(cookie_prefix));
165     p = url + sizeof(cookie_prefix)/sizeof(WCHAR);
166 
167     memcpy(p, user, user_len*sizeof(WCHAR));
168     p += user_len;
169 
170     *p++ = '@';
171 
172     memcpy(p, domain.str, domain.len*sizeof(WCHAR));
173     p += domain.len;
174 
175     for(i=0; i < path.len; i++)
176         p[i] = tolowerW(path.str[i]);
177     p[path.len] = 0;
178 
179     ret_path->str = p;
180     ret_path->len = path.len;
181     return url;
182 }
183 
184 static cookie_container_t *get_cookie_container(substr_t domain, substr_t path, BOOL create)
185 {
186     cookie_domain_t *cookie_domain;
187     cookie_container_t *cookie_container, *iter;
188 
189     cookie_domain = get_cookie_domain(domain, create);
190     if(!cookie_domain)
191         return NULL;
192 
193     LIST_FOR_EACH_ENTRY(cookie_container, &cookie_domain->path_list, cookie_container_t, entry) {
194         if(cookie_container->path.len < path.len)
195             break;
196 
197         if(path.len == cookie_container->path.len && !strncmpiW(cookie_container->path.str, path.str, path.len))
198             return cookie_container;
199     }
200 
201     if(!create)
202         return NULL;
203 
204     cookie_container = heap_alloc(sizeof(*cookie_container));
205     if(!cookie_container)
206         return NULL;
207 
208     cookie_container->cookie_url = create_cookie_url(substrz(cookie_domain->domain), path, &cookie_container->path);
209     if(!cookie_container->cookie_url) {
210         heap_free(cookie_container);
211         return NULL;
212     }
213 
214     cookie_container->domain = cookie_domain;
215     list_init(&cookie_container->cookie_list);
216 
217     LIST_FOR_EACH_ENTRY(iter, &cookie_domain->path_list, cookie_container_t, entry) {
218         if(iter->path.len <= path.len) {
219             list_add_before(&iter->entry, &cookie_container->entry);
220             return cookie_container;
221         }
222     }
223 
224     list_add_tail(&cookie_domain->path_list, &cookie_container->entry);
225     return cookie_container;
226 }
227 
228 static void delete_cookie(cookie_t *cookie)
229 {
230     list_remove(&cookie->entry);
231 
232     heap_free(cookie->name);
233     heap_free(cookie->data);
234     heap_free(cookie);
235 }
236 
237 static cookie_t *alloc_cookie(substr_t name, substr_t data, FILETIME expiry, FILETIME create_time, DWORD flags)
238 {
239     cookie_t *new_cookie;
240 
241     new_cookie = heap_alloc(sizeof(*new_cookie));
242     if(!new_cookie)
243         return NULL;
244 
245     new_cookie->expiry = expiry;
246     new_cookie->create = create_time;
247     new_cookie->flags = flags;
248     list_init(&new_cookie->entry);
249 
250     new_cookie->name = heap_strndupW(name.str, name.len);
251     new_cookie->data = heap_strndupW(data.str, data.len);
252     if(!new_cookie->name || !new_cookie->data) {
253         delete_cookie(new_cookie);
254         return NULL;
255     }
256 
257     return new_cookie;
258 }
259 
260 static cookie_t *find_cookie(cookie_container_t *container, substr_t name)
261 {
262     cookie_t *iter;
263 
264     LIST_FOR_EACH_ENTRY(iter, &container->cookie_list, cookie_t, entry) {
265         if(strlenW(iter->name) == name.len && !strncmpiW(iter->name, name.str, name.len))
266             return iter;
267     }
268 
269     return NULL;
270 }
271 
272 static void add_cookie(cookie_container_t *container, cookie_t *new_cookie)
273 {
274     TRACE("Adding %s=%s to %s\n", debugstr_w(new_cookie->name), debugstr_w(new_cookie->data),
275           debugstr_w(container->cookie_url));
276 
277     list_add_tail(&container->cookie_list, &new_cookie->entry);
278     new_cookie->container = container;
279 }
280 
281 static void replace_cookie(cookie_container_t *container, cookie_t *new_cookie)
282 {
283     cookie_t *old_cookie;
284 
285     old_cookie = find_cookie(container, substrz(new_cookie->name));
286     if(old_cookie)
287         delete_cookie(old_cookie);
288 
289     add_cookie(container, new_cookie);
290 }
291 
292 static BOOL cookie_match_path(cookie_container_t *container, substr_t path)
293 {
294     return path.len >= container->path.len && !strncmpiW(container->path.str, path.str, container->path.len);
295 }
296 
297 static BOOL load_persistent_cookie(substr_t domain, substr_t path)
298 {
299     INTERNET_CACHE_ENTRY_INFOW *info;
300     cookie_container_t *cookie_container;
301     cookie_t *new_cookie;
302     HANDLE cookie;
303     char *str = NULL, *pbeg, *pend;
304     DWORD size, flags;
305     WCHAR *name, *data;
306     FILETIME expiry, create, time;
307 
308     cookie_container = get_cookie_container(domain, path, TRUE);
309     if(!cookie_container)
310         return FALSE;
311 
312     size = 0;
313     RetrieveUrlCacheEntryStreamW(cookie_container->cookie_url, NULL, &size, FALSE, 0);
314     if(GetLastError() != ERROR_INSUFFICIENT_BUFFER)
315         return TRUE;
316     info = heap_alloc(size);
317     if(!info)
318         return FALSE;
319     cookie = RetrieveUrlCacheEntryStreamW(cookie_container->cookie_url, info, &size, FALSE, 0);
320     size = info->dwSizeLow;
321     heap_free(info);
322     if(!cookie)
323         return FALSE;
324 
325     if(!(str = heap_alloc(size+1)) || !ReadUrlCacheEntryStream(cookie, 0, str, &size, 0)) {
326         UnlockUrlCacheEntryStream(cookie, 0);
327         heap_free(str);
328         return FALSE;
329     }
330     str[size] = 0;
331     UnlockUrlCacheEntryStream(cookie, 0);
332 
333     GetSystemTimeAsFileTime(&time);
334     for(pbeg=str; pbeg && *pbeg; name=data=NULL) {
335         pend = strchr(pbeg, '\n');
336         if(!pend)
337             break;
338         *pend = 0;
339         name = heap_strdupAtoW(pbeg);
340 
341         pbeg = pend+1;
342         pend = strchr(pbeg, '\n');
343         if(!pend)
344             break;
345         *pend = 0;
346         data = heap_strdupAtoW(pbeg);
347 
348         pbeg = strchr(pend+1, '\n');
349         if(!pbeg)
350             break;
351         sscanf(pbeg, "%u %u %u %u %u", &flags, &expiry.dwLowDateTime, &expiry.dwHighDateTime,
352                 &create.dwLowDateTime, &create.dwHighDateTime);
353 
354         /* skip "*\n" */
355         pbeg = strchr(pbeg, '*');
356         if(pbeg) {
357             pbeg++;
358             if(*pbeg)
359                 pbeg++;
360         }
361 
362         if(!name || !data)
363             break;
364 
365         if(CompareFileTime(&time, &expiry) <= 0) {
366             new_cookie = alloc_cookie(substr(NULL, 0), substr(NULL, 0), expiry, create, flags);
367             if(!new_cookie)
368                 break;
369 
370             new_cookie->name = name;
371             new_cookie->data = data;
372 
373             replace_cookie(cookie_container, new_cookie);
374         }else {
375             heap_free(name);
376             heap_free(data);
377         }
378     }
379     heap_free(str);
380     heap_free(name);
381     heap_free(data);
382 
383     return TRUE;
384 }
385 
386 static BOOL save_persistent_cookie(cookie_container_t *container)
387 {
388     static const WCHAR txtW[] = {'t','x','t',0};
389 
390     WCHAR cookie_file[MAX_PATH];
391     HANDLE cookie_handle;
392     cookie_t *cookie_container = NULL, *cookie_iter;
393     BOOL do_save = FALSE;
394     char buf[64], *dyn_buf;
395     FILETIME time;
396     DWORD bytes_written;
397     size_t len;
398 
399     /* check if there's anything to save */
400     GetSystemTimeAsFileTime(&time);
401     LIST_FOR_EACH_ENTRY_SAFE(cookie_container, cookie_iter, &container->cookie_list, cookie_t, entry)
402     {
403         if((cookie_container->expiry.dwLowDateTime || cookie_container->expiry.dwHighDateTime)
404                 && CompareFileTime(&time, &cookie_container->expiry) > 0) {
405             delete_cookie(cookie_container);
406             continue;
407         }
408 
409         if(!(cookie_container->flags & INTERNET_COOKIE_IS_SESSION)) {
410             do_save = TRUE;
411             break;
412         }
413     }
414 
415     if(!do_save) {
416         DeleteUrlCacheEntryW(container->cookie_url);
417         return TRUE;
418     }
419 
420     if(!CreateUrlCacheEntryW(container->cookie_url, 0, txtW, cookie_file, 0))
421         return FALSE;
422 
423     cookie_handle = CreateFileW(cookie_file, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
424     if(cookie_handle == INVALID_HANDLE_VALUE) {
425         DeleteFileW(cookie_file);
426         return FALSE;
427     }
428 
429     LIST_FOR_EACH_ENTRY(cookie_container, &container->cookie_list, cookie_t, entry)
430     {
431         if(cookie_container->flags & INTERNET_COOKIE_IS_SESSION)
432             continue;
433 
434         dyn_buf = heap_strdupWtoA(cookie_container->name);
435         if(!dyn_buf || !WriteFile(cookie_handle, dyn_buf, strlen(dyn_buf), &bytes_written, NULL)) {
436             heap_free(dyn_buf);
437             do_save = FALSE;
438             break;
439         }
440         heap_free(dyn_buf);
441         if(!WriteFile(cookie_handle, "\n", 1, &bytes_written, NULL)) {
442             do_save = FALSE;
443             break;
444         }
445 
446         dyn_buf = heap_strdupWtoA(cookie_container->data);
447         if(!dyn_buf || !WriteFile(cookie_handle, dyn_buf, strlen(dyn_buf), &bytes_written, NULL)) {
448             heap_free(dyn_buf);
449             do_save = FALSE;
450             break;
451         }
452         heap_free(dyn_buf);
453         if(!WriteFile(cookie_handle, "\n", 1, &bytes_written, NULL)) {
454             do_save = FALSE;
455             break;
456         }
457 
458         dyn_buf = heap_strdupWtoA(container->domain->domain);
459         if(!dyn_buf || !WriteFile(cookie_handle, dyn_buf, strlen(dyn_buf), &bytes_written, NULL)) {
460             heap_free(dyn_buf);
461             do_save = FALSE;
462             break;
463         }
464         heap_free(dyn_buf);
465 
466         len = WideCharToMultiByte(CP_ACP, 0, container->path.str, container->path.len, NULL, 0, NULL, NULL);
467         dyn_buf = heap_alloc(len+1);
468         if(dyn_buf) {
469             WideCharToMultiByte(CP_ACP, 0, container->path.str, container->path.len, dyn_buf, len, NULL, NULL);
470             dyn_buf[len] = 0;
471         }
472         if(!dyn_buf || !WriteFile(cookie_handle, dyn_buf, strlen(dyn_buf), &bytes_written, NULL)) {
473             heap_free(dyn_buf);
474             do_save = FALSE;
475             break;
476         }
477         heap_free(dyn_buf);
478 
479         sprintf(buf, "\n%u\n%u\n%u\n%u\n%u\n*\n", cookie_container->flags,
480                 cookie_container->expiry.dwLowDateTime, cookie_container->expiry.dwHighDateTime,
481                 cookie_container->create.dwLowDateTime, cookie_container->create.dwHighDateTime);
482         if(!WriteFile(cookie_handle, buf, strlen(buf), &bytes_written, NULL)) {
483             do_save = FALSE;
484             break;
485         }
486     }
487 
488     CloseHandle(cookie_handle);
489     if(!do_save) {
490         ERR("error saving cookie file\n");
491         DeleteFileW(cookie_file);
492         return FALSE;
493     }
494 
495     memset(&time, 0, sizeof(time));
496     return CommitUrlCacheEntryW(container->cookie_url, cookie_file, time, time, 0, NULL, 0, txtW, 0);
497 }
498 
499 static BOOL cookie_parse_url(const WCHAR *url, substr_t *host, substr_t *path)
500 {
501     URL_COMPONENTSW comp = { sizeof(comp) };
502     static const WCHAR rootW[] = {'/',0};
503 
504     comp.dwHostNameLength = 1;
505     comp.dwUrlPathLength = 1;
506 
507     if(!InternetCrackUrlW(url, 0, 0, &comp) || !comp.dwHostNameLength)
508         return FALSE;
509 
510     /* discard the webpage off the end of the path */
511     while(comp.dwUrlPathLength && comp.lpszUrlPath[comp.dwUrlPathLength-1] != '/')
512         comp.dwUrlPathLength--;
513 
514     *host = substr(comp.lpszHostName, comp.dwHostNameLength);
515     *path = comp.dwUrlPathLength ? substr(comp.lpszUrlPath, comp.dwUrlPathLength) : substr(rootW, 1);
516     return TRUE;
517 }
518 
519 typedef struct {
520     cookie_t **cookies;
521     unsigned cnt;
522     unsigned size;
523 
524     unsigned string_len;
525 } cookie_set_t;
526 
527 static DWORD get_cookie(substr_t host, substr_t path, DWORD flags, cookie_set_t *res)
528 {
529     static const WCHAR empty_path[] = { '/',0 };
530 
531     const WCHAR *p;
532     cookie_domain_t *domain;
533     cookie_container_t *container;
534     FILETIME tm;
535 
536     GetSystemTimeAsFileTime(&tm);
537 
538     p = host.str + host.len;
539     while(p > host.str && p[-1] != '.') p--;
540     while(p != host.str) {
541         p--;
542         while(p > host.str && p[-1] != '.') p--;
543         if(p == host.str) break;
544 
545         load_persistent_cookie(substr(p, host.str+host.len-p), substr(empty_path, 1));
546     }
547 
548     p = path.str + path.len;
549     do {
550         load_persistent_cookie(host, substr(path.str, p-path.str));
551 
552         p--;
553         while(p > path.str && p[-1] != '/') p--;
554     }while(p != path.str);
555 
556     domain = get_cookie_domain(host, FALSE);
557     if(!domain) {
558         TRACE("Unknown host %s\n", debugstr_wn(host.str, host.len));
559         return ERROR_NO_MORE_ITEMS;
560     }
561 
562     for(domain = get_cookie_domain(host, FALSE); domain; domain = domain->parent) {
563         LIST_FOR_EACH_ENTRY(container, &domain->path_list, cookie_container_t, entry) {
564             struct list *cursor, *cursor2;
565 
566             if(!cookie_match_path(container, path))
567                 continue;
568 
569             LIST_FOR_EACH_SAFE(cursor, cursor2, &container->cookie_list) {
570                 cookie_t *cookie_iter = LIST_ENTRY(cursor, cookie_t, entry);
571 
572                 /* check for expiry */
573                 if((cookie_iter->expiry.dwLowDateTime != 0 || cookie_iter->expiry.dwHighDateTime != 0)
574                     && CompareFileTime(&tm, &cookie_iter->expiry)  > 0) {
575                     TRACE("Found expired cookie. deleting\n");
576                     delete_cookie(cookie_iter);
577                     continue;
578                 }
579 
580                 if((cookie_iter->flags & INTERNET_COOKIE_HTTPONLY) && !(flags & INTERNET_COOKIE_HTTPONLY))
581                     continue;
582 
583                 if(!res->size) {
584                     res->cookies = heap_alloc(4*sizeof(*res->cookies));
585                     if(!res->cookies)
586                         continue;
587                     res->size = 4;
588                 }else if(res->cnt == res->size) {
589                     cookie_t **new_cookies = heap_realloc(res->cookies, res->size*2*sizeof(*res->cookies));
590                     if(!new_cookies)
591                         continue;
592                     res->cookies = new_cookies;
593                     res->size *= 2;
594                 }
595 
596                 TRACE("%s = %s domain %s path %s\n", debugstr_w(cookie_iter->name), debugstr_w(cookie_iter->data),
597                       debugstr_w(domain->domain), debugstr_wn(container->path.str, container->path.len));
598 
599                 if(res->cnt)
600                     res->string_len += 2; /* '; ' */
601                 res->cookies[res->cnt++] = cookie_iter;
602 
603                 res->string_len += strlenW(cookie_iter->name);
604                 if(*cookie_iter->data)
605                     res->string_len += 1 /* = */ + strlenW(cookie_iter->data);
606             }
607         }
608     }
609 
610     return ERROR_SUCCESS;
611 }
612 
613 static void cookie_set_to_string(const cookie_set_t *cookie_set, WCHAR *str)
614 {
615     WCHAR *ptr = str;
616     unsigned i, len;
617 
618     for(i=0; i<cookie_set->cnt; i++) {
619         if(i) {
620             *ptr++ = ';';
621             *ptr++ = ' ';
622         }
623 
624         len = strlenW(cookie_set->cookies[i]->name);
625         memcpy(ptr, cookie_set->cookies[i]->name, len*sizeof(WCHAR));
626         ptr += len;
627 
628         if(*cookie_set->cookies[i]->data) {
629             *ptr++ = '=';
630             len = strlenW(cookie_set->cookies[i]->data);
631             memcpy(ptr, cookie_set->cookies[i]->data, len*sizeof(WCHAR));
632             ptr += len;
633         }
634     }
635 
636     assert(ptr-str == cookie_set->string_len);
637     TRACE("%s\n", debugstr_wn(str, ptr-str));
638 }
639 
640 DWORD get_cookie_header(const WCHAR *host, const WCHAR *path, WCHAR **ret)
641 {
642     cookie_set_t cookie_set = {0};
643     DWORD res;
644 
645     static const WCHAR cookieW[] = {'C','o','o','k','i','e',':',' '};
646 
647     EnterCriticalSection(&cookie_cs);
648 
649     res = get_cookie(substrz(host), substrz(path), INTERNET_COOKIE_HTTPONLY, &cookie_set);
650     if(res != ERROR_SUCCESS) {
651         LeaveCriticalSection(&cookie_cs);
652         return res;
653     }
654 
655     if(cookie_set.cnt) {
656         WCHAR *header, *ptr;
657 
658         ptr = header = heap_alloc(sizeof(cookieW) + (cookie_set.string_len + 3 /* crlf0 */) * sizeof(WCHAR));
659         if(header) {
660             memcpy(ptr, cookieW, sizeof(cookieW));
661             ptr += sizeof(cookieW)/sizeof(*cookieW);
662 
663             cookie_set_to_string(&cookie_set, ptr);
664             heap_free(cookie_set.cookies);
665             ptr += cookie_set.string_len;
666 
667             *ptr++ = '\r';
668             *ptr++ = '\n';
669             *ptr++ = 0;
670 
671             *ret = header;
672         }else {
673             res = ERROR_NOT_ENOUGH_MEMORY;
674         }
675     }else {
676         *ret = NULL;
677     }
678 
679     LeaveCriticalSection(&cookie_cs);
680     return res;
681 }
682 
683 static void free_cookie_domain_list(struct list *list)
684 {
685     cookie_container_t *container;
686     cookie_domain_t *domain;
687 
688     while(!list_empty(list)) {
689         domain = LIST_ENTRY(list_head(list), cookie_domain_t, entry);
690 
691         free_cookie_domain_list(&domain->subdomain_list);
692 
693         while(!list_empty(&domain->path_list)) {
694             container = LIST_ENTRY(list_head(&domain->path_list), cookie_container_t, entry);
695 
696             while(!list_empty(&container->cookie_list))
697                 delete_cookie(LIST_ENTRY(list_head(&container->cookie_list), cookie_t, entry));
698 
699             heap_free(container->cookie_url);
700             list_remove(&container->entry);
701             heap_free(container);
702         }
703 
704         heap_free(domain->domain);
705         list_remove(&domain->entry);
706         heap_free(domain);
707     }
708 }
709 
710 /***********************************************************************
711  *           InternetGetCookieExW (WININET.@)
712  *
713  * Retrieve cookie from the specified url
714  *
715  *  It should be noted that on windows the lpszCookieName parameter is "not implemented".
716  *    So it won't be implemented here.
717  *
718  * RETURNS
719  *    TRUE  on success
720  *    FALSE on failure
721  *
722  */
723 BOOL WINAPI InternetGetCookieExW(LPCWSTR lpszUrl, LPCWSTR lpszCookieName,
724         LPWSTR lpCookieData, LPDWORD lpdwSize, DWORD flags, void *reserved)
725 {
726     cookie_set_t cookie_set = {0};
727     substr_t host, path;
728     DWORD res;
729     BOOL ret;
730 
731     TRACE("(%s, %s, %p, %p, %x, %p)\n", debugstr_w(lpszUrl),debugstr_w(lpszCookieName), lpCookieData, lpdwSize, flags, reserved);
732 
733     if (flags)
734         FIXME("flags 0x%08x not supported\n", flags);
735 
736     if (!lpszUrl)
737     {
738         SetLastError(ERROR_INVALID_PARAMETER);
739         return FALSE;
740     }
741 
742     ret = cookie_parse_url(lpszUrl, &host, &path);
743     if (!ret) {
744         SetLastError(ERROR_INVALID_PARAMETER);
745         return FALSE;
746     }
747 
748     EnterCriticalSection(&cookie_cs);
749 
750     res = get_cookie(host, path, flags, &cookie_set);
751     if(res != ERROR_SUCCESS) {
752         LeaveCriticalSection(&cookie_cs);
753         SetLastError(res);
754         return FALSE;
755     }
756 
757     if(cookie_set.cnt) {
758         if(!lpCookieData || cookie_set.string_len+1 > *lpdwSize) {
759             *lpdwSize = (cookie_set.string_len + 1) * sizeof(WCHAR);
760             TRACE("returning %u\n", *lpdwSize);
761             if(lpCookieData) {
762                 SetLastError(ERROR_INSUFFICIENT_BUFFER);
763                 ret = FALSE;
764             }
765         }else {
766             *lpdwSize = cookie_set.string_len + 1;
767             cookie_set_to_string(&cookie_set, lpCookieData);
768             lpCookieData[cookie_set.string_len] = 0;
769         }
770     }else {
771         TRACE("no cookies found for %s\n", debugstr_wn(host.str, host.len));
772         SetLastError(ERROR_NO_MORE_ITEMS);
773         ret = FALSE;
774     }
775 
776     heap_free(cookie_set.cookies);
777     LeaveCriticalSection(&cookie_cs);
778     return ret;
779 }
780 
781 /***********************************************************************
782  *           InternetGetCookieW (WININET.@)
783  *
784  * Retrieve cookie for the specified URL.
785  */
786 BOOL WINAPI InternetGetCookieW(const WCHAR *url, const WCHAR *name, WCHAR *data, DWORD *size)
787 {
788     TRACE("(%s, %s, %s, %p)\n", debugstr_w(url), debugstr_w(name), debugstr_w(data), size);
789 
790     return InternetGetCookieExW(url, name, data, size, 0, NULL);
791 }
792 
793 /***********************************************************************
794  *           InternetGetCookieExA (WININET.@)
795  *
796  * Retrieve cookie from the specified url
797  *
798  * RETURNS
799  *    TRUE  on success
800  *    FALSE on failure
801  *
802  */
803 BOOL WINAPI InternetGetCookieExA(LPCSTR lpszUrl, LPCSTR lpszCookieName,
804         LPSTR lpCookieData, LPDWORD lpdwSize, DWORD flags, void *reserved)
805 {
806     WCHAR *url, *name;
807     DWORD len, size = 0;
808     BOOL r;
809 
810     TRACE("(%s %s %p %p(%u) %x %p)\n", debugstr_a(lpszUrl), debugstr_a(lpszCookieName),
811           lpCookieData, lpdwSize, lpdwSize ? *lpdwSize : 0, flags, reserved);
812 
813     url = heap_strdupAtoW(lpszUrl);
814     name = heap_strdupAtoW(lpszCookieName);
815 
816     r = InternetGetCookieExW( url, name, NULL, &len, flags, reserved );
817     if( r )
818     {
819         WCHAR *szCookieData;
820 
821         szCookieData = heap_alloc(len * sizeof(WCHAR));
822         if( !szCookieData )
823         {
824             r = FALSE;
825         }
826         else
827         {
828             r = InternetGetCookieExW( url, name, szCookieData, &len, flags, reserved );
829 
830             if(r) {
831                 size = WideCharToMultiByte( CP_ACP, 0, szCookieData, len, NULL, 0, NULL, NULL);
832                 if(lpCookieData) {
833                     if(*lpdwSize >= size) {
834                         WideCharToMultiByte( CP_ACP, 0, szCookieData, len, lpCookieData, *lpdwSize, NULL, NULL);
835                     }else {
836                         SetLastError(ERROR_INSUFFICIENT_BUFFER);
837                         r = FALSE;
838                     }
839                 }
840             }
841 
842             heap_free( szCookieData );
843         }
844     }
845     *lpdwSize = size;
846     heap_free( name );
847     heap_free( url );
848     return r;
849 }
850 
851 /***********************************************************************
852  *           InternetGetCookieA (WININET.@)
853  *
854  * See InternetGetCookieW.
855  */
856 BOOL WINAPI InternetGetCookieA(const char *url, const char *name, char *data, DWORD *size)
857 {
858     TRACE("(%s, %s, %p, %p)\n", debugstr_a(url), debugstr_a(name), data, size);
859 
860     return InternetGetCookieExA(url, name, data, size, 0, NULL);
861 }
862 
863 static BOOL is_domain_legal_for_cookie(substr_t domain, substr_t full_domain)
864 {
865     const WCHAR *ptr;
866 
867     if(!domain.len || *domain.str == '.' || !full_domain.len || *full_domain.str == '.') {
868         SetLastError(ERROR_INVALID_NAME);
869         return FALSE;
870     }
871 
872     if(domain.len > full_domain.len || !memchrW(domain.str, '.', domain.len) || !memchrW(full_domain.str, '.', full_domain.len))
873         return FALSE;
874 
875     ptr = full_domain.str + full_domain.len - domain.len;
876     if (strncmpiW(domain.str, ptr, domain.len) || (full_domain.len > domain.len && ptr[-1] != '.')) {
877         SetLastError(ERROR_INVALID_PARAMETER);
878         return FALSE;
879     }
880 
881     return TRUE;
882 }
883 
884 /***********************************************************************
885  *           IsDomainLegalCookieDomainW (WININET.@)
886  */
887 BOOL WINAPI IsDomainLegalCookieDomainW(const WCHAR *domain, const WCHAR *full_domain)
888 {
889     FIXME("(%s, %s) semi-stub\n", debugstr_w(domain), debugstr_w(full_domain));
890 
891     if (!domain || !full_domain) {
892         SetLastError(ERROR_INVALID_PARAMETER);
893         return FALSE;
894     }
895 
896     return is_domain_legal_for_cookie(substrz(domain), substrz(full_domain));
897 }
898 
899 static void substr_skip(substr_t *str, size_t len)
900 {
901     assert(str->len >= len);
902     str->str += len;
903     str->len -= len;
904 }
905 
906 DWORD set_cookie(substr_t domain, substr_t path, substr_t name, substr_t data, DWORD flags)
907 {
908     cookie_container_t *container;
909     cookie_t *thisCookie;
910     substr_t value;
911     const WCHAR *end_ptr;
912     FILETIME expiry, create;
913     BOOL expired = FALSE, update_persistent = FALSE;
914     DWORD cookie_flags = 0, len;
915 
916     TRACE("%s %s %s=%s %x\n", debugstr_wn(domain.str, domain.len), debugstr_wn(path.str, path.len),
917           debugstr_wn(name.str, name.len), debugstr_wn(data.str, data.len), flags);
918 
919     memset(&expiry,0,sizeof(expiry));
920     GetSystemTimeAsFileTime(&create);
921 
922     /* lots of information can be parsed out of the cookie value */
923 
924     if(!(end_ptr = memchrW(data.str, ';', data.len)))
925        end_ptr = data.str + data.len;
926     value = substr(data.str, end_ptr-data.str);
927     data.str += value.len;
928     data.len -= value.len;
929 
930     for(;;) {
931         static const WCHAR szDomain[] = {'d','o','m','a','i','n','='};
932         static const WCHAR szPath[] = {'p','a','t','h','='};
933         static const WCHAR szExpires[] = {'e','x','p','i','r','e','s','='};
934         static const WCHAR szSecure[] = {'s','e','c','u','r','e'};
935         static const WCHAR szHttpOnly[] = {'h','t','t','p','o','n','l','y'};
936         static const WCHAR szVersion[] = {'v','e','r','s','i','o','n','='};
937         static const WCHAR max_ageW[] = {'m','a','x','-','a','g','e','='};
938 
939         /* Skip ';' */
940         if(data.len)
941             substr_skip(&data, 1);
942 
943         while(data.len && *data.str == ' ')
944             substr_skip(&data, 1);
945 
946         if(!data.len)
947             break;
948 
949         if(!(end_ptr = memchrW(data.str, ';', data.len)))
950             end_ptr = data.str + data.len;
951 
952         if(data.len >= (len = sizeof(szDomain)/sizeof(WCHAR)) && !strncmpiW(data.str, szDomain, len)) {
953             substr_skip(&data, len);
954 
955             if(data.len && *data.str == '.')
956                 substr_skip(&data, 1);
957 
958             if(!is_domain_legal_for_cookie(substr(data.str, end_ptr-data.str), domain))
959                 return COOKIE_STATE_UNKNOWN;
960 
961             domain = substr(data.str, end_ptr-data.str);
962             TRACE("Parsing new domain %s\n", debugstr_wn(domain.str, domain.len));
963         }else if(data.len >= (len = sizeof(szPath)/sizeof(WCHAR)) && !strncmpiW(data.str, szPath, len)) {
964             substr_skip(&data, len);
965             path = substr(data.str, end_ptr - data.str);
966             TRACE("Parsing new path %s\n", debugstr_wn(path.str, path.len));
967         }else if(data.len >= (len = sizeof(szExpires)/sizeof(WCHAR)) && !strncmpiW(data.str, szExpires, len)) {
968             SYSTEMTIME st;
969             WCHAR buf[128];
970 
971             substr_skip(&data, len);
972 
973             if(end_ptr - data.str < sizeof(buf)/sizeof(WCHAR)-1) {
974                 memcpy(buf, data.str, data.len*sizeof(WCHAR));
975                 buf[data.len] = 0;
976 
977                 if (InternetTimeToSystemTimeW(data.str, &st, 0)) {
978                     SystemTimeToFileTime(&st, &expiry);
979 
980                     if (CompareFileTime(&create,&expiry) > 0) {
981                         TRACE("Cookie already expired.\n");
982                         expired = TRUE;
983                     }
984                 }
985             }
986         }else if(data.len >= (len = sizeof(szSecure)/sizeof(WCHAR)) && !strncmpiW(data.str, szSecure, len)) {
987             substr_skip(&data, len);
988             FIXME("secure not handled\n");
989         }else if(data.len >= (len = sizeof(szHttpOnly)/sizeof(WCHAR)) && !strncmpiW(data.str, szHttpOnly, len)) {
990             substr_skip(&data, len);
991 
992             if(!(flags & INTERNET_COOKIE_HTTPONLY)) {
993                 WARN("HTTP only cookie added without INTERNET_COOKIE_HTTPONLY flag\n");
994                 SetLastError(ERROR_INVALID_OPERATION);
995                 return COOKIE_STATE_REJECT;
996             }
997 
998             cookie_flags |= INTERNET_COOKIE_HTTPONLY;
999         }else if(data.len >= (len = sizeof(szVersion)/sizeof(WCHAR)) && !strncmpiW(data.str, szVersion, len)) {
1000             substr_skip(&data, len);
1001 
1002             FIXME("version not handled (%s)\n",debugstr_wn(data.str, data.len));
1003         }else if(data.len >= (len = sizeof(max_ageW)/sizeof(WCHAR)) && !strncmpiW(data.str, max_ageW, len)) {
1004             /* Native doesn't support Max-Age attribute. */
1005             WARN("Max-Age ignored\n");
1006         }else if(data.len) {
1007             FIXME("Unknown additional option %s\n", debugstr_wn(data.str, data.len));
1008         }
1009 
1010         substr_skip(&data, end_ptr - data.str);
1011     }
1012 
1013     EnterCriticalSection(&cookie_cs);
1014 
1015     load_persistent_cookie(domain, path);
1016 
1017     container = get_cookie_container(domain, path, !expired);
1018     if(!container) {
1019         LeaveCriticalSection(&cookie_cs);
1020         return COOKIE_STATE_ACCEPT;
1021     }
1022 
1023     if(!expiry.dwLowDateTime && !expiry.dwHighDateTime)
1024         cookie_flags |= INTERNET_COOKIE_IS_SESSION;
1025     else
1026         update_persistent = TRUE;
1027 
1028     if ((thisCookie = find_cookie(container, name))) {
1029         if ((thisCookie->flags & INTERNET_COOKIE_HTTPONLY) && !(flags & INTERNET_COOKIE_HTTPONLY)) {
1030             WARN("An attempt to override httponly cookie\n");
1031             SetLastError(ERROR_INVALID_OPERATION);
1032             LeaveCriticalSection(&cookie_cs);
1033             return COOKIE_STATE_REJECT;
1034         }
1035 
1036         if (!(thisCookie->flags & INTERNET_COOKIE_IS_SESSION))
1037             update_persistent = TRUE;
1038         delete_cookie(thisCookie);
1039     }
1040 
1041     TRACE("setting cookie %s=%s for domain %s path %s\n", debugstr_wn(name.str, name.len),
1042           debugstr_wn(value.str, value.len), debugstr_w(container->domain->domain),
1043           debugstr_wn(container->path.str, container->path.len));
1044 
1045     if (!expired) {
1046         cookie_t *new_cookie;
1047 
1048         new_cookie = alloc_cookie(name, value, expiry, create, cookie_flags);
1049         if(!new_cookie) {
1050             LeaveCriticalSection(&cookie_cs);
1051             return COOKIE_STATE_UNKNOWN;
1052         }
1053 
1054         add_cookie(container, new_cookie);
1055     }
1056 
1057     if (!update_persistent || save_persistent_cookie(container))
1058     {
1059         LeaveCriticalSection(&cookie_cs);
1060         return COOKIE_STATE_ACCEPT;
1061     }
1062     LeaveCriticalSection(&cookie_cs);
1063     return COOKIE_STATE_UNKNOWN;
1064 }
1065 
1066 /***********************************************************************
1067  *           InternetSetCookieExW (WININET.@)
1068  *
1069  * Sets cookie for the specified url
1070  */
1071 DWORD WINAPI InternetSetCookieExW(LPCWSTR lpszUrl, LPCWSTR lpszCookieName,
1072         LPCWSTR lpCookieData, DWORD flags, DWORD_PTR reserved)
1073 {
1074     substr_t host, path, name, data;
1075     BOOL ret;
1076 
1077     TRACE("(%s, %s, %s, %x, %lx)\n", debugstr_w(lpszUrl), debugstr_w(lpszCookieName),
1078           debugstr_w(lpCookieData), flags, reserved);
1079 
1080     if (flags & ~INTERNET_COOKIE_HTTPONLY)
1081         FIXME("flags %x not supported\n", flags);
1082 
1083     if (!lpszUrl || !lpCookieData)
1084     {
1085         SetLastError(ERROR_INVALID_PARAMETER);
1086         return COOKIE_STATE_UNKNOWN;
1087     }
1088 
1089     ret = cookie_parse_url(lpszUrl, &host, &path);
1090     if (!ret || !host.len) return COOKIE_STATE_UNKNOWN;
1091 
1092     if (!lpszCookieName) {
1093         const WCHAR *ptr;
1094 
1095         /* some apps (or is it us??) try to add a cookie with no cookie name, but
1096          * the cookie data in the form of name[=data].
1097          */
1098         if (!(ptr = strchrW(lpCookieData, '=')))
1099             ptr = lpCookieData + strlenW(lpCookieData);
1100 
1101         name = substr(lpCookieData, ptr - lpCookieData);
1102         data = substrz(*ptr == '=' ? ptr+1 : ptr);
1103     }else {
1104         name = substrz(lpszCookieName);
1105         data = substrz(lpCookieData);
1106     }
1107 
1108     return set_cookie(host, path, name, data, flags);
1109 }
1110 
1111 /***********************************************************************
1112  *           InternetSetCookieW (WININET.@)
1113  *
1114  * Sets a cookie for the specified URL.
1115  */
1116 BOOL WINAPI InternetSetCookieW(const WCHAR *url, const WCHAR *name, const WCHAR *data)
1117 {
1118     TRACE("(%s, %s, %s)\n", debugstr_w(url), debugstr_w(name), debugstr_w(data));
1119 
1120     return InternetSetCookieExW(url, name, data, 0, 0) == COOKIE_STATE_ACCEPT;
1121 }
1122 
1123 /***********************************************************************
1124  *           InternetSetCookieA (WININET.@)
1125  *
1126  * Sets cookie for the specified url
1127  *
1128  * RETURNS
1129  *    TRUE  on success
1130  *    FALSE on failure
1131  *
1132  */
1133 BOOL WINAPI InternetSetCookieA(LPCSTR lpszUrl, LPCSTR lpszCookieName,
1134     LPCSTR lpCookieData)
1135 {
1136     LPWSTR data, url, name;
1137     BOOL r;
1138 
1139     TRACE("(%s,%s,%s)\n", debugstr_a(lpszUrl),
1140         debugstr_a(lpszCookieName), debugstr_a(lpCookieData));
1141 
1142     url = heap_strdupAtoW(lpszUrl);
1143     name = heap_strdupAtoW(lpszCookieName);
1144     data = heap_strdupAtoW(lpCookieData);
1145 
1146     r = InternetSetCookieW( url, name, data );
1147 
1148     heap_free( data );
1149     heap_free( name );
1150     heap_free( url );
1151     return r;
1152 }
1153 
1154 /***********************************************************************
1155  *           InternetSetCookieExA (WININET.@)
1156  *
1157  * See InternetSetCookieExW.
1158  */
1159 DWORD WINAPI InternetSetCookieExA( LPCSTR lpszURL, LPCSTR lpszCookieName, LPCSTR lpszCookieData,
1160                                    DWORD dwFlags, DWORD_PTR dwReserved)
1161 {
1162     WCHAR *data, *url, *name;
1163     DWORD r;
1164 
1165     TRACE("(%s, %s, %s, %x, %lx)\n", debugstr_a(lpszURL), debugstr_a(lpszCookieName),
1166           debugstr_a(lpszCookieData), dwFlags, dwReserved);
1167 
1168     url = heap_strdupAtoW(lpszURL);
1169     name = heap_strdupAtoW(lpszCookieName);
1170     data = heap_strdupAtoW(lpszCookieData);
1171 
1172     r = InternetSetCookieExW(url, name, data, dwFlags, dwReserved);
1173 
1174     heap_free( data );
1175     heap_free( name );
1176     heap_free( url );
1177     return r;
1178 }
1179 
1180 /***********************************************************************
1181  *           InternetClearAllPerSiteCookieDecisions (WININET.@)
1182  *
1183  * Clears all per-site decisions about cookies.
1184  *
1185  * RETURNS
1186  *    TRUE  on success
1187  *    FALSE on failure
1188  *
1189  */
1190 BOOL WINAPI InternetClearAllPerSiteCookieDecisions( VOID )
1191 {
1192     FIXME("stub\n");
1193     return TRUE;
1194 }
1195 
1196 /***********************************************************************
1197  *           InternetEnumPerSiteCookieDecisionA (WININET.@)
1198  *
1199  * See InternetEnumPerSiteCookieDecisionW.
1200  */
1201 BOOL WINAPI InternetEnumPerSiteCookieDecisionA( LPSTR pszSiteName, ULONG *pcSiteNameSize,
1202                                                 ULONG *pdwDecision, ULONG dwIndex )
1203 {
1204     FIXME("(%s, %p, %p, 0x%08x) stub\n",
1205           debugstr_a(pszSiteName), pcSiteNameSize, pdwDecision, dwIndex);
1206     return FALSE;
1207 }
1208 
1209 /***********************************************************************
1210  *           InternetEnumPerSiteCookieDecisionW (WININET.@)
1211  *
1212  * Enumerates all per-site decisions about cookies.
1213  *
1214  * RETURNS
1215  *    TRUE  on success
1216  *    FALSE on failure
1217  *
1218  */
1219 BOOL WINAPI InternetEnumPerSiteCookieDecisionW( LPWSTR pszSiteName, ULONG *pcSiteNameSize,
1220                                                 ULONG *pdwDecision, ULONG dwIndex )
1221 {
1222     FIXME("(%s, %p, %p, 0x%08x) stub\n",
1223           debugstr_w(pszSiteName), pcSiteNameSize, pdwDecision, dwIndex);
1224     return FALSE;
1225 }
1226 
1227 /***********************************************************************
1228  *           InternetGetPerSiteCookieDecisionA (WININET.@)
1229  */
1230 BOOL WINAPI InternetGetPerSiteCookieDecisionA( LPCSTR pwchHostName, ULONG *pResult )
1231 {
1232     FIXME("(%s, %p) stub\n", debugstr_a(pwchHostName), pResult);
1233     return FALSE;
1234 }
1235 
1236 /***********************************************************************
1237  *           InternetGetPerSiteCookieDecisionW (WININET.@)
1238  */
1239 BOOL WINAPI InternetGetPerSiteCookieDecisionW( LPCWSTR pwchHostName, ULONG *pResult )
1240 {
1241     FIXME("(%s, %p) stub\n", debugstr_w(pwchHostName), pResult);
1242     return FALSE;
1243 }
1244 
1245 /***********************************************************************
1246  *           InternetSetPerSiteCookieDecisionA (WININET.@)
1247  */
1248 BOOL WINAPI InternetSetPerSiteCookieDecisionA( LPCSTR pchHostName, DWORD dwDecision )
1249 {
1250     FIXME("(%s, 0x%08x) stub\n", debugstr_a(pchHostName), dwDecision);
1251     return FALSE;
1252 }
1253 
1254 /***********************************************************************
1255  *           InternetSetPerSiteCookieDecisionW (WININET.@)
1256  */
1257 BOOL WINAPI InternetSetPerSiteCookieDecisionW( LPCWSTR pchHostName, DWORD dwDecision )
1258 {
1259     FIXME("(%s, 0x%08x) stub\n", debugstr_w(pchHostName), dwDecision);
1260     return FALSE;
1261 }
1262 
1263 void free_cookie(void)
1264 {
1265     EnterCriticalSection(&cookie_cs);
1266 
1267     free_cookie_domain_list(&domain_list);
1268 
1269     LeaveCriticalSection(&cookie_cs);
1270 }
1271